feat(app): announce scan phase changes to screen readers politely - #176
Muawiya-contact merged 2 commits into
Conversation
The scan panel re-renders its file and byte counters on every progress event (~150ms), so any live region wrapping them would chatter constantly. The phase label only changes a handful of times per scan — walking files, impact graph, classifying, duplicates, findings — so a visually-hidden aria-live=polite region carrying just that text announces each transition and stays quiet between them. Cancelling also swaps the announcement to the stopping copy so the state change is heard, matching the visible status text. Adds the shared .sr-only clip helper — keeps content in the accessibility tree but off screen — and string-match tests asserting the live region exists, carries the phase rather than the counters, and announces the cancelling state.
|
@LouisDeconinck have to read the discussion? I leave a message for you. |
Muawiya-contact
left a comment
There was a problem hiding this comment.
Thanks for contributing again, Louis! Keeping counters out of the announcements is a good improvement, and the focused tests and current CI pass. Please fix the live-region lifecycle described inline so the first scan phase is announced reliably, with a check for the initial scan and preview transition.
| once more when a cancel registers — so a live region | ||
| carrying just it announces each transition while the | ||
| 150ms file/byte ticks above stay silent. */} | ||
| <p className="sr-only" aria-live="polite" aria-atomic="true"> |
There was a problem hiding this comment.
[P2] Mount an empty live region before adding phase text. App.jsx mounts ScanningIndicator only after scanning starts, so this region appears already containing "Walking files". Polite regions generally announce subsequent content changes, not initial populated insertion (https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Guides/Live_regions); users can therefore miss the start and long walking phase. The first preview also replaces this component via the conditional report branches. Please keep one initially empty live region mounted outside those branches and update its text when scanning/phase changes, while continuing to exclude counters.
There was a problem hiding this comment.
Good catch — thank you! Fixed in 94914e0: the region moved out of ScanningIndicator into App.jsx, where one initially-empty aria-live="polite" region mounts inside <main> ahead of the !displayedReport / displayedReport branches. It now tracks liveProgress.phase plus the cancelling state, so the first phase announces as a content change and the region survives the preview swap — still no counters in it.
The region inside ScanningIndicator mounted already populated, and polite regions announce content changes rather than initial content — and the first preview swaps the whole panel between the report branches, so the first phase (and the long walking phase) could pass unannounced. The region now lives in App.jsx, mounted empty inside <main> ahead of the conditional branches, and follows liveProgress.phase plus the cancelling state; the 150ms counters stay out of it.
|
Fixed in The live region moved out of Tests updated to match the new ownership: |
Muawiya-contact
left a comment
There was a problem hiding this comment.
Thanks, Louis—the persistent, initially empty live region now addresses the start-of-scan and preview lifecycle concern while keeping counter updates silent. I rechecked the code, all 12 app test files passed locally, and current CI is green. Approved!
What & why
Closes #156.
The scan panel re-renders its file and byte counters on every
scan-progressevent (~150ms). Any live region wrapping that output would chatter constantly, so screen reader users currently get either nothing or noise.This adds a visually-hidden
aria-live="polite"region toScanningIndicatorcarrying only the phase text ({phase || "Walking files"}). Because its text node only changes on a phase transition — walking files → impact graph → classifying → duplicates → findings — each stage is announced once and the count ticks stay silent.aria-atomic="true"makes the whole phrase announce rather than a diff.One deliberate addition beyond the issue's snippet: while
cancelling, the region reads the same "Stopping the scan… nothing has been changed" copy as the visible status line, so pressing Cancel announces the state change instead of going quiet until the report/cancelled notice appears.The new
.sr-onlyhelper is the classic 1px clip pattern — off screen, but still in the accessibility tree (display:nonewould silence it).Checklist
cargo fmt --allandcargo clippy --workspaceare clean — no Rust touchedcargo test --workspacepasses — no Rust touched;cd app && npm test(75 tests) andnpm run buildpasschangelog.d/—156-sr-announcements.added.mdScanningIndicator.jsxwhich no open PR modifies