Accessibility gaps: a finished transcript is announced, public pages take focus, the Contents sheet and Modal hold focus, the statement is in the account menu (#367) #1049

Merged
claude-bot merged 3 commits from fix/367-a11y-gaps into main 2026-10-03 16:09:29 +00:00
Collaborator

Part of #367 (the automatable part, plan in comment 51371). The manual screen-reader pass and the real-device check stay the owner's, so #367 stays open.

Summary

  • Transcript state. TranscriptState.jsx's wrapper is now one persistent role=status region, and the inner Busy is not a second one. A transcript that finishes or fails is announced; before, only Busy was live, and it unmounted at exactly that moment.
  • Public pages. PublicPage calls useRouteFocus the way AppFrame and TableFrame do, and <main> takes tabIndex=-1. Following a link between /, /help…, /privacy, /accessibility and the rest moves focus to the new page's heading. The hook sits in PublicPage, not PublicFrame, because the crash page renders PublicFrame outside the router.
  • Phone Contents sheet. It is a labelled role=dialog, aria-modal=true, and Tab wraps inside it (a document keydown, like its Escape). Before, Tab walked into the page behind the scrim.
  • Modal.
    • On close, if what opened it is gone (its row was deleted), focus goes to the page's h1, else <main>, not <body>.
    • First focus skips disabled controls.
  • Accessibility statement, D4.
    • An "Accessibility" row in the account menu, desktop and phone. It is added in AppFrame's accountItems, which is where the real menu is built, and in AccountMenu's defaults.
    • Two new known gaps: small map markers, and status changes not always announced. The existing map line is left until it is checked in a browser.
  • axe coverage. j0w now scans /privacy, /accessibility and /licences (it loaded them and never scanned them), plus /help, /auth-error and the 404.

Changelog

  • changelog.d/367-a11y-gaps.fixed.md; catalogue SH-4 and IS-197 name the new row.

Test plan

  • New unit tests: TranscriptState (one persistent status node), PublicPage (focus lands on the new h1), Contents (Tab wraps), Modal (fallback, disabled skipped), AccountMenu and AppFrame (the row), Accessibility (five gap rows).
  • On docker-test: eslint 0 errors (the 28 warnings were there before); vitest src/ui, src/shell, src/pages/session, Accessibility and StartCharacter: 1066 passed, 1 skipped.
  • The full vitest run was not completed locally. The first full run had 3 failures; one was StartCharacter under load and the other two were not identified. CI decides.
  • The journeys were not run locally. The new axe scans may find real violations on /auth-error or the 404.
  • Needs a look (render): the new account-menu row on desktop and in the phone sheet, and focus outlines after navigating between public pages. For the owner's review on dev.

🤖 Generated with Claude Code

Part of #367 (the automatable part, plan in comment 51371). The manual screen-reader pass and the real-device check stay the owner's, so #367 stays open. ## Summary - **Transcript state.** `TranscriptState.jsx`'s wrapper is now one persistent `role=status` region, and the inner `Busy` is not a second one. A transcript that finishes or fails is announced; before, only `Busy` was live, and it unmounted at exactly that moment. - **Public pages.** `PublicPage` calls `useRouteFocus` the way `AppFrame` and `TableFrame` do, and `<main>` takes `tabIndex=-1`. Following a link between `/`, `/help…`, `/privacy`, `/accessibility` and the rest moves focus to the new page's heading. The hook sits in `PublicPage`, not `PublicFrame`, because the crash page renders `PublicFrame` outside the router. - **Phone Contents sheet.** It is a labelled `role=dialog`, `aria-modal=true`, and Tab wraps inside it (a document keydown, like its Escape). Before, Tab walked into the page behind the scrim. - **`Modal`.** - On close, if what opened it is gone (its row was deleted), focus goes to the page's `h1`, else `<main>`, not `<body>`. - First focus skips disabled controls. - **Accessibility statement, D4.** - An "Accessibility" row in the account menu, desktop and phone. It is added in `AppFrame`'s `accountItems`, which is where the real menu is built, and in `AccountMenu`'s defaults. - Two new known gaps: small map markers, and status changes not always announced. The existing map line is left until it is checked in a browser. - **axe coverage.** `j0w` now scans `/privacy`, `/accessibility` and `/licences` (it loaded them and never scanned them), plus `/help`, `/auth-error` and the 404. ## Changelog - [x] `changelog.d/367-a11y-gaps.fixed.md`; catalogue SH-4 and IS-197 name the new row. ## Test plan - [x] New unit tests: TranscriptState (one persistent status node), PublicPage (focus lands on the new h1), Contents (Tab wraps), Modal (fallback, disabled skipped), AccountMenu and AppFrame (the row), Accessibility (five gap rows). - [x] On docker-test: eslint 0 errors (the 28 warnings were there before); vitest `src/ui`, `src/shell`, `src/pages/session`, Accessibility and StartCharacter: 1066 passed, 1 skipped. - [ ] **The full vitest run was not completed locally.** The first full run had 3 failures; one was StartCharacter under load and the other two were not identified. CI decides. - [ ] **The journeys were not run locally.** The new axe scans may find real violations on `/auth-error` or the 404. - [ ] **Needs a look (render):** the new account-menu row on desktop and in the phone sheet, and focus outlines after navigating between public pages. For the owner's review on dev. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ContentsSheet: Tab containment on the document, for the lint rule (#367)
All checks were successful
CI / Docker image build (pull_request) Successful in 30s
CI / Bot/backend version sync (pull_request) Successful in 34s
CI / Backend lint (ruff) (pull_request) Successful in 37s
CI / Does this change touch the recording path? (pull_request) Successful in 37s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Feedback site tests (pull_request) Successful in 53s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m6s
CI / Bot tests and audit (pull_request) Successful in 2m13s
CI / Frontend (shard 2/2) (pull_request) Successful in 8m24s
CI / Frontend (shard 1/2) (pull_request) Successful in 10m48s
CI / End-to-end journeys (phone) (pull_request) Successful in 10m30s
CI / End-to-end journeys (keyboard) (pull_request) Successful in 10m53s
CI / End-to-end journeys (desktop) (pull_request) Successful in 11m40s
CI / Backend migration, tests, and audit (pull_request) Successful in 24m40s
b8e5f68d84
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Merge remote-tracking branch 'origin/main' into fix/367-a11y-gaps
All checks were successful
CI / Does this change touch the recording path? (pull_request) Successful in 21s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Docker image build (pull_request) Successful in 24s
CI / Bot/backend version sync (pull_request) Successful in 1m2s
CI / Backend lint (ruff) (pull_request) Successful in 1m14s
CI / Feedback site tests (pull_request) Successful in 1m30s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m51s
CI / Bot tests and audit (pull_request) Successful in 3m0s
CI / Frontend (shard 2/2) (pull_request) Successful in 8m35s
CI / End-to-end journeys (phone) (pull_request) Successful in 8m9s
CI / Frontend (shard 1/2) (pull_request) Successful in 10m22s
CI / End-to-end journeys (desktop) (pull_request) Successful in 10m51s
CI / End-to-end journeys (keyboard) (pull_request) Successful in 10m55s
CI / Backend migration, tests, and audit (pull_request) Successful in 23m1s
edd2d12615
claude-bot deleted branch fix/367-a11y-gaps 2026-10-03 16:09:30 +00:00
rbrooks referenced this pull request from a commit 2026-10-03 17:39:50 +00:00
Sign in to join this conversation.
No description provided.