docs(backlog): add Phase 8 — platform/DX/showcase (WP-37..46)

New phase from the 5-item analysis: linked showcase snippets + animations,
runnable scaffolding generators (pages/wizards/context/SSP), dependency graph +
declarative boundaries, PII hardening (branded Bsn VO + masked-value atom +
persisted no-PII audit) doubling as a privacy showcase, and Vitest coverage.
Goal: both a teaching showcase and a production starter template. Sequenced with
priorities + dependencies in the README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
eho
2026-07-23 13:34:01 +02:00
co-authored by Claude Opus 4.8
parent a5ac1fb729
commit a828e604d1
10 changed files with 356 additions and 38 deletions
+55 -38
View File
@@ -43,44 +43,54 @@ WP-19's own file), so it's a separate manual/CI step, not chained into the other
Gates land before the work they cover; each lint rule lands in the same WP as the fixes 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. for its existing violations, so every WP ends green.
| WP | Title | Phase | Status | | WP | Title | Phase | Status |
| ------------------------------------------ | --------------------------------------------------------------------------------- | --------------------------- | ------ | | ---------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------- | ------ |
| [WP-01](WP-01-axe-ci-gate.md) | Axe-on-every-story CI gate | 0 · gates | done | | [WP-01](WP-01-axe-ci-gate.md) | Axe-on-every-story CI gate | 0 · gates | done |
| [WP-02](WP-02-check-tokens.md) | Harden `check:tokens` + fix what it catches | 0 · gates | done | | [WP-02](WP-02-check-tokens.md) | Harden `check:tokens` + fix what it catches | 0 · gates | done |
| [WP-03](WP-03-contracts-purity.md) | Boundaries I: contracts purity + ApiClient confinement | 0 · gates | done | | [WP-03](WP-03-contracts-purity.md) | Boundaries I: contracts purity + ApiClient confinement | 0 · gates | done |
| [WP-04](WP-04-ui-not-infrastructure.md) | Boundaries II: `ui ↛ infrastructure` + showcase sanction | 0 · gates | done | | [WP-04](WP-04-ui-not-infrastructure.md) | Boundaries II: `ui ↛ infrastructure` + showcase sanction | 0 · gates | done |
| [WP-05](WP-05-parse-boundaries.md) | Parse-don't-validate closure + MDX | 1 · FP/DDD | done | | [WP-05](WP-05-parse-boundaries.md) | Parse-don't-validate closure + MDX | 1 · FP/DDD | done |
| [WP-06](WP-06-typed-async.md) | Generic async template contexts — kill `$any()` | 1 · FP/DDD | done | | [WP-06](WP-06-typed-async.md) | Generic async template contexts — kill `$any()` | 1 · FP/DDD | done |
| [WP-07](WP-07-brief-idioms.md) | Brief on the shared idioms + RemoteData MDX | 1 · FP/DDD | done | | [WP-07](WP-07-brief-idioms.md) | Brief on the shared idioms + RemoteData MDX | 1 · FP/DDD | done |
| [WP-08](WP-08-store-idiom.md) | One store idiom + machine naming + TEA MDX | 1 · FP/DDD | done | | [WP-08](WP-08-store-idiom.md) | One store idiom + machine naming + TEA MDX | 1 · FP/DDD | done |
| [WP-09](WP-09-pure-logic.md) | Pure-logic closure: dates + missing command specs | 1 · FP/DDD | done | | [WP-09](WP-09-pure-logic.md) | Pure-logic closure: dates + missing command specs | 1 · FP/DDD | done |
| [WP-10](WP-10-button-fidelity.md) | CIBG button fidelity | 2 · CIBG | done | | [WP-10](WP-10-button-fidelity.md) | CIBG button fidelity | 2 · CIBG | done |
| [WP-11](WP-11-markup-fidelity.md) | CIBG markup fidelity: application-link + absent-class triage | 2 · CIBG | done | | [WP-11](WP-11-markup-fidelity.md) | CIBG markup fidelity: application-link + absent-class triage | 2 · CIBG | done |
| [WP-12](WP-12-datablock.md) | CIBG Datablock for application data | 2 · CIBG | done | | [WP-12](WP-12-datablock.md) | CIBG Datablock for application data | 2 · CIBG | done |
| [WP-13](WP-13-cibg-gap-register.md) | CIBG-gap register + hygiene + MDX | 2 · CIBG | done | | [WP-13](WP-13-cibg-gap-register.md) | CIBG-gap register + hygiene + MDX | 2 · CIBG | done |
| [WP-14](WP-14-storybook-taxonomy.md) | Storybook taxonomy reorg + Layers MDX | 3 · Storybook | done | | [WP-14](WP-14-storybook-taxonomy.md) | Storybook taxonomy reorg + Layers MDX | 3 · Storybook | done |
| [WP-15](WP-15-missing-stories.md) | Missing stories: shell + brief components | 3 · Storybook | done | | [WP-15](WP-15-missing-stories.md) | Missing stories: shell + brief components | 3 · Storybook | done |
| [WP-16](WP-16-component-a11y.md) | Component a11y: description wiring + alert role | 4 · a11y | done | | [WP-16](WP-16-component-a11y.md) | Component a11y: description wiring + alert role | 4 · a11y | done |
| [WP-17](WP-17-app-a11y.md) | App-level a11y: route focus, template lint, WCAG checklist | 4 · a11y | done | | [WP-17](WP-17-app-a11y.md) | App-level a11y: route focus, template lint, WCAG checklist | 4 · a11y | done |
| [WP-18](WP-18-abac-capability-spine.md) | ABAC capability spine (Principal + capabilities, phase P1) | 5 · productie-volwassenheid | done | | [WP-18](WP-18-abac-capability-spine.md) | ABAC capability spine (Principal + capabilities, phase P1) | 5 · productie-volwassenheid | done |
| [WP-19](WP-19-e2e-smoke.md) | Playwright e2e smoke | 5 · productie-volwassenheid | done | | [WP-19](WP-19-e2e-smoke.md) | Playwright e2e smoke | 5 · productie-volwassenheid | done |
| [WP-20](WP-20-second-locale.md) | Second locale proof | 5 · productie-volwassenheid | done | | [WP-20](WP-20-second-locale.md) | Second locale proof | 5 · productie-volwassenheid | done |
| [WP-21](WP-21-resilience-seams.md) | Resilience seams (correlation-id, idempotency, retry) | 5 · productie-volwassenheid | done | | [WP-21](WP-21-resilience-seams.md) | Resilience seams (correlation-id, idempotency, retry) | 5 · productie-volwassenheid | done |
| [WP-22](WP-22-durable-persistence.md) | Durable persistence (optional tier) | 5 · productie-volwassenheid | done | | [WP-22](WP-22-durable-persistence.md) | Durable persistence (optional tier) | 5 · productie-volwassenheid | done |
| [WP-23](WP-23-org-template-backend.md) | Org-template backend + admin role | 6 · Brief v2 | done | | [WP-23](WP-23-org-template-backend.md) | Org-template backend + admin role | 6 · Brief v2 | done |
| [WP-24](WP-24-letter-canvas.md) | Letter canvas (edit on the letter) | 6 · Brief v2 | done | | [WP-24](WP-24-letter-canvas.md) | Letter canvas (edit on the letter) | 6 · Brief v2 | done |
| [WP-25](WP-25-letter-preview-html.md) | Server-rendered letter preview (HTML; PDF deferred) | 6 · Brief v2 | done | | [WP-25](WP-25-letter-preview-html.md) | Server-rendered letter preview (HTML; PDF deferred) | 6 · Brief v2 | done |
| [WP-26](WP-26-org-template-editor.md) | Admin org-template editor | 6 · Brief v2 | done | | [WP-26](WP-26-org-template-editor.md) | Admin org-template editor | 6 · Brief v2 | done |
| [WP-27](WP-27-brief-ux-layer.md) | Brief UX layer (undo/redo, standaardbrief, diff) | 6 · Brief v2 | done | | [WP-27](WP-27-brief-ux-layer.md) | Brief UX layer (undo/redo, standaardbrief, diff) | 6 · Brief v2 | done |
| [WP-28](WP-28-brief-v2-demo-polish.md) | Brief v2 demo polish (scenarios, e2e, docs) | 6 · Brief v2 | todo | | [WP-28](WP-28-brief-v2-demo-polish.md) | Brief v2 demo polish (scenarios, e2e, docs) | 6 · Brief v2 | todo |
| [WP-29](WP-29-stamdata-beheer-editor.md) | Stamdata beheer editor (low-code, PR-emitting) | follow-on · ADR-0004 | done | | [WP-29](WP-29-stamdata-beheer-editor.md) | Stamdata beheer editor (low-code, PR-emitting) | follow-on · ADR-0004 | done |
| [WP-30](WP-30-ci-perf-followups.md) | CI performance follow-ups (node_modules cache, runner image, path filters) | follow-on · CI/infra | todo | | [WP-30](WP-30-ci-perf-followups.md) | CI performance follow-ups (node_modules cache, runner image, path filters) | follow-on · CI/infra | todo |
| [WP-31](WP-31-shared-store-helpers.md) | Shared store helpers (ActionState/SaveState, history, debounced-save, RemoteData) | 7 · refinements | done | | [WP-31](WP-31-shared-store-helpers.md) | Shared store helpers (ActionState/SaveState, history, debounced-save, RemoteData) | 7 · refinements | done |
| [WP-32](WP-32-stamdata-undo.md) | Undo/redo in the stamdata editor | 7 · refinements | done | | [WP-32](WP-32-stamdata-undo.md) | Undo/redo in the stamdata editor | 7 · refinements | done |
| [WP-33](WP-33-dev-switchers.md) | In-app dev switchers (scenario + role) | 7 · refinements | done | | [WP-33](WP-33-dev-switchers.md) | In-app dev switchers (scenario + role) | 7 · refinements | done |
| [WP-34](WP-34-adres-phone-brp-readonly.md) | Adres: phone field + BRP address read-only | 7 · refinements | done | | [WP-34](WP-34-adres-phone-brp-readonly.md) | Adres: phone field + BRP address read-only | 7 · refinements | done |
| [WP-35](WP-35-one-concept-per-type.md) | One Concept per case type (server-enforced) | 7 · refinements | done | | [WP-35](WP-35-one-concept-per-type.md) | One Concept per case type (server-enforced) | 7 · refinements | done |
| [WP-36](WP-36-admin-cases.md) | Admin cases page + admin delete | 7 · refinements | done | | [WP-36](WP-36-admin-cases.md) | Admin cases page + admin delete | 7 · refinements | done |
| [WP-37](WP-37-dev-switcher-reset.md) | Dev-switcher reset fix (scenario/role URL param) | 8 · platform/DX/showcase | done |
| [WP-38](WP-38-dependency-graph-boundaries.md) | Dependency graph + declarative boundaries (visualize + enforce) | 8 · platform/DX/showcase | todo |
| [WP-39](WP-39-showcase-snippets-animations.md) | Showcase: linked code snippets + teaching animations | 8 · platform/DX/showcase | todo |
| [WP-40](WP-40-pii-kernel.md) | PII kernel: branded `Bsn` VO (elfproef) + masked-value atom | 8 · platform/DX/showcase | todo |
| [WP-41](WP-41-persisted-authz-audit.md) | Persisted, queryable authz/PII-reveal audit (no PII) | 8 · platform/DX/showcase | todo |
| [WP-42](WP-42-privacy-security-showcase.md) | Privacy & security showcase page (mask + no-PII log) | 8 · platform/DX/showcase | todo |
| [WP-43](WP-43-scaffold-generators.md) | Runnable generators: value-object / form-machine / bff-endpoint / ui-component | 8 · platform/DX/showcase | todo |
| [WP-44](WP-44-context-generator.md) | Runnable generator: `gen:context` | 8 · platform/DX/showcase | todo |
| [WP-45](WP-45-create-ssp-generator.md) | `create-ssp` bootstrap generator (mechanise new-ssp) | 8 · platform/DX/showcase | todo |
| [WP-46](WP-46-vitest-coverage.md) | Vitest coverage (report + report-only thresholds) | 8 · platform/DX/showcase | todo |
Sequencing dependencies (stated in the WPs too): 01 before 10–15 (axe covers story churn); 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 03/04 before 05–09 (boundaries stop new violations during refactors); 06 before 07 (typed
@@ -95,6 +105,13 @@ Phase 6 (Brief v2, the "Brief opstellen v2" PRD) is strictly ordered
`letter.css` contract, 26 needs 23's endpoints + 24's canvas, 27/28 polish on top. `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` 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. 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-ssp`) 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.
## WP template ## WP template
@@ -0,0 +1,38 @@
# WP-38 — Dependency graph + declarative boundaries
Status: todo
Phase: 8 — platform/DX/showcase
Priority: P1
## Why
Bounded-context + atomic-layer boundaries are enforced only by hand-duplicated
`no-restricted-imports` blocks in `eslint.config.mjs` — pass/fail, no graph, and brittle: the
`new-context` skill literally says "grep the config and copy a block", and `herregistratie` is
missing its explicit ban block (asymmetry). We want to **see** the dependencies AND **enforce**
them from one declarative source.
## Decisions
- **Step 1 — tool fork:** dependency-cruiser (recommended: graph + CI rules on plain Angular) vs
Sheriff (tag-based, DDD/atomic-native, weaker graph). Decide before building.
- Encode context + layer rules once (contexts `shared/auth/registratie/herregistratie/brief/beheer/
showcase`; layers `domain/application/infrastructure/contracts/ui`); **fix the herregistratie gap**.
- Keep ESLint for the intra-file rules it does better (`domain↛@angular`, ApiClient value-import
confinement, `no-explicit-any`); migrate only the cross-module _direction_ rules to the new tool.
- Emit a graph the showcase/teaching can reuse (feeds WP-39).
## Files
- New: `.dependency-cruiser.js` (or `sheriff.config.ts`); `npm run graph` + validate script.
- `scripts/ci-local.sh` + `.github/workflows/ci.yml` — add `depcruise --validate` (non-optional).
- `eslint.config.mjs` — remove the migrated direction rules (keep the rest).
- New doc `docs/reference/architecture/dependencies.md`; embed the graph in a Foundations page.
- `.claude/skills/new-context/SKILL.md` — point at the single declarative source.
## Acceptance criteria
- [ ] One declarative config expresses all allowed context/layer edges; herregistratie included.
- [ ] `npm run graph` produces an architecture graph (SVG/HTML); validate runs in `npm run ci`.
- [ ] A deliberately-illegal import fails the validate step (proven, then reverted).
- [ ] No loss of enforcement vs the old ESLint blocks; `npm run ci` green.
@@ -0,0 +1,35 @@
# WP-39 — Showcase: linked code snippets + teaching animations
Status: todo
Phase: 8 — platform/DX/showcase
Priority: P1
## Why
The `/concepts` showcase shows live components, but its code snippets are hand-authored
`innerHTML` strings with manual highlighting — they drift from the real code. And the animation
toolkit (`withViewTransitions`, native `animate.enter/leave`, reduced-motion-safe keyframes in
`styles.scss`) is proven in dashboard/upload but unused for teaching motion.
## Decisions
- **Anti-drift snippets:** load _real_ source excerpts via Vite `?raw` imports with `// #region name`
markers + a tiny pure extractor; render with lightweight highlighting. No more `innerHTML` literals.
- On the Storybook/MDX side, use `<Source>`/Canvas "show code" consistently (extend beyond the
current 4 of 14 pages).
- **Reuse** the existing motion toolkit (no new animation lib): `animate.enter/leave` on the
fold/union demos + a "step through `reduce`" animation for the TEA loop; keep reduced-motion-safe.
## Files
- `src/app/showcase/concepts.page.ts` — replace `innerHTML` snippets with `?raw`+region excerpts;
apply animations.
- New tiny extractor util (pure) + spec (region slicing).
- `src/docs/machines.mdx`, `fp-in-ui.mdx`, `remote-data.mdx` — reference the live linked snippets.
## Acceptance criteria
- [ ] Every showcase snippet is sourced from a real file (edit the source → snippet updates); none
hand-copied.
- [ ] The TEA/fold demos animate (reduced-motion respected); no new animation dependency.
- [ ] Storybook a11y stays green; `npm run ci` green.
+34
View File
@@ -0,0 +1,34 @@
# WP-40 — PII kernel: branded `Bsn` VO + masked-value atom
Status: todo
Phase: 8 — platform/DX/showcase
Priority: P2
## Why
Masking is real but ad-hoc: pure helpers live in `debug-state/mask.ts`, the BSN is a bare `string`
in `session.ts` (no branded VO / checksum, unlike `BigNummer`/`Postcode`), and masked state is
detected by `.includes('*')` sniffing — no reusable atomic-design component. For a register this is
AVG art. 9 (BSN = special category) territory; the pieces should be first-class and reusable.
## Decisions
- `Bsn` branded value object + `parseBsn` with the **elfproef** (9 digits, weighted mod-11)
checksum — parity with the other value objects. Replace the bare `string` in `session.ts`.
- Consolidate the pure maskers into `shared/kernel/pii.ts` (functional core): `maskBsn`, `maskTail`,
`redactProfile`, … Keep the backend `MaskTail` twin in sync.
- `<app-masked-value>` atom (`shared/ui`) — masked by default, optional reveal affordance + a11y;
replaces `.includes('*')` sniffing and composes into the behandel-scherm reveal.
## Files
- New `src/app/shared/kernel/bsn.ts` (+spec, elfproef cases) and `shared/kernel/pii.ts` (+spec).
- `src/app/auth/domain/session.ts` — `bsn: Bsn`.
- New `src/app/shared/ui/masked-value/` atom (+ story).
- `src/app/shared/ui/debug-state/mask.ts` — re-export/move to the kernel module.
## Acceptance criteria
- [ ] `parseBsn` accepts valid elfproef numbers, rejects bad checksum/length (spec).
- [ ] `<app-masked-value>` renders masked by default with an accessible reveal; behandel-scherm uses it.
- [ ] Pure maskers have specs (no TestBed); backend `MaskTail` parity noted; `npm run ci` green.
@@ -0,0 +1,34 @@
# WP-41 — Persisted, queryable authz/PII-reveal audit
Status: todo
Phase: 8 — platform/DX/showcase
Priority: P2
Depends on: WP-40
## Why
The security-relevant events (authz denials via `AuditAuthz`, BIG-nummer reveal, step-up) are
**log-only** — never persisted, so there's no queryable trail. The one persisted `AuditEntry` table
covers document lifecycle only. PRD-0002 §8 calls for a persisted authorization audit. A register
(BIO) wants this queryable, and strictly data-minimised.
## Decisions
- Persist authz/reveal/step-up events into a data-minimised EF table extending the existing
`AuditEntry`/`AppDbContext` pattern: role, action, resource-ref, decision, correlation-id,
timestamp — **never** the value or any PII.
- Wire `AuditAuthz` + the reveal endpoint to persist (in addition to the log line).
- Optional thin admin read view — reuse `cases:manage` or add an `audit:read` capability.
## Files
- `backend/.../Data/` — new audit entity + `AppDbContext` DbSet + EF migration.
- `backend/.../Program.cs` — `AuditAuthz` + reveal endpoint persist.
- Backend tests: events are persisted; **no PII columns**; correlation-id captured.
- (Optional) FE audit read page + adapter/store, gated by the capability.
## Acceptance criteria
- [ ] Denials, reveals, and step-up attempts land as rows with no PII/value fields.
- [ ] A test asserts the schema carries no name/bsn/value column.
- [ ] `dotnet test` + `npm run ci` green; api-client drift clean if endpoints added.
@@ -0,0 +1,31 @@
# WP-42 — Privacy & security showcase page
Status: todo
Phase: 8 — platform/DX/showcase
Priority: P2
Depends on: WP-40, WP-41
## Why
Once the reusable privacy pieces exist (WP-40 masked-value atom + pure maskers, WP-41 persisted
no-PII audit), showcase them as a teaching artifact: how to **mask** and **log** PII safely with FP
(pure functional core) + atomic design (the masked-value atom), tied to the existing reveal +
step-up + capability slice.
## Decisions
- A Foundations MDX + a small `showcase` demo (showcase context may read every context).
- Teach the principles explicitly: data-minimisation, PII out of logs, masked-by-default +
audited reveal, server as authority — framed for a Dutch register (AVG art. 9, BIO).
- Reuse WP-39's linked-snippet mechanism so the shown code can't drift.
## Files
- New `src/docs/privacy-security.mdx` (Foundations).
- New/extended `showcase` demo wiring `<app-masked-value>`, the pure maskers, and the reveal flow.
## Acceptance criteria
- [ ] Page demonstrates mask + no-PII logging with live components and linked (non-drifting) code.
- [ ] Explains the register rationale (AVG/BIO/data-minimisation) concisely.
- [ ] Storybook a11y green; `npm run ci` green.
@@ -0,0 +1,31 @@
# WP-43 — Runnable leaf generators
Status: todo
Phase: 8 — platform/DX/showcase
Priority: P3
## Why
The house patterns (value object, form machine, bff endpoint, ui component) are documented only as
agent-executed prose skills; there's no `npm run gen:*` a developer runs to emit the boilerplate.
For a template meant to bootstrap real registers, mechanise the highest-churn recipes.
## Decisions
- **Step 1 — tool fork:** plop (recommended: tiny, readable templates, `npm run gen:*`) vs Angular
schematics (native `ng generate`, heavier to author). Decide before building.
- Generators emit house-pattern files **with co-located spec/story**, matching current idioms:
`gen:value-object`, `gen:form-machine` (page/wizard), `gen:bff-endpoint`, `gen:ui-component`.
- Templates become the single source; each matching skill gets a one-line "run `npm run gen:x`, then …".
## Files
- New `plopfile.mjs` (or `schematics/`) + template files; `package.json` `gen:*` scripts.
- Update the corresponding `.claude/skills/*/SKILL.md` to invoke the generator.
## Acceptance criteria
- [ ] Each generator emits files that pass `npm run lint` + `npm run ci` with no hand-edits beyond
filling in the domain specifics.
- [ ] Generated units include their spec/story per house rules.
- [ ] A smoke run of each generator is documented; `npm run ci` green.
@@ -0,0 +1,32 @@
# WP-44 — `gen:context` generator
Status: todo
Phase: 8 — platform/DX/showcase
Priority: P3
Depends on: WP-38, WP-43
## Why
Adding a bounded context is currently a manual multi-file edit (folders + tsconfig alias + copied
ESLint boundary block + lazy route) — the `new-context` skill's most error-prone steps. Mechanise it.
## Decisions
- `gen:context` (same tool as WP-43) emits `{domain,application,infrastructure,ui,contracts}`
folders + the `@ctx/*` tsconfig path alias + a lazy route + the boundary entry.
- **Simpler because of WP-38:** boundaries are declarative by then, so the generator adds one config
entry instead of hand-copying ESLint blocks.
- Replaces the manual portion of the `new-context` skill.
## Files
- Extend the WP-43 generator setup with a `context` generator + templates.
- Wire tsconfig alias + boundary config + route insertion (idempotent edits).
- Update `.claude/skills/new-context/SKILL.md`.
## Acceptance criteria
- [ ] `npm run gen:context <name>` produces a context that lints clean (boundaries recognised) and
routes lazily.
- [ ] Boundary tool (WP-38) validates the new context's allowed edges.
- [ ] `npm run ci` green.
@@ -0,0 +1,32 @@
# WP-45 — `create-ssp` bootstrap generator
Status: todo
Phase: 8 — platform/DX/showcase
Priority: P4
Depends on: WP-43, WP-44
## Why
`new-ssp` is a manual fork-and-strip recipe. To actually bootstrap a production-ready frontend for
a different Dutch register quickly, mechanise it into a runnable script that produces a clean
scaffold keeping the enforced architecture + shared building blocks.
## Decisions
- A `create-ssp` script (Node/plop-driven) that clones-and-strips per the `new-ssp` recipe:
drop the business contexts, prune their routes/aliases/boundary entries, rename `BigRegister.*`,
re-point the `--rhc-*` token bridge, re-seed the backend, re-run `gen:api`.
- Keep-as-is list from `new-ssp` (shared kernel/ui/layout, eslint/boundary config, check-tokens,
CI, nswag, storybook, auth shell, ADRs, CLAUDE.md, the skills).
- Reuse WP-44's `gen:context` to seed the first real context of the new portal.
## Files
- New `scripts/create-ssp.*` (+ any templates); documented in `.claude/skills/new-ssp/SKILL.md`.
## Acceptance criteria
- [ ] Running it yields a portal that builds, lints (boundaries intact), and passes `npm run ci`
with no business contexts left over.
- [ ] The generated scaffold's `gen:api` + token bridge point at the new backend/theme.
- [ ] Documented end-to-end in the `new-ssp` skill.
@@ -0,0 +1,34 @@
# WP-46 — Vitest coverage (report + thresholds)
Status: todo
Phase: 8 — platform/DX/showcase
Priority: P1
## Why
Tests run via the `@angular/build:unit-test` (Vitest) builder but report no coverage — there's no
visibility into what the house test rules actually cover. A template/showcase should surface this.
## Decisions
- **Step 1 — verify wiring:** confirm how coverage is enabled with the Angular unit-test builder
(`codeCoverage` option and/or a vitest config), add `@vitest/coverage-v8`.
- `npm run test:coverage` → text + HTML/lcov report.
- **Report-only first** (ponytail: visibility before a blocking gate, like the semgrep job).
Threshold focus on the layers the house rules already demand tests for (`domain/**`, pure
combinators, `parse*` boundaries, reducers), not a blanket global %; exclude stories, the
generated `api-client.ts`, and DTOs/contracts.
- Wire into `ci-local.sh`/CI **non-blocking**, with a documented path to flip on enforcement later.
- **Open choice:** report-only vs enforce a minimum now.
## Files
- `package.json` (`test:coverage` + dep), coverage config (builder option or vitest config).
- `scripts/ci-local.sh` / `.github/workflows/ci.yml` — non-blocking coverage step.
- `.gitignore` — ignore the coverage output dir.
## Acceptance criteria
- [ ] `npm run test:coverage` produces a report (text + HTML/lcov) with sensible excludes.
- [ ] Coverage runs in CI without failing the build (report-only).
- [ ] `npm run ci` green.