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>
Backlog — showcase hardening
Ordered work packages that take this POC from "good" to reference showcase: CIBG design-system fidelity, DDD/FP consistency, Storybook as curriculum, and WCAG compliance with automated gates. Source: the architecture/CIBG/a11y audit of 2026-07-02 (plan: "Showcase hardening").
This backlog supersedes docs/project/SHOWCASE-ROADMAP.md.
Session protocol
- One WP per session. Read
CLAUDE.md, this README, the WP file, and the WP's "Read first" list — then execute. Do not start the next WP in the same session. - Match the model to the step, not the whole session (see CLAUDE.md's "Model routing
for agent delegation"). Read the WP's Decisions block with the
planneragent (Opus) if the current session isn't already Opus — the approach and edge cases deserve the stronger model. Implement directly if already on Sonnet, or hand off to thedeveloperagent otherwise, once the plan is approved. Delegate simple, read-only CLI checks (running a test suite,grep/git status, verifying a file exists) to thetask-runneragent (Haiku) — proportion cost to the step's difficulty, not the WP's. - The Decisions block in each WP is pre-made — don't relitigate it.
- A WP ends GREEN (below) with its acceptance criteria checked off and its Status
updated to
done(+ commit hash). - No WP leaves a lint rule/check disabled without an inline justification comment and a cross-reference to the WP that will remove it.
GREEN (global definition of done)
npm run lint && npm run check:tokens && npm test && npm run build && npm run build-storybook
From WP-01 onward, additionally:
npm run test-storybook:ci
Phases 0–5 were frontend-only; phase 6 (Brief v2) touches backend/ — for those
WPs cd backend && dotnet test is part of GREEN, and any wire change ends with
npm run gen:api leaving no drift.
From WP-19 onward, npm run e2e is part of CI (its own job) but NOT part of the local
GREEN one-liner above — it needs the real backend + npm start already running (see
WP-19's own file), so it's a separate manual/CI step, not chained into the others.
Order
Gates land before the work they cover; each lint rule lands in the same WP as the fixes for its existing violations, so every WP ends green.
| WP | Title | Phase | Status |
|---|---|---|---|
| WP-01 | Axe-on-every-story CI gate | 0 · gates | done |
| WP-02 | Harden check:tokens + fix what it catches |
0 · gates | done |
| WP-03 | Boundaries I: contracts purity + ApiClient confinement | 0 · gates | done |
| WP-04 | Boundaries II: ui ↛ infrastructure + showcase sanction |
0 · gates | done |
| WP-05 | Parse-don't-validate closure + MDX | 1 · FP/DDD | done |
| WP-06 | Generic async template contexts — kill $any() |
1 · FP/DDD | done |
| WP-07 | Brief on the shared idioms + RemoteData MDX | 1 · FP/DDD | done |
| WP-08 | One store idiom + machine naming + TEA MDX | 1 · FP/DDD | done |
| WP-09 | Pure-logic closure: dates + missing command specs | 1 · FP/DDD | done |
| WP-10 | CIBG button fidelity | 2 · CIBG | done |
| WP-11 | CIBG markup fidelity: application-link + absent-class triage | 2 · CIBG | done |
| WP-12 | CIBG Datablock for application data | 2 · CIBG | done |
| WP-13 | CIBG-gap register + hygiene + MDX | 2 · CIBG | done |
| WP-14 | Storybook taxonomy reorg + Layers MDX | 3 · Storybook | done |
| WP-15 | Missing stories: shell + brief components | 3 · Storybook | done |
| WP-16 | Component a11y: description wiring + alert role | 4 · a11y | done |
| WP-17 | App-level a11y: route focus, template lint, WCAG checklist | 4 · a11y | done |
| WP-18 | ABAC capability spine (Principal + capabilities, phase P1) | 5 · productie-volwassenheid | done |
| WP-19 | Playwright e2e smoke | 5 · productie-volwassenheid | done |
| WP-20 | Second locale proof | 5 · productie-volwassenheid | done |
| WP-21 | Resilience seams (correlation-id, idempotency, retry) | 5 · productie-volwassenheid | done |
| WP-22 | Durable persistence (optional tier) | 5 · productie-volwassenheid | done |
| WP-23 | Org-template backend + admin role | 6 · Brief v2 | done |
| WP-24 | Letter canvas (edit on the letter) | 6 · Brief v2 | done |
| WP-25 | Server-rendered letter preview (HTML; PDF deferred) | 6 · Brief v2 | done |
| WP-26 | Admin org-template editor | 6 · Brief v2 | done |
| WP-27 | Brief UX layer (undo/redo, standaardbrief, diff) | 6 · Brief v2 | done |
| WP-28 | Brief v2 demo polish (scenarios, e2e, docs) | 6 · Brief v2 | done |
| WP-29 | Stamdata beheer editor (low-code, PR-emitting) | follow-on · ADR-0004 | done |
| WP-30 | CI performance follow-ups (node_modules cache, runner image, path filters) | follow-on · CI/infra | done |
| WP-31 | Shared store helpers (ActionState/SaveState, history, debounced-save, RemoteData) | 7 · refinements | done |
| WP-32 | Undo/redo in the stamdata editor (folded into WP-31 — no separate file) | 7 · refinements | done |
| WP-33 | In-app dev switchers (scenario + role) | 7 · refinements | done |
| WP-34 | Adres: phone field + BRP address read-only | 7 · refinements | done |
| WP-35 | One Concept per case type (server-enforced) | 7 · refinements | done |
| WP-36 | Admin cases page + admin delete | 7 · refinements | done |
| WP-37 | Dev-switcher reset fix (scenario/role URL param) | 8 · platform/DX/showcase | done |
| WP-38 | Dependency graph + declarative boundaries (visualize + enforce) | 8 · platform/DX/showcase | done |
| WP-39 | Showcase: linked code snippets + teaching animations | 8 · platform/DX/showcase | done |
| WP-40 | PII kernel: branded Bsn VO (elfproef) + masked-value atom |
8 · platform/DX/showcase | done |
| WP-41 | Persisted, queryable authz/PII-reveal audit (no PII) | 8 · platform/DX/showcase | done |
| WP-42 | Privacy & security showcase page (mask + no-PII log) | 8 · platform/DX/showcase | done |
| WP-43 | Runnable generators: value-object / form-machine (plop; ui-component/bff = skills) | 8 · platform/DX/showcase | done |
| WP-44 | Runnable generator: gen:context |
8 · platform/DX/showcase | done |
| WP-45 | create-frontend bootstrap generator (mechanise new-ssp) |
8 · platform/DX/showcase | done |
| WP-46 | Vitest coverage (report + report-only thresholds) | 8 · platform/DX/showcase | done |
| WP-47 | Runtime feature flags (catalog-in-code, admin toggle, FE+backend) | 8 · platform/DX/showcase | done |
| WP-48 | Stamdata deletion protection (CI referential gate + editor expire/warn) | 8 · platform/DX/showcase | done |
| WP-49 | OpenZaak zaken read seam (IZaakSource + ZGW client, config-gated, offline default) | 9 · OpenZaak/ZGW | done |
| WP-50 | OpenZaak create-zaak (first write slice) | 9 · OpenZaak/ZGW | done |
| WP-51 | OpenZaak Documenten (DRC) upload + zaak link | 9 · OpenZaak/ZGW | done |
| WP-52 | OpenZaak Notificaties (NRC) live status via webhook | 9 · OpenZaak/ZGW | done |
| WP-53 | Inbound identity seam + citizen-scoping (per-request BSN, ZGW audit claims) | 9 · OpenZaak/ZGW | done |
| WP-54 | Docker OpenZaak integration-test harness (opt-in, live round-trip) | 9 · OpenZaak/ZGW | done |
| WP-55 | Real secrets + TLS for the OpenZaak harness | 10 · OpenZaak hardening | done |
| WP-56 | Idempotent catalogus provisioning | 10 · OpenZaak hardening | done |
| WP-57 | Least-privilege client scopes | 10 · OpenZaak hardening | done |
| WP-58 | Real notifications (celery + scripted abonnement) | 10 · OpenZaak hardening | done |
| WP-59 | Per-document-type confidentialiteit config | 10 · OpenZaak hardening | done |
| WP-60 | Write-divergence resilience (local + ZGW writes) | 10 · OpenZaak hardening | done |
| WP-61 | Bootstrap the behandelportal app | 11 · Behandelportal | done |
| WP-62 | Backend: medewerker caller identity + authz seam | 11 · Behandelportal | done |
| WP-63 | Backend: aanvraag status lifecycle as a published DTO | 11 · Behandelportal | done |
| WP-64 | Behandelportal: werkvoorraad (queue) screen | 11 · Behandelportal | done |
| WP-65 | Behandelportal: zaak detail + beoordeling (decision) screen | 11 · Behandelportal | done |
| WP-66 | Wire the decision into OpenZaak | 11 · Behandelportal | done |
| WP-67 | Merge behandelportal into this repo as a monorepo | 11 · Behandelportal | done |
| WP-68 | Aggregate invariants + status modelling (architecture review) | 12 · DDD hardening | done |
| WP-69 | Enforce the scholing threshold server-side | 12 · DDD hardening | done |
| WP-70 | Test-data builders: illegal fixtures unrepresentable (ADR-0006) | 12 · DDD hardening | done |
| WP-71 | Test framework coherence: BDD/DDD alignment, close the escape hatches | 12 · DDD hardening | done |
| WP-72 | Delete the dead legacy submit endpoints | 12 · DDD hardening | done |
| WP-73 | RegistrationStatus and Aanvraag as closed unions |
12 · DDD hardening | done |
| WP-74 | E2E isolation without a new backend endpoint | 12 · DDD hardening | done |
| WP-75 | Close the remaining FE/BE seams | 12 · DDD hardening | done |
Sequencing dependencies (stated in the WPs too): 01 before 10–15 (axe covers story churn);
03/04 before 05–09 (boundaries stop new violations during refactors); 06 before 07 (typed
<app-async> before brief adopts it); 13 defines the gap-marker format that 11/12 reference
— if 11/12 run first, they define it and 13 adopts it. 18–22 (phase 5, "productie-volwassenheid")
are independent of each other and of phases 1–4 — pick any order; 18 is the recommended
first pick (it's the headline gap: no authorization spine exists yet, and it closes the
FE-computed-authz anti-pattern in brief.store.ts). 22 is explicitly lower priority — the
current in-memory persistence is a documented, defensible POC choice, not a bug.
Phase 6 (Brief v2, the "Brief opstellen v2" PRD) is strictly ordered
23 → 24 → 25 → 26 → 27 → 28: 24 needs 23's orgTemplate on the wire, 25 needs 24's
letter.css contract, 26 needs 23's endpoints + 24's canvas, 27/28 polish on top.
Phase 7 (refinements, WP-31..36): only 31 → 32 is ordered (32 reuses the createHistory
helper 31 extracts); 33–36 are independent — pick any order.
Phase 8 (platform/DX/showcase, WP-37..46): goal is BOTH a teaching showcase AND a production
starter template. Priority: WP-37 (P0 quick fix) → WP-46 + WP-38 + WP-39 (P1, parallel) →
WP-40 (P2) → WP-43 (P3) → WP-41 → WP-42 → WP-44 → WP-45 (P4). Ordering constraints:
41 needs 40's PII kernel; 42 needs 40+41; 44 (gen:context) needs 38 (declarative boundaries
make the generator simple) + 43; 45 (create-frontend) needs 43+44. 37/38/39/40/43/46 are otherwise
independent. Two open tool forks, decided as step 1 of their WP: 38 dependency-cruiser vs Sheriff;
43 plop vs Angular schematics.
Phase 9 (OpenZaak/ZGW integration, WP-49..52) is strictly ordered 49 → 50 → 51 → 52: 49 lands
the source seam + ZGW client the rest reuse; 50 (create) needs a zaak to exist; 51 (documents)
links to 50's zaak; 52 (notificaties) reacts to changes on the zaken 49–51 manage. All slices
stay behind Zgw:Enabled (default off) so the POC keeps running offline.
Two cross-cutting WPs sit alongside that read→write arc: 53 (a real per-request identity
seam + citizen-scoping — replaces the stubbed owner/BSN; production-blocking for a real
deployment of 49–52) and 54 (a docker OpenZaak harness + opt-in integration test — makes
50/51/52 developable against a live instance instead of only fixtures). 53 is independent of the
CRUD arc and can land any time; 54 depends on 49 (something to read) and unlocks realistic
testing for the rest. Both are self-contained (each WP file carries its own current-state
handoff) and sized for a fresh Sonnet session.
Phase 10 (OpenZaak production hardening, WP-55..60 — now done) and Phase 11
(Behandelportal, WP-61..66) are two independent tracks that can be worked concurrently —
neither blocks the other. Within phase 10: 55/59/60 were fully independent; 57 and 58 both
built on 56's provisioning mechanism, otherwise independent of each other. Within phase 11: 61
(bootstrap), 62 (backend medewerker identity), and 63 (backend status DTO) are
independent of each other and can land in any order; 64 needs all three (61 for the app
to exist, 62 for identity, 63 for the status it reads); 65 needs 64; 66 needs 65 and — now
that WP-60 has landed (bounded retry + flagged divergence in ZgwHttpClient/Program.cs) —
inherits that retry for free, but must call RecordZgwDivergence on its own besluit write path
to get the flagging half too.
WP template
# WP-NN — Title
Status: todo | in-progress | done (<commit>)
Phase: N — name
## Why
## Read first
## Decisions (pre-made, don't relitigate)
## Files
## Steps
## Acceptance criteria
## Verification
## Out of scope
## Risks