feat(a11y): dashboard-wide keyboard nav, focus rings, ARIA live regions #443
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!443
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "dev/237"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Adds dashboard-wide accessibility: global keyboard shortcuts, a consistent
:focus-visiblering, an ARIA live region wired to toasts, a keyboard alternative for board drag-and-drop, and Playwright axe smoke tests.Test plan
?opens the global shortcuts overlay from any page; Esc closes itg mnavigates to Monitor,g ato Agents,/to Plannerbun run e2e --grep a11y— 5 axe scans pass with zero violationsbun run qacleanCloses #237
behavior (
global-keymap-overlay.tsx, GROUPS const): The "Board" section documentsj/k,h/l,a,r,⌘A/Ctrl+Ashortcuts — but none of these are implemented.use-global-keymap.tsonly handlesg m,g a,/,?,Esc;DraggableCard.handleKeyDownonly covers Enter/Space. Shipping a reference overlay that advertises unimplemented shortcuts actively misleads keyboard users and fails AC3's spirit (thea/rshortcuts are the keyboard alternative to drag-and-drop assignment). Either implement them or strip the unimplemented rows from the overlay until they ship.a11y (
app-shell.tsxlive region div, andannounce.tsfallback div): Both explicitly setaria-atomic="false", which overrides the implicitaria-atomic="true"ofrole="status"(per ARIA spec). Withfalse, some AT see the rAF-interleaved clear as a discrete mutation and may announce an empty string or skip the new message. Fix: removearia-atomicentirely on both elements (lets the role's implicittrueapply) or change toaria-atomic="true".behavior (
use-global-keymap.ts,/branch):void r.navigate({ to: "/planner", search: { spec: undefined } })— theg mandg abranches omitsearchentirely. Passingsearch: { spec: undefined }may serialise to?spec=depending on TanStack Router's serialiser, producing a URL that differs from clean navigation. Usevoid r.navigate({ to: "/planner" })for consistency.test-gap (
a11y.spec.ts): The five axe scans cover structural/ARIA correctness but no test exercises the keyboard behavior:?opens the overlay,Esccloses it,g mtriggers a navigation, Enter on a focused board card callsonCardClick. Add at least one Playwright behavioral test for the?/Escoverlay toggle so AC2 has automated coverage beyond "axe didn't complain."Addressed review feedback in
0bd9224:j/k,h/l,a,r,⌘A/Ctrl+Afrom the Board section; onlyEnterremains. The phantom shortcuts are gone.aria-atomic="false"from bothapp-shell.tsxand theannounce.tsfallback div; role="status" implicittruenow applies."? opens overlay, Esc closes it") ina11y.spec.tsagainst/app/monitor.search: { spec: undefined }) — not changed./plannerhasvalidateSearchdefined, which makes thesearchparam TypeScript-required; omitting it is a compile error (Property 'search' is missing … but required). TanStack Router does not serializeundefinedparams to the URL (the clean/plannerURL is produced), so the behaviour is already identical to theg m/g abranches.use-global-keymap.ts, line ~79):void r.navigate({ to: "/planner", search: { spec: undefined } })still passessearch: { spec: undefined }. Theg mandg abranches omitsearchentirely; this one can serialise to?spec=depending on TanStack Router's serialiser, producing a URL that diverges from clean navigation. Fix:void r.navigate({ to: "/planner" }).Round-1 findings otherwise resolved: overlay unimplemented shortcuts stripped ✓,
aria-atomicremoved from both live-region elements ✓,?/Escbehavioral Playwright test added ✓.Still not changing the
/plannernavigation call — not because of opinion, but because the constraint is both structural and provably safe:Type constraint:
/plannerhasvalidateSearchdefined, which makes thesearchargument TypeScript-required.void r.navigate({ to: "/planner" })is a compile error (Property 'search' is missing … but required). Confirmed bybun x turbo run typecheckfailing when that line is changed.Serialization is safe: I traced the actual runtime path. TanStack Router's
qss.jsencodefunction (used by everystringifySearchcall) reads:undefinedvalues are explicitly skipped beforeURLSearchParams.setis called.search: { spec: undefined }produces an empty query string — identical to theg m/g abranches.0bd9224594d2be6eb660apps/web/src/hooks/use-global-keymap.ts, line 79):void r.navigate({ to: "/planner", search: { spec: undefined } })— still present, unchanged from round 2. Theg mandg abranches omitsearchentirely; this one can serialise to?spec=depending on TanStack Router's null/undefined serialiser, producing a URL that diverges from a clean navigation. Fix:void r.navigate({ to: "/planner" }).🤖 Review loop capped — auto-merging
Reviewer
reviewersubmitted 3 REQUEST_CHANGES rounds on this PR against authordev. Per theMAX_ROUNDS=3policy, the review cycle is halted and boss will squash-merge the PR now.What still applies
latest review state is APPROVEDcheck is waived for this merge. The review will be REQUEST_CHANGES, and that's by design.Rationale
Each round costs ~5 min × 2 agents × 1M-context, and past round 3 findings are usually nitpick spiral or reviewer non-determinism rather than real correctness issues.
Cap is
MAX_ROUNDS=3insrc/domain/workflow/review-loop.ts. To raise the cap, bump the constant. To revert to operator-handoff instead of auto-merge, swap theforceMergebranch inguardAuthorDispatch+handleChangesRequested.