Contrast sweeps for the faint grey and dark-only chips, and page-scoped a11y-baseline entries (#772, #775, #771) #793

Merged
claude-bot merged 5 commits from build/a11y-sweep into main 2026-09-24 02:47:26 +00:00
Collaborator

Closes #772, #775 and #771. Owner, 2026-09-23 (B6, B8): now, in one PR.

This PR only changes colour classes, plus the baseline mechanism. No behaviour or layout changes. Every pair was checked by computing WCAG contrast from Tailwind v4's oklch palette, in both modes, against the surface each sits on. Translucent backgrounds (/50, /20) are composited over the paper first. Every changed pair reaches 4.5:1.

#772: the faint-line grey

text-gray-400 dark:text-gray-600 measured 2.60:1 light and 2.66:1 dark. It's now text-gray-500 dark:text-gray-400: 4.84:1 light, 7.73:1 dark (6.82:1 on the nested panel). There were 17 text sites across BeatNotesEditor, ColdOpenPanel, ShelfPalette (×3), CampaignPlanning, SessionDetail, SessionShelf (×7) and SessionTable. One site was left alone: a resize grip in SessionShelf that is aria-hidden decoration, not text.

#775: dark-only chips, and a grey that was too weak

Every bare bg-{color}-900/50 text-{color}-300 (plus the /40 and /60 siblings in the same status maps) now has a light pair, bg-{color}-100 text-{color}-800, with the old look kept behind dark:. Colour meaning is unchanged: amber is maybe/running, green yes/done, red no/failed.

  • Light mode: 6.40–8.18:1.
  • Dark mode: 6.82–11.55:1.
  • Files: CampaignAiSettings, PostSessionChecklist, RecordingPanel, SessionRsvp, VotingGrid, ContentPacks, Admin (×8 plus two status maps), CampaignDetail and SessionDetail (status maps, badges, the attendance toggle).

The issue under-called one of these. PostSessionChecklist's grey "none" and "skipped" was text-gray-500 dark:text-gray-500. #775 said its dark side "may already be fine". It isn't: it measures 3.04:1 dark, as well as 3.9:1 light. It's now text-gray-700 dark:text-gray-400, at 8.33:1 and 5.64:1.

#775's named SessionDetail items are all fixed:

  • The winning time-slot panel and its "Confirmed" label went from 1.21:1 to 7.09 / 13.21:1.
  • The confirmed-time line went from 1.34:1 to 7.09 / 10.64:1.
  • The "your time" line went from 2.48:1 to 7.56 / 7.73:1.
  • .text-indigo-400 was 2.99:1 on white. The same bare class appeared about 14 times across SessionDetail and the children it renders, so every occurrence is now text-indigo-600 dark:text-indigo-400, including the hover states: 6.44:1 and up in light, 6.45:1 and up in dark.

#771: page-scoped baseline entries

A11Y_BASELINE can now be keyed by a route pattern such as /campaigns/:id/planning, as well as by an exact pathname or "*". Lookup goes: an exact key first, then the first pattern key that matches; "*"'s entries always apply on top of either.

The owner's rule is enforced: every entry's owner text must name an issue (#<number>), or the module throws when it loads and every journey fails loudly. The file's contents stay {}. With this sweep, nothing needs an entry. It's documented in the file header and in the e2e README.

Left alone, and filed

Same family of defect, but outside both issues' targets, and fixing them here would have doubled the PR. They're filed together as a follow-up:

  • about 15 full-width alert banners in Admin, CampaignDetail and WikiFront that are dark-only;
  • bare text-green-400 / text-red-400 status words in QuestLog, CampaignPlanning, LootLedger, entryTypes.js and VotingGrid;
  • VotingGrid's base text-gray-500 dark:text-gray-600, at 2.66:1 dark.

Checks

  • vitest 1928/1928 in 141 files (--maxWorkers=2). One file hit a worker-start timeout from machine contention on the full run, and passed 8/8 when re-run alone.
  • eslint 0 errors, warnings unchanged. Every touched file has equal insertions and deletions, all class-string edits, so no warning line moved.
  • The baseline's pattern matching, precedence and missing-issue refusal were exercised with a throwaway harness.

🤖 Generated with Claude Code

Closes #772, #775 and #771. Owner, 2026-09-23 (B6, B8): now, in one PR. This PR only changes colour classes, plus the baseline mechanism. **No behaviour or layout changes.** Every pair was checked by computing WCAG contrast from Tailwind v4's `oklch` palette, in **both** modes, against the surface each sits on. Translucent backgrounds (`/50`, `/20`) are composited over the paper first. Every changed pair reaches **4.5:1**. ## #772: the faint-line grey `text-gray-400 dark:text-gray-600` measured **2.60:1** light and **2.66:1** dark. It's now `text-gray-500 dark:text-gray-400`: **4.84:1** light, **7.73:1** dark (6.82:1 on the nested panel). There were 17 text sites across `BeatNotesEditor`, `ColdOpenPanel`, `ShelfPalette` (×3), `CampaignPlanning`, `SessionDetail`, `SessionShelf` (×7) and `SessionTable`. One site was left alone: a resize grip in `SessionShelf` that is `aria-hidden` decoration, not text. ## #775: dark-only chips, and a grey that was too weak Every bare `bg-{color}-900/50 text-{color}-300` (plus the `/40` and `/60` siblings in the same status maps) now has a light pair, `bg-{color}-100 text-{color}-800`, with the old look kept behind `dark:`. Colour meaning is unchanged: amber is maybe/running, green yes/done, red no/failed. - **Light mode:** 6.40–8.18:1. - **Dark mode:** 6.82–11.55:1. - **Files:** `CampaignAiSettings`, `PostSessionChecklist`, `RecordingPanel`, `SessionRsvp`, `VotingGrid`, `ContentPacks`, `Admin` (×8 plus two status maps), `CampaignDetail` and `SessionDetail` (status maps, badges, the attendance toggle). **The issue under-called one of these.** `PostSessionChecklist`'s grey "none" and "skipped" was `text-gray-500 dark:text-gray-500`. #775 said its dark side "may already be fine". It isn't: it measures **3.04:1** dark, as well as 3.9:1 light. It's now `text-gray-700 dark:text-gray-400`, at 8.33:1 and 5.64:1. **#775's named `SessionDetail` items are all fixed:** - The winning time-slot panel and its "Confirmed" label went from 1.21:1 to 7.09 / 13.21:1. - The confirmed-time line went from 1.34:1 to 7.09 / 10.64:1. - The "your time" line went from 2.48:1 to 7.56 / 7.73:1. - `.text-indigo-400` was 2.99:1 on white. The same bare class appeared about 14 times across `SessionDetail` and the children it renders, so every occurrence is now `text-indigo-600 dark:text-indigo-400`, including the hover states: 6.44:1 and up in light, 6.45:1 and up in dark. ## #771: page-scoped baseline entries `A11Y_BASELINE` can now be keyed by a route pattern such as `/campaigns/:id/planning`, as well as by an exact pathname or `"*"`. Lookup goes: an exact key first, then the first pattern key that matches; `"*"`'s entries always apply on top of either. **The owner's rule is enforced:** every entry's `owner` text must name an issue (`#<number>`), or the module throws when it loads and every journey fails loudly. **The file's contents stay `{}`.** With this sweep, nothing needs an entry. It's documented in the file header and in the e2e README. ## Left alone, and filed Same family of defect, but outside both issues' targets, and fixing them here would have doubled the PR. They're filed together as a follow-up: - about 15 **full-width alert banners** in `Admin`, `CampaignDetail` and `WikiFront` that are dark-only; - bare `text-green-400` / `text-red-400` status words in `QuestLog`, `CampaignPlanning`, `LootLedger`, `entryTypes.js` and `VotingGrid`; - `VotingGrid`'s base `text-gray-500 dark:text-gray-600`, at 2.66:1 dark. ## Checks - vitest **1928/1928** in 141 files (`--maxWorkers=2`). One file hit a worker-start timeout from machine contention on the full run, and passed 8/8 when re-run alone. - eslint **0 errors**, warnings unchanged. Every touched file has equal insertions and deletions, all class-string edits, so no warning line moved. - The baseline's pattern matching, precedence and missing-issue refusal were exercised with a throwaway harness. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(frontend): contrast sweeps for the faint grey and dark-only chips, and a11y-baseline patterns (#772, #775, #771)
Some checks failed
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 49s
CI / Backend lint (ruff) (pull_request) Has been cancelled
CI / Feedback site tests (pull_request) Has been cancelled
CI / Bot/backend version sync (pull_request) Has been cancelled
CI / Docker image build (pull_request) Has been cancelled
CI / Does this change touch the recording path? (pull_request) Has been cancelled
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been cancelled
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Has been cancelled
CI / Frontend tests, audit, and build (pull_request) Has been cancelled
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
CI / Bot tests and audit (pull_request) Has been cancelled
56148d7b53
Two contrast sweeps decided by the owner on 2026-09-23 (#729, B6/B8):

- text-gray-400 dark:text-gray-600 (2.6:1 light, 2.7:1 dark) is
  text-gray-500 dark:text-gray-400 now, on every text site the pair was
  used for across BeatNotesEditor, ColdOpenPanel, ShelfPalette,
  CampaignPlanning, SessionDetail, SessionShelf and SessionTable — the
  pair #770 already established for WikiFront.jsx. One decorative
  resize-grip icon (aria-hidden) was left alone.
- Status chips built dark-only (bg-{color}-900/50 text-{color}-300 with
  no dark: prefix) across RSVP, the post-session checklist, content
  packs, votes, recordings, wiki suggestions and Admin's badges now have
  an opaque light-mode pair behind a dark: prefix on what was already
  there, keeping each colour's meaning. SessionDetail.jsx and the
  children it renders also had a green-900/20 winner panel, a
  confirmed-time line, and every plain text-indigo-400 link fixed the
  same way.

Every pair was checked with a small oklch-based contrast script against
Tailwind's own palette (not committed — scratchpad only), composited
over the paper where the dark background is translucent: every pair
clears 4.5:1 in both modes, most well past it.

Also (#771): A11Y_BASELINE can now be keyed by a react-router-style
:param pattern as well as an exact pathname, for a page whose address
carries a per-run UUID — looked up exact-first, then the first matching
pattern, with "*" layered on in addition as before. An entry with no
issue reference in its owner text now throws at import time. The
baseline itself stays {}.

Verified in Docker: `npx vitest run --maxWorkers=2` — 141 files, 1928
tests, all passing (one file hit a transient worker-pool timeout under
machine load on the first pass and was re-run alone to confirm).
`npx eslint src e2e` — 0 errors, 228 warnings, unchanged from before
this change (every edited file has equal insertion/deletion counts, so
no line-numbered warning could have moved, and none coincide with an
edited line).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Merge remote-tracking branch 'origin/main' into build/a11y-sweep
All checks were successful
CI / Backend lint (ruff) (pull_request) Successful in 1m36s
CI / Feedback site tests (pull_request) Successful in 46s
CI / Does this change touch the recording path? (pull_request) Successful in 14s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Bot/backend version sync (pull_request) Successful in 1m7s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 3m14s
CI / Bot tests and audit (pull_request) Successful in 4m49s
CI / Docker image build (pull_request) Successful in 3m8s
CI / Frontend tests, audit, and build (pull_request) Successful in 6m40s
CI / Backend migration, tests, and audit (pull_request) Successful in 9m45s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 16m37s
ccae86b50e
# Conflicts:
#	CHANGELOG.md
Merge remote-tracking branch 'origin/main' into build/a11y-sweep
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 59s
CI / Backend lint (ruff) (pull_request) Successful in 56s
CI / Does this change touch the recording path? (pull_request) Successful in 19s
CI / Bot/backend version sync (pull_request) Successful in 48s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Feedback site tests (pull_request) Successful in 1m22s
CI / Bot tests and audit (pull_request) Successful in 3m15s
CI / Docker image build (pull_request) Successful in 4m9s
CI / Frontend tests, audit, and build (pull_request) Successful in 7m26s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 6m25s
CI / Backend migration, tests, and audit (pull_request) Successful in 17m11s
ae0f761b01
# Conflicts:
#	CHANGELOG.md
Merge remote-tracking branch 'origin/main' into build/a11y-sweep
Some checks failed
CI / Backend lint (ruff) (pull_request) Successful in 39s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m19s
CI / Bot/backend version sync (pull_request) Successful in 32s
CI / Does this change touch the recording path? (pull_request) Successful in 17s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Feedback site tests (pull_request) Successful in 1m0s
CI / Bot tests and audit (pull_request) Successful in 1m49s
CI / Frontend tests, audit, and build (pull_request) Failing after 4m37s
CI / Docker image build (pull_request) Successful in 4m3s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Has been cancelled
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
f6bbcbe7a5
# Conflicts:
#	CHANGELOG.md
test(frontend): Home's tests load the page before they wait on it
All checks were successful
CI / Backend lint (ruff) (pull_request) Successful in 29s
CI / Bot/backend version sync (pull_request) Successful in 26s
CI / Does this change touch the recording path? (pull_request) Successful in 18s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Feedback site tests (pull_request) Successful in 1m45s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 2m10s
CI / Bot tests and audit (pull_request) Successful in 3m45s
CI / Docker image build (pull_request) Successful in 4m17s
CI / Frontend tests, audit, and build (pull_request) Successful in 6m49s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 6m35s
CI / Backend migration, tests, and audit (pull_request) Successful in 22m36s
37e6eaba23
Separate from this PR's contrast work; here because it failed this PR's CI
(run #10457) and would fail any PR the same way.

`Home.test.jsx` renders the full route table, where `App.jsx` imports Home
lazily. So the first test in the file paid the page's cold import inside its
own five-second `findByRole`. On a loaded runner that wasn't always enough:
the route was still on its "Loading…" fallback when the wait ran out, and
whichever test ran first failed — here "is not there at all when nothing is
waiting (SH-040)". It passed on #789's own run and locally on the same tree.

A `beforeAll` now imports the page once, so every test's wait covers the
render and nothing else. Three runs of the file on a single CPU: 35/35 each.

Another race in the family this week: something slow happening inside a
wait that was sized for something fast.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign in to join this conversation.
No description provided.