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>
4.9 KiB
4.9 KiB
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.tshas onlyprovideRouter(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-eslintis entirely absent. - User decision: a manual WCAG checklist documents what automation can't test.
Read first
src/app/app.config.tseslint.config.mjs,package.jsonsrc/app/shared/layout/shell/shell.component.ts(#maintarget),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'sh1(or#mainfallback,tabindex="-1"), deferred viaafterNextRenderso it coexists with view transitions; skip the initial load. Implement as a small root service/effect wired inapp.config.ts— not per page. - Lint:
angular-eslintat the version matching Angular 22 (~v21+ — checknpm view angular-eslint peerDependencies), withprocessInlineTemplates(this repo has no.htmltemplates). 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 smallshared/focus-on-navigation serviceeslint.config.mjs,package.json(angular-eslint)- Any template the new lint rules flag
- New
docs/reference/wcag-checklist.md - New
src/docs/a11y.mdx— titleFoundations/Accessibility
Steps
- Router scroll + focus service; manual keyboard smoke (tab through, navigate, confirm focus lands on the new page's heading).
- Add angular-eslint; enable the template rules on inline templates; plant a bad pattern, see it fail, remove it; fix real hits.
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).src/docs/a11y.mdx: the layered approach — axe gate (WP-01) + template lint + play tests (WP-16) + manual checklist; the alert-role rationale; where thea11y.disableskip register lives and its rules.
Acceptance criteria
- Navigating between routes moves focus to the new page's heading; scroll resets; view transitions still play.
- Template a11y rules active and proven to fire; lint green.
- Checklist checked in with an initial pass filled in for the dashboard at minimum.
a11y.mdxrenders; links to checklist and WP-01 skip rules.
Deviation from the original plan
- Used the official
angular.configs.templateAccessibilitybundle (11 rules) instead of hand-listing the 6 named in this WP's Decisions — it's a strict superset (includesno-autofocus,no-distracting-elements,mouse-events-have-key-events,role-has-required-aria,table-scopeon 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 warningapp-alert(twoapp-buttonactions) overflows the viewport at 320px — its.feedbackflex row doesn't wrap. Documented indocs/reference/wcag-checklist.mdwith 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
withViewTransitionstiming →afterNextRender+ test in a real browser (npm start), not just unit-level.