Skip to content

Latest commit

 

History

History
156 lines (132 loc) · 9.76 KB

File metadata and controls

156 lines (132 loc) · 9.76 KB

Manual accessibility walkthrough

The automated gates (web/a11y.py in CI, blocking pa11y/axe) check structure and computed contrast. They cannot check the lived experience: whether the page is actually operable and comprehensible with a screen reader and a keyboard. This file is the record of that human step. Until the result table below is filled in by a person, the demo is accessibility-reviewed by automation only, and the README and model card say so.

How to run it

Test the deployed demo (or web/index.html served locally) with at least one screen reader and keyboard-only, at default zoom and at 400%. Record the date, the tool and version, and the outcome of each item. File a follow-up issue for anything that fails and link it here.

Recommended coverage: NVDA + Firefox (Windows) and VoiceOver + Safari (macOS or iOS). One desktop and one mobile pass is the minimum for a phone-first civic audience.

Four pages are public, not one. The checklist below is written against the chat page, but /embed, /offline, and /guide are equally reachable, and the last two exist for riders with no signal at the stop or who would rather browse than type — the audience least able to route around a problem. At minimum, walk /offline and /guide for heading navigation and reading order (they are long documents, so heading structure is the whole navigation story) and /embed for keyboard operability inside an iframe, which is the one context the standalone page never exercises. As of 2026-08-05 all four are covered by the structural gate (python -m web.a11y), and as of 2026-08-12 all four are also covered by the merge-blocking browser gate (axe + HTML CodeSniffer at WCAG2AA), which until then had only ever loaded the chat page. All four pass both runners. None of them has had a human pass.

Checklist

Each item is pass / fail / not-tested with a note. Items marked (auto) are also covered by the static gate; they are listed so the manual pass confirms the automation did not miss the real behavior.

Keyboard

  • Every control (text box, Ask, examples, text-size A/A+/A++, high contrast, Yes/No feedback, Start over) is reachable and operable by Tab and Enter.
  • Focus order is logical and visible (the focus outline is the 4px ring with a 3px offset).
  • No keyboard trap anywhere in the form or transcript.
  • After submitting a question, focus moves to the new answer turn so a keyboard user lands on the response (the page sets tabindex=-1 and calls focus() on each turn; confirm it actually lands).

Screen reader

  • The page title and the single h1 are announced; heading order (h1 then the card h2s) reads sensibly. (auto: structure)
  • The "What it will not do" list is announced before the input, so the limits are heard, not skipped.
  • The status line ("Looking through the published policies…", errors) is announced through the polite live region without stealing focus. (auto: role=status, aria-live=polite present)
  • A new answer is announced or is reachable immediately after it arrives.
  • Citations read as usable source references, not a wall of brackets: each "AGENCY: Title (fetched DATE)" link is announced with its agency and title. (this is the R1a-3 item the static gate cannot verify)
  • The "Based on policies published as of DATE" line and the "Fetched N days ago" staleness note are announced and understandable.
  • The feedback control announces its purpose ("Was this helpful?", Yes/No with aria-label).
  • Spanish answers carry lang="es" so the screen reader switches voice.

Low vision / zoom / contrast

  • Text-size controls (A / A+ / A++) visibly scale the page and persist on reload; the pressed state is announced (aria-pressed).
  • High-contrast toggle increases contrast and persists; pressed state announced.
  • The page reflows without horizontal scrolling at 400% zoom (1.4.10). (auto: zoom not disabled)
  • Contrast meets AA in both the default and high-contrast themes. (auto: blocking axe/pa11y)
  • Target sizes are comfortable on a phone. (auto: 24px minimum in CSS)

Code-level pre-audit — 2026-07-11 (by source inspection, not a screen reader)

This section is a head start for the human pass, not a substitute for it. It records what could be confirmed by reading web/index.html and running the static gate (python -m web.a11y, green on this commit). Every row a real screen reader must judge — whether an announcement is actually heard and makes sense — is left explicitly for the human pass. Do not promote any "needs human" row to pass without an actual screen-reader session.

