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:
@@ -44,7 +44,7 @@ Gates land before the work they cover; each lint rule lands in the same WP as th
|
|||||||
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 |
|
||||||
@@ -81,6 +81,16 @@ for its existing violations, so every WP ends green.
|
|||||||
| [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.
|
||||||
@@ -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.
|
||||||
Reference in New Issue
Block a user