[UX] Accessibility pass #367

Open
opened 2026-08-25 20:39:27 +00:00 by claude-bot · 10 comments
Collaborator

Found in the August 2026 session lifecycle review (#319). Not separately audited in the review — filed because the rebuild is the moment it is cheap, and because several findings point at it directly.

Why

The review surfaced accessibility-adjacent problems incidentally rather than by looking for them: touch targets well under any usable minimum throughout, core Session Shelf interactions available only via keyboard with no alternative, disabled states explained only by title= tooltips (invisible on touch and to screen readers), status conveyed by colour alone in the voting grid, and a "Wiki" control styled exactly like a tab that actually opens a new browser tab.

None of that was the audit's focus, so the real state is unknown. That is itself the finding.

What

A genuine pass during the rebuild rather than a retrofit: keyboard navigability of every flow, focus management in modals and after navigation, semantic landmarks and headings, form labelling and error association, colour contrast and non-colour status encoding, screen-reader announcement of async state changes (a summary finishing processing should be announced, not silently swapped in), and honest interactive semantics — a link that opens a new tab should say so.

Set a target standard explicitly rather than leaving it implied.

Acceptance criteria

  • A target standard is chosen and stated
  • Every core journey completable by keyboard alone
  • Focus managed on modal open/close and route change
  • Status never conveyed by colour alone
  • Async state changes announced to assistive technology
  • Automated checks in CI, plus one manual screen-reader pass of the core journeys
Found in the August 2026 session lifecycle review (#319). Not separately audited in the review — filed because the rebuild is the moment it is cheap, and because several findings point at it directly. ## Why The review surfaced accessibility-adjacent problems incidentally rather than by looking for them: touch targets well under any usable minimum throughout, core Session Shelf interactions available only via keyboard with no alternative, disabled states explained only by `title=` tooltips (invisible on touch and to screen readers), status conveyed by colour alone in the voting grid, and a "Wiki" control styled exactly like a tab that actually opens a new browser tab. None of that was the audit's focus, so the real state is unknown. That is itself the finding. ## What A genuine pass during the rebuild rather than a retrofit: keyboard navigability of every flow, focus management in modals and after navigation, semantic landmarks and headings, form labelling and error association, colour contrast and non-colour status encoding, screen-reader announcement of async state changes (a summary finishing processing should be announced, not silently swapped in), and honest interactive semantics — a link that opens a new tab should say so. Set a target standard explicitly rather than leaving it implied. ## Acceptance criteria - [ ] A target standard is chosen and stated - [ ] Every core journey completable by keyboard alone - [ ] Focus managed on modal open/close and route change - [ ] Status never conveyed by colour alone - [ ] Async state changes announced to assistive technology - [ ] Automated checks in CI, plus one manual screen-reader pass of the core journeys
Author
Collaborator

Addition (owner, 2026-09-15; decision D4 in the surface catalogue, #659): Quest Board gets an accessibility statement page (catalogue surface PUB-10), public and linked from the account menu and the Help pages. It states:

  • the WCAG 2.2 AA target
  • the known gaps
  • how to report a barrier

Keep its known-gaps list true to what this pass finds. The catalogue also lists #367's other user-visible requirements under the shared patterns (SH-7): skip link, announcements when async work finishes, status never shown by colour alone, and focus management.

**Addition (owner, 2026-09-15; decision D4 in the surface catalogue, #659):** Quest Board gets an **accessibility statement page** (catalogue surface PUB-10), public and linked from the account menu and the Help pages. It states: - the WCAG 2.2 AA target - the known gaps - how to report a barrier Keep its known-gaps list true to what this pass finds. The catalogue also lists #367's other user-visible requirements under the shared patterns (SH-7): skip link, announcements when async work finishes, status never shown by colour alone, and focus management.
Author
Collaborator

The standard is chosen: WCAG 2.2 AA (build plan §5, 2026-09-21), and it is checked three ways rather than once at the end:

  • eslint-plugin-jsx-a11y in the frontend lint job, from lane 0 (#364);
  • an axe scan (@axe-core/playwright) on every page a journey visits, inside the journeys suite (#726), so a violation fails the PR that introduced it;
  • the keyboard-only project of the same suite, which runs every journey with Tab, Enter and Space and no pointer, on every PR — that is "every core journey completable by keyboard alone" as a continuous check.

What stays for this issue at the end: the manual screen-reader pass of the seven journeys, focus management on modal open and close and on route change (a rule of the Modal and page components in lane 0), and status announcements for async changes (the Busy and SaveState components carry live regions). Umbrella: #366.

**The standard is chosen: WCAG 2.2 AA** (build plan §5, 2026-09-21), and it is checked three ways rather than once at the end: - `eslint-plugin-jsx-a11y` in the frontend lint job, from lane 0 (#364); - an axe scan (`@axe-core/playwright`) on every page a journey visits, inside the journeys suite (#726), so a violation fails the PR that introduced it; - the keyboard-only project of the same suite, which runs every journey with Tab, Enter and Space and no pointer, on every PR — that is "every core journey completable by keyboard alone" as a continuous check. What stays for this issue at the end: the manual screen-reader pass of the seven journeys, focus management on modal open and close and on route change (a rule of the `Modal` and page components in lane 0), and status announcements for async changes (the `Busy` and `SaveState` components carry live regions). Umbrella: #366.
Author
Collaborator

Picking up the automatable part (3 October, while the owner is away). A code audit against comment 42364's standard found these gaps. The manual screen-reader pass and the real-device check stay the owner's.

Fixing now (branch fix/367-a11y-gaps):

  1. Transcript state. TranscriptState.jsx puts the status title outside any live region, so a transcript that finishes or fails is not announced; only the inner Busy is live, and it unmounts. The wrapper becomes the persistent role=status. This is the very example in this issue's body.
  2. Public pages. PublicFrame (/, /login, /help…, /accessibility, /privacy, /licences, /join/:code, /setup) never calls useRouteFocus, so client-side navigation there leaves focus where it was.
  3. Phone Contents sheet. ContentsSheet moves focus in, restores it and closes on Escape, but does not contain focus: Tab walks into the page behind the scrim. It gets the same containment as Modal.
  4. Modal. It returns focus to <body> when its trigger was removed while it was open, for example a deleted row's button. It falls back to the page's heading or <main>. Its first-focus selector also skips disabled controls.
  5. Statement page (D4). It is linked from the account menu, as D4 says (today it is only in the footer), and its known-gaps list gains the open items below.
  6. axe coverage. axe runs on /privacy, /accessibility and /licences (the j0w loop loads them and never scans them), plus /help, the 404 and /auth-error.

Not in this branch. Each needs a render check or the owner:

  • Map markers render at 16 px and are focusable (Map.css:242), and standalone .linkish buttons have no padding. Both need sizing to at least 24 px, and that changes layout.
  • The statement says the map's panning and zooming need a pointer. Leaflet's keyboard is on (MapCanvas.jsx:102), so that line may be stale; it needs checking in a browser before rewording.
  • Many routes have no axe scan: the campaign settings sub-pages, wiki map, history, edit and new, and the admin sub-pages. That list is the next slice.
  • A shared persistent announcer for conditionally mounted Busy and Banner: whether it is needed is the manual pass's call.
  • A skip link on public pages: low value, and it changes layout.
**Picking up the automatable part** (3 October, while the owner is away). A code audit against comment 42364's standard found these gaps. The manual screen-reader pass and the real-device check stay the owner's. **Fixing now** (branch `fix/367-a11y-gaps`): 1. **Transcript state.** `TranscriptState.jsx` puts the status title outside any live region, so a transcript that finishes or fails is not announced; only the inner `Busy` is live, and it unmounts. The wrapper becomes the persistent `role=status`. This is the very example in this issue's body. 2. **Public pages.** `PublicFrame` (`/`, `/login`, `/help…`, `/accessibility`, `/privacy`, `/licences`, `/join/:code`, `/setup`) never calls `useRouteFocus`, so client-side navigation there leaves focus where it was. 3. **Phone Contents sheet.** `ContentsSheet` moves focus in, restores it and closes on Escape, but does not contain focus: Tab walks into the page behind the scrim. It gets the same containment as `Modal`. 4. **`Modal`.** It returns focus to `<body>` when its trigger was removed while it was open, for example a deleted row's button. It falls back to the page's heading or `<main>`. Its first-focus selector also skips disabled controls. 5. **Statement page (D4).** It is linked from the account menu, as D4 says (today it is only in the footer), and its known-gaps list gains the open items below. 6. **axe coverage.** axe runs on `/privacy`, `/accessibility` and `/licences` (the j0w loop loads them and never scans them), plus `/help`, the 404 and `/auth-error`. **Not in this branch.** Each needs a render check or the owner: - Map markers render at 16 px and are focusable (`Map.css:242`), and standalone `.linkish` buttons have no padding. Both need sizing to at least 24 px, and that changes layout. - The statement says the map's panning and zooming need a pointer. Leaflet's keyboard is on (`MapCanvas.jsx:102`), so that line may be stale; it needs checking in a browser before rewording. - Many routes have no axe scan: the campaign settings sub-pages, wiki map, history, edit and new, and the admin sub-pages. That list is the next slice. - A shared persistent announcer for conditionally mounted `Busy` and `Banner`: whether it is needed is the manual pass's call. - A skip link on public pages: low value, and it changes layout.
Author
Collaborator

First slice merged: PR #1049, 2026-10-03. CI was green twice: on its own base, and again after merging main, since main had changed AppFrame.jsx in the meantime. All six items from comment 51371 are in:

  • the transcript state is one persistent role=status
  • public pages move focus to the new heading on navigation
  • the phone Contents sheet is a modal dialog that holds Tab
  • Modal falls back to the page heading when its opener is gone, and skips disabled controls
  • the statement is in the account menu and lists two more known gaps
  • axe scans the policy pages, /help, /auth-error and the 404

Not on dev yet: it lands on dev with the next prerelease. To look at there: the account-menu row, and the focus outline after following a public link.

Second slice under way: axe scans for the routes no journey reaches (campaign settings and prep, wiki map, history, edit, new and the rest, the admin sub-pages, the session edit page, /profile/plan), with fixes for what they find.

Still the owner's: the manual screen-reader pass, the real-device check, and anything whose fix changes layout. That includes the map markers and the standalone .linkish buttons.

**First slice merged:** PR #1049, 2026-10-03. CI was green twice: on its own base, and again after merging main, since main had changed `AppFrame.jsx` in the meantime. All six items from comment 51371 are in: - the transcript state is one persistent `role=status` - public pages move focus to the new heading on navigation - the phone Contents sheet is a modal dialog that holds Tab - `Modal` falls back to the page heading when its opener is gone, and skips disabled controls - the statement is in the account menu and lists two more known gaps - axe scans the policy pages, `/help`, `/auth-error` and the 404 **Not on dev yet:** it lands on dev with the next prerelease. To look at there: the account-menu row, and the focus outline after following a public link. **Second slice under way:** axe scans for the routes no journey reaches (campaign settings and prep, wiki map, history, edit, new and the rest, the admin sub-pages, the session edit page, `/profile/plan`), with fixes for what they find. **Still the owner's:** the manual screen-reader pass, the real-device check, and anything whose fix changes layout. That includes the map markers and the standalone `.linkish` buttons.
Author
Collaborator

Second slice merged: PR #1050, 2026-10-03, CI green on a head carrying main.

Every route in App.jsx that no journey scanned now gets an axe scan. That is 34 campaign, wiki, session, profile and admin pages, in three read-only specs (a11y-campaign-pages, a11y-wiki-pages, a11y-admin-pages), plus the public pages from #1049.

The scans found four real violations. All are fixed, and no rule is disabled:

Rule Page(s) Fix
link-in-text-block About this server, Settings → Ownership, wiki Rules In-sentence links told apart by colour alone. They take a new .textlink style: underlined, --accent-ink.
scrollable-region-focusable Admin → Usage, phone Its three tables become focusable, labelled regions.

Left, for the owner or later:

  • A global underline for every in-sentence link. It would change many pages, so it is the owner's call.
  • Latent .tscroll gaps in AdminHealth, AdminNotices and AdminUsers, if their content overflows. They pass with the seeded data.
  • Scans see seeded states only, so states the seed never reaches are unscanned.
  • From comment 51475: the manual screen-reader pass, the real-device check, and the fixes that change layout (map markers, standalone .linkish buttons).
**Second slice merged:** PR #1050, 2026-10-03, CI green on a head carrying main. Every route in `App.jsx` that no journey scanned now gets an axe scan. That is 34 campaign, wiki, session, profile and admin pages, in three read-only specs (`a11y-campaign-pages`, `a11y-wiki-pages`, `a11y-admin-pages`), plus the public pages from #1049. The scans found four real violations. All are fixed, and no rule is disabled: | Rule | Page(s) | Fix | |---|---|---| | `link-in-text-block` | About this server, Settings → Ownership, wiki Rules | In-sentence links told apart by colour alone. They take a new `.textlink` style: underlined, `--accent-ink`. | | `scrollable-region-focusable` | Admin → Usage, phone | Its three tables become focusable, labelled regions. | **Left, for the owner or later:** - **A global underline for every in-sentence link.** It would change many pages, so it is the owner's call. - **Latent `.tscroll` gaps** in `AdminHealth`, `AdminNotices` and `AdminUsers`, if their content overflows. They pass with the seeded data. - **Scans see seeded states only**, so states the seed never reaches are unscanned. - **From comment 51475:** the manual screen-reader pass, the real-device check, and the fixes that change layout (map markers, standalone `.linkish` buttons).
rbrooks referenced this issue from a commit 2026-10-03 17:39:50 +00:00
Author
Collaborator

On dev: v4.5.0-rc2 (2026-10-03) carries #1049, #1050 and #1051. #1051 gave the last four admin tables (Health, Notices, Backup, a person's campaigns) the same keyboard-scrollable region the axe pass required on Usage.

Dev was deployed from the published images: every service healthy, /api/meta returns 4.5.0-rc2, and no tracebacks since.

To look at on dev:

  • the Accessibility row in the account menu, desktop and phone
  • the underlined links on About this server, Settings → Ownership and the wiki's Rules page
  • the focus outline after following a link between public pages

Still the owner's: the screen-reader pass, the real-device check, and the layout-changing fixes listed above.

**On dev:** `v4.5.0-rc2` (2026-10-03) carries #1049, #1050 and #1051. #1051 gave the last four admin tables (Health, Notices, Backup, a person's campaigns) the same keyboard-scrollable region the axe pass required on Usage. Dev was deployed from the published images: every service healthy, `/api/meta` returns `4.5.0-rc2`, and no tracebacks since. To look at on dev: - the Accessibility row in the account menu, desktop and phone - the underlined links on About this server, Settings → Ownership and the wiki's Rules page - the focus outline after following a link between public pages Still the owner's: the screen-reader pass, the real-device check, and the layout-changing fixes listed above.
Author
Collaborator

Owner's answers, 2026-10-04 (Questions page, both as recommended):

  • Q149 (A12): (a). Underline every link in running text, everywhere: one rule in the design system, not one fix per scan finding. Links in menus, lists, buttons and navigation are not affected. To be recorded as D277.
  • Q150 (A13): (a). Enlarge the pressable area of map markers and of the stand-alone link-style buttons, keeping the look as close as possible. The marker's dot stays the size it is drawn. To be recorded as D278.

Both are being built now. With them, what is left on this issue is the manual screen-reader pass and the real-device check.

**Owner's answers, 2026-10-04** (Questions page, both as recommended): - **Q149 (A12): (a).** Underline every link in running text, everywhere: one rule in the design system, not one fix per scan finding. Links in menus, lists, buttons and navigation are not affected. To be recorded as D277. - **Q150 (A13): (a).** Enlarge the pressable area of map markers and of the stand-alone link-style buttons, keeping the look as close as possible. The marker's dot stays the size it is drawn. To be recorded as D278. Both are being built now. With them, what is left on this issue is the manual screen-reader pass and the real-device check.
Author
Collaborator

D277 and D278 built: PR #1071, merged to main. CI was green on the PR's head.

  • Links in running text are underlined by one rule in src/ui/base.css; menus, lists, navigation, cards and buttons are left alone.
  • Stand-alone text buttons ("Show more", "Mark all read") have a 24 px target, 44 px on touch.
  • Map markers, pointer: a 24×24 px pressable area (44×44 on touch); where two overlap, the press goes to the nearer one.
  • Map markers, keyboard: Enter and Space open a focused marker. Before this, a marker took focus and Enter did nothing.
  • The accessibility statement is corrected in two lines:
    • "Map markers" names the gap that remains.
    • "The map" no longer says panning and zooming need a pointer; the arrow keys and + and − work while the map itself has focus.

Left open on this issue

  • Two markers drawn closer than 24 px still fail axe's target-size rule. The statement names it.
  • The trail stops and the "party is here" marker are on the canvas but not in the "On this map" list.
  • The manual passes with a screen reader and on a real device are still to do, and need the owner.
  • The new wording of "The map" line has not been seen by the owner.
**D277 and D278 built: PR #1071, merged to main. CI was green on the PR's head.** - **Links in running text** are underlined by one rule in `src/ui/base.css`; menus, lists, navigation, cards and buttons are left alone. - **Stand-alone text buttons** ("Show more", "Mark all read") have a 24 px target, 44 px on touch. - **Map markers, pointer:** a 24×24 px pressable area (44×44 on touch); where two overlap, the press goes to the nearer one. - **Map markers, keyboard:** Enter and Space open a focused marker. Before this, a marker took focus and Enter did nothing. - **The accessibility statement** is corrected in two lines: - "Map markers" names the gap that remains. - "The map" no longer says panning and zooming need a pointer; the arrow keys and + and − work while the map itself has focus. **Left open on this issue** - Two markers drawn closer than 24 px still fail axe's `target-size` rule. The statement names it. - The trail stops and the "party is here" marker are on the canvas but not in the "On this map" list. - The manual passes with a screen reader and on a real device are still to do, and need the owner. - The new wording of "The map" line has not been seen by the owner.
Author
Collaborator

D287 (the owner's answer to Q159, 2026-10-05), built in PR #1097 and merged. CI was green on the PR's head.

The accessibility statement no longer lists "The map" as a known gap: the owner judged it no longer describes a gap worth listing. Four gaps remain, "Map markers" among them.

Left on this issue, and needing a person: a pass with a screen reader, and a real phone at 375 px.

Reaches dev with the next prerelease.

**D287 (the owner's answer to Q159, 2026-10-05), built in PR #1097 and merged. CI was green on the PR's head.** The accessibility statement no longer lists "The map" as a known gap: the owner judged it no longer describes a gap worth listing. Four gaps remain, "Map markers" among them. **Left on this issue, and needing a person:** a pass with a screen reader, and a real phone at 375 px. Reaches dev with the next prerelease.
Author
Collaborator

Moved to v4.7.0 (owner, 17 Oct 2026, closing the v4.5.0 milestone). Built and on prod: links, pressable areas and map markers (#1071), and the accessibility statement as you approved it (Q159). Still needs a person: a screen-reader pass, and a real phone at 375 px. v4.7.0 is the review pass before others run Quest Board, which is where this belongs.

Moved to v4.7.0 (owner, 17 Oct 2026, closing the v4.5.0 milestone). Built and on prod: links, pressable areas and map markers (#1071), and the accessibility statement as you approved it (Q159). Still needs a person: a screen-reader pass, and a real phone at 375 px. v4.7.0 is the review pass before others run Quest Board, which is where this belongs.
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/Quest-Board#367
No description provided.