Keyboard

  • Verified by code: every control is a native <button>, <textarea>, or <a> — focusable and Enter/Space-operable without extra ARIA. Focus order follows DOM order, which matches the visual order (banner → h1 → display settings → "will not do" → ask form → examples → status → transcript). The focus ring is outline: 4px solid #1d4ed8 with outline-offset: 3px via :focus-visible. (This note read "3px" until 2026-08-05. The page had since moved to a thicker ring with an offset, which is the stronger result for 2.4.13 Focus Appearance — but a pre-audit that describes code the repo no longer ships is worth nothing to the person doing the human pass, so every claim in this section was re-verified against HEAD on 2026-08-05 and this one was the only drift found.) No script installs a focus trap. On each new answer the code sets tabindex=-1 on the turn and calls focus(), so a keyboard user is moved onto the response.
  • Needs human pass: confirm the focus visibly lands on the new turn and that the ring is perceivable at 400% and in high-contrast mode.

Screen reader

  • Verified by code (structure): one <h1>, card <h2>s, structured-answer <h3>s — no skipped levels. The "What it will not do" <section> precedes the ask form in the DOM. Status is role=status aria-live=polite. Answer turns carry lang (ans.setAttribute("lang", data.language)) so a Spanish answer can switch voice. Feedback buttons have aria-label ("Yes, helpful" / "No, not helpful") beside a "Was this helpful?" label. Citations render as <a>AGENCY: Title</a> (fetched DATE) inside a <ul>.
  • Minor finding (not a failure): the "Sources" caption is a <strong>, not a heading, so it is not a screen-reader heading-nav target. Fixed 2026-08-05. It is now an <h3> on web/index.html (under the "Conversation" <h2>) and an <h2> on /embed (whose only other heading is its <h1>, so an <h3> would skip a level), styled to keep the inline bold caption's appearance. Where an answer came from is exactly what a screen-reader user goes looking for, and heading navigation is how they look; the recorded transcript the independent audit grades already used <h3>Sources</h3>, so the page riders actually use was the less accessible of the two. The human pass should still confirm the sources are easy to find by heading navigation, which is the part only listening can settle.
  • Needs human pass: whether the moved focus actually announces the new answer; whether the citation list reads as usable references rather than a bracket wall; whether the polite live region announces status without stealing focus; and whether the Spanish lang actually flips the voice.

Low vision / zoom / contrast

  • Verified by code: the page is rem/em-based with html.tsize-large (112.5%) and html.tsize-xlarge (125%), toggled with aria-pressed and persisted in localStorage; high contrast toggles body.contrast (which only deepens colours), also aria-pressed and persisted. Viewport allows zoom (no user-scalable=no). Target sizes: primary controls min-height: 2.5rem (40px), secondary 1.75rem (28px) — both ≥ the 24px 2.5.8 minimum. Static contrast is covered by the blocking axe/pa11y gate.
  • Needs human pass: reflow with no horizontal scroll at 400%, and a subjective contrast/readability check in both themes on a real device.

Result log

Date Tool + version Platform Pass / fail summary Follow-ups
2026-07-11 source inspection + web.a11y static gate n/a (code review) Structure, keyboard wiring, ARIA, lang, target sizes, persistence all correct by inspection; static gate green. Screen-reader listening items not yet performed. Human SR pass still required (rows above)
2026-08-05 source inspection + widened web.a11y static gate n/a (code review) Re-verified the 2026-07-11 claims against HEAD: one drift (focus ring, corrected above). Fixed the open "Sources is a <strong>" finding on both answering surfaces. Widened the gate from web/index.html to all four public pages; /embed, /offline, and /guide were previously unchecked and all three pass. Still no screen reader was used — every listening row is untouched. Human SR pass still required, now across four pages
2026-08-12 pa11y 9.1.1, runners axe + htmlcs (headless Chrome) n/a (automated, CI) Widened the browser gate from web/index.html to all four public pages and added HTML CodeSniffer alongside axe, so eight page/runner combinations now gate every PR. /embed, /offline, and /guide had never been loaded in a browser by any check. All four pass both runners with zero violations — no defects to fix, so the value here is regression protection, not remediation. Still no screen reader was used; every listening row is untouched. Human SR pass still required across four pages
pending NVDA/Firefox or VoiceOver/Safari desktop + mobile not yet performed

Until a row here records a real screen-reader pass, do not present the demo as production-ready for accessibility.