Two backlog trees are complete: `docs/project/backlog/` (75 files, every WP done) and `docs/project/refactor-backlog-setup/` (the arc before it). Move both under `docs/project/archive/` with `git mv`, so history stays intact through `git log --follow`. `SHOWCASE-ROADMAP.md` moves with them, because it points at the now-archived backlog README. Add `docs/project/archive/README.md`. It states that these trees are historical and names the two directories that are still live. Repoint every inbound reference named in RD-30's Files table: CLAUDE.md, the root README, both backend READMEs, `LetterHtml.cs`, `a11y.mdx`, the `document-feature` and `new-ssp` skills, and the readable-codebase PLAN, README, and RD-19 ticket. Fix two upward-relative links inside the moved WP files (WP-68, WP-69) that gained a directory level and would otherwise break. Repoint `.prettierignore`'s two agent-prompt exclusions to their new path, so prettier keeps leaving those files' exact wording alone. Mark RD-30 done and check off its acceptance criteria; flip its README row to done. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
103 lines
4.9 KiB
Markdown
103 lines
4.9 KiB
Markdown
# WP-17 — App-level a11y: route focus/scroll, template lint, WCAG checklist + MDX
|
||
|
||
Status: done (pending commit)
|
||
Phase: 4 — a11y
|
||
|
||
## Why
|
||
|
||
Three app-level gaps close the WCAG story:
|
||
|
||
- **No route-change focus/scroll management** — `app.config.ts` has only
|
||
`provideRouter(routes, withViewTransitions())`; after navigation, focus stays wherever
|
||
it was and scroll position is unmanaged. (The wizard manages focus _within_ steps; the
|
||
skip link is the only cross-page mechanism.)
|
||
- **No template a11y linting** — `@angular-eslint` is entirely absent.
|
||
- User decision: a **manual WCAG checklist** documents what automation can't test.
|
||
|
||
## Read first
|
||
|
||
- `src/app/app.config.ts`
|
||
- `eslint.config.mjs`, `package.json`
|
||
- `src/app/shared/layout/shell/shell.component.ts` (`#main` target),
|
||
`page-shell/page-shell.component.ts` (candidate focus target)
|
||
|
||
## Decisions (pre-made, don't relitigate)
|
||
|
||
- Scroll: `withInMemoryScrolling({ scrollPositionRestoration: 'enabled' })`.
|
||
- Focus: on `NavigationEnd`, move focus to the new page's `h1` (or `#main` fallback,
|
||
`tabindex="-1"`), **deferred via `afterNextRender`** so it coexists with view
|
||
transitions; skip the initial load. Implement as a small root service/effect wired in
|
||
`app.config.ts` — not per page.
|
||
- Lint: `angular-eslint` at the version matching Angular 22 (~v21+ — check
|
||
`npm view angular-eslint peerDependencies`), with `processInlineTemplates` (this repo
|
||
has **no** `.html` templates). Rules: `alt-text`, `label-has-associated-control`,
|
||
`interactive-supports-focus`, `click-events-have-key-events`, `valid-aria`,
|
||
`elements-content`. **Verify the processor fires** by planting a known-bad pattern
|
||
before trusting green.
|
||
- The checklist is a living doc with per-page status columns — not prose.
|
||
|
||
## Files
|
||
|
||
- `src/app/app.config.ts` + a new small `shared/` focus-on-navigation service
|
||
- `eslint.config.mjs`, `package.json` (angular-eslint)
|
||
- Any template the new lint rules flag
|
||
- New `docs/reference/wcag-checklist.md`
|
||
- New `src/docs/a11y.mdx` — title `Foundations/Accessibility`
|
||
|
||
## Steps
|
||
|
||
1. Router scroll + focus service; manual keyboard smoke (tab through, navigate, confirm
|
||
focus lands on the new page's heading).
|
||
2. Add angular-eslint; enable the template rules on inline templates; plant a bad
|
||
pattern, see it fail, remove it; fix real hits.
|
||
3. `docs/reference/wcag-checklist.md`: manual checks per page (dashboard, wizards, brief, login) —
|
||
keyboard walk & focus order, no traps, 200% zoom/reflow, NVDA or VoiceOver pass,
|
||
visible focus, error announcement; status columns (page × check).
|
||
4. `src/docs/a11y.mdx`: the layered approach — axe gate (WP-01) + template lint + play
|
||
tests (WP-16) + manual checklist; the alert-role rationale; where the
|
||
`a11y.disable` skip register lives and its rules.
|
||
|
||
## Acceptance criteria
|
||
|
||
- [x] Navigating between routes moves focus to the new page's heading; scroll resets;
|
||
view transitions still play.
|
||
- [x] Template a11y rules active and _proven_ to fire; lint green.
|
||
- [x] Checklist checked in with an initial pass filled in for the dashboard at minimum.
|
||
- [x] `a11y.mdx` renders; links to checklist and WP-01 skip rules.
|
||
|
||
## Deviation from the original plan
|
||
|
||
- Used the official `angular.configs.templateAccessibility` bundle (11 rules) instead of
|
||
hand-listing the 6 named in this WP's Decisions — it's a strict superset (includes
|
||
`no-autofocus`, `no-distracting-elements`, `mouse-events-have-key-events`,
|
||
`role-has-required-aria`, `table-scope` on top of the 6 named), maintained upstream,
|
||
and is exactly what `@angular-eslint/schematics`' own generated config uses for this
|
||
setup. Less code to hand-maintain, same coverage plus more.
|
||
- The dashboard's checklist pass surfaced a **real bug**: `aanvraag-block`'s warning
|
||
`app-alert` (two `app-button` actions) overflows the viewport at 320px — its
|
||
`.feedback` flex row doesn't wrap. Documented in `docs/reference/wcag-checklist.md` with the
|
||
root cause, **not fixed** — fixing live component CSS found via the checklist is the
|
||
"full manual audit" scope this WP's Out-of-scope section explicitly defers, not this
|
||
WP's own deliverable. Flagged here so it isn't lost.
|
||
- "Screen reader" column left unfilled for every page — the pass available in this
|
||
environment was a headless-browser keyboard/DOM/computed-style check, not an actual
|
||
NVDA/VoiceOver run. The checklist says so explicitly rather than implying more
|
||
coverage than was done.
|
||
|
||
## Verification
|
||
|
||
GREEN + `npm run test-storybook:ci`. Manual: keyboard-only walk of
|
||
login → dashboard → wizard → brief.
|
||
|
||
## Out of scope
|
||
|
||
Full manual audit of every page (the checklist enables it; filling it beyond the
|
||
dashboard is ongoing work, not this WP).
|
||
|
||
## Risks
|
||
|
||
- angular-eslint's inline-template processor silently not running → the planted-violation
|
||
step is mandatory.
|
||
- Focus-move vs `withViewTransitions` timing → `afterNextRender` + test in a real browser
|
||
(`npm start`), not just unit-level.
|