docs: reorganize into project/ + reference/, expand Storybook Foundations

Move working docs (backlog, prd, roadmap) under docs/project/ and durable
docs (architecture ADRs, guides, audits) under docs/reference/; add a
docs/README.md index. Update every path reference in code comments, CLAUDE.md,
READMEs, and the new-ssp skill. Expand the Storybook Foundations curriculum
(Overview, BDD, i18n; rename Layers→Domain-Driven Design) and reorder the sidebar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
eho
2026-07-20 20:33:32 +02:00
co-authored by Claude Opus 4.8
parent 55a0a2d166
commit d4e5a76873
40 changed files with 1699 additions and 1087 deletions
+4 -4
View File
@@ -17,8 +17,8 @@ business content. Keep the machinery, replace the domain.
`nswag.json`, `.storybook/`, `proxy.conf.json`, `.npmrc` (`legacy-peer-deps` — `nswag.json`, `.storybook/`, `proxy.conf.json`, `.npmrc` (`legacy-peer-deps` —
and never `npm audit fix --force`, it downgrades Angular). and never `npm audit fix --force`, it downgrades Angular).
- `src/app/auth/` (fake auth shell) and `src/app/shared/infrastructure/scenario.interceptor.ts` (dev-only). - `src/app/auth/` (fake auth shell) and `src/app/shared/infrastructure/scenario.interceptor.ts` (dev-only).
- `docs/architecture/` ADRs 0001–0003 — the decisions still apply; amend, don't delete. - `docs/reference/architecture/` ADRs 0001–0003 — the decisions still apply; amend, don't delete.
- `CLAUDE.md`, `docs/ARCHITECTURE.md`, `docs/fp-tea-atomic-design.md` — update names/examples as contexts change. - `CLAUDE.md`, `docs/reference/architecture/ARCHITECTURE.md`, `docs/reference/fp-tea-atomic-design.md` — update names/examples as contexts change.
- `.claude/skills/` — these recipes are the point of the template. - `.claude/skills/` — these recipes are the point of the template.
## Strip / replace ## Strip / replace
@@ -39,8 +39,8 @@ business content. Keep the machinery, replace the domain.
- Branding: `public/cibg-huisstijl/` + the token bridge in `src/styles.scss` — for a - Branding: `public/cibg-huisstijl/` + the token bridge in `src/styles.scss` — for a
different house style, swap the vendored CSS and re-point the `--rhc-*` bridge different house style, swap the vendored CSS and re-point the `--rhc-*` bridge
(ADR-0003 pattern: bridge, don't rewrite tokens). (ADR-0003 pattern: bridge, don't rewrite tokens).
- `docs/backlog/` WPs, PRDs, and memory-specific docs — new portal, new backlog - `docs/project/backlog/` WPs, PRDs, and memory-specific docs — new portal, new backlog
(keep `docs/backlog/README.md`'s WP process/template if you like the workflow). (keep `docs/project/backlog/README.md`'s WP process/template if you like the workflow).
## Verify — the GREEN gate must pass at every step ## Verify — the GREEN gate must pass at every step
+15
View File
@@ -31,6 +31,21 @@ const preview: Preview = {
storySort: { storySort: {
order: [ order: [
'Foundations', 'Foundations',
[
'Overview',
'Domain-Driven Design',
'Atomic Design',
'FP in the UI',
'State Machines (TEA)',
'RemoteData & Async',
"Parse, don't validate",
'Design Tokens',
'CIBG Gap Register',
'Accessibility',
'Testing strategy',
'BDD',
'Internationalization',
],
'Design System', 'Design System',
['Atoms', 'Molecules', 'Organisms', 'Templates', 'Devtools'], ['Atoms', 'Molecules', 'Organisms', 'Templates', 'Devtools'],
'Domein', 'Domein',
+4 -4
View File
@@ -1,8 +1,8 @@
# CLAUDE.md # CLAUDE.md
Agent guide for this repo. The _why_ lives in `docs/ARCHITECTURE.md`, Agent guide for this repo. The _why_ lives in `docs/reference/architecture/ARCHITECTURE.md`,
`docs/architecture/0001-bff-lite-decision-dtos.md`, and the learning guide `docs/reference/architecture/0001-bff-lite-decision-dtos.md`, and the learning guide
`docs/fp-tea-atomic-design.md` (FP + The Elm Architecture + atomic design); this `docs/reference/fp-tea-atomic-design.md` (FP + The Elm Architecture + atomic design); this
file is the _rules_. When a decision below and those docs disagree, the docs win — file is the _rules_. When a decision below and those docs disagree, the docs win —
update this file. update this file.
@@ -12,7 +12,7 @@ signals. Auth is faked; **data and business rules are served by a minimal ASP.NE
Core backend** (`backend/`, see its README) and consumed through an NSwag-generated Core backend** (`backend/`, see its README) and consumed through an NSwag-generated
typed client. The FE renders the backend's decisions. Reference data mimicking typed client. The FE renders the backend's decisions. Reference data mimicking
BRP/DUO (`Data/SeedData.cs`) is in-memory; applications, documents and the brief BRP/DUO (`Data/SeedData.cs`) is in-memory; applications, documents and the brief
persist to a SQLite file via EF Core (WP-22) — `docs/backlog/WP-22-durable-persistence.md`. persist to a SQLite file via EF Core (WP-22) — `docs/project/backlog/WP-22-durable-persistence.md`.
## Commands ## Commands
+1 -1
View File
@@ -41,7 +41,7 @@ eligibility, thresholds); see **[backend/README.md](backend/README.md)**.
> **New here:** a **branching intake questionnaire** (`/intake`) where later questions > **New here:** a **branching intake questionnaire** (`/intake`) where later questions
> appear based on earlier answers and progress survives a page reload, plus a visual > appear based on earlier answers and progress survives a page reload, plus a visual
> walkthrough of the state-management ideas. See > walkthrough of the state-management ideas. See
> **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** for diagrams (atomic-design pyramid, > **[docs/reference/architecture/ARCHITECTURE.md](docs/reference/architecture/ARCHITECTURE.md)** for diagrams (atomic-design pyramid,
> the dispatch→reduce→view loop, RemoteData states, and "why not just signals") and a > the dispatch→reduce→view loop, RemoteData states, and "why not just signals") and a
> section on **connecting to a .NET backend**. > section on **connecting to a .NET backend**.
+2 -2
View File
@@ -2,7 +2,7 @@
The backend that hosts the **business rules** for the BIG-register portal. The The backend that hosts the **business rules** for the BIG-register portal. The
frontend renders the decisions this service computes; it does not recompute them frontend renders the decisions this service computes; it does not recompute them
(BFF-lite + decision DTOs — see `../docs/architecture/0001-bff-lite-decision-dtos.md`). (BFF-lite + decision DTOs — see `../docs/reference/architecture/0001-bff-lite-decision-dtos.md`).
No real BRP/DUO: the reference data they'd return (registration, person, diplomas, No real BRP/DUO: the reference data they'd return (registration, person, diplomas,
notes — `Data/SeedData.cs`) is in-memory and seeded, but the endpoints, DTOs, notes — `Data/SeedData.cs`) is in-memory and seeded, but the endpoints, DTOs,
@@ -15,7 +15,7 @@ status codes and error envelope are production-shaped.
covers it, see `docker-compose.yml`) does **not** lose data. Delete the file to covers it, see `docker-compose.yml`) does **not** lose data. Delete the file to
reset demo data back to empty, the same state a fresh clone starts from. This is reset demo data back to empty, the same state a fresh clone starts from. This is
a deliberate, right-sized choice for a POC (SQLite, no external DB service) — see a deliberate, right-sized choice for a POC (SQLite, no external DB service) — see
`docs/backlog/WP-22-durable-persistence.md`. `docs/project/backlog/WP-22-durable-persistence.md`.
## Run ## Run
@@ -150,7 +150,7 @@ public static class LetterHtml
private static string EncLines(string s) => Enc(s).Replace("\n", "<br>"); private static string EncLines(string s) => Enc(s).Replace("\n", "<br>");
// Walks up from the running assembly's own directory (NOT the process cwd, which // Walks up from the running assembly's own directory (NOT the process cwd, which
// varies by how `dotnet run`/docker/tests invoke it — see docs/backlog/WP-25) until // varies by how `dotnet run`/docker/tests invoke it — see docs/project/backlog/WP-25) until
// it finds `public/letter.css`. docker-compose.yml bind-mounts `./public` under the // it finds `public/letter.css`. docker-compose.yml bind-mounts `./public` under the
// api container's `/src` for exactly this walk to resolve there too. // api container's `/src` for exactly this walk to resolve there too.
private static string FindLetterCss() private static string FindLetterCss()
+36
View File
@@ -0,0 +1,36 @@
# Documentation
Docs are split by **kind**, and kept out of each other's way:
- **`reference/` — information.** How the system works and why: architecture, decisions
(ADRs), the FP/TEA/atomic learning guide, accessibility and UX reference. Stable
knowledge, not tied to a sprint.
- **`project/` — administration.** Planning and tracking: the work-package backlog,
product requirements (PRDs), and the (superseded) roadmap. This is the moving,
process-facing material.
Teaching material that is best read **next to the components** lives in Storybook, not
here — see the **Foundations** section (`src/docs/*.mdx`), starting at *Foundations →
Overview*. The `reference/` docs are the long-form source; the Foundations pages are the
condensed, cross-linked curriculum.
## `reference/` — information
| Doc | What it is |
| --- | --- |
| [architecture/ARCHITECTURE.md](reference/architecture/ARCHITECTURE.md) | The architecture walkthrough: contexts/layers, state management, parse-don't-validate, the feature recipe, the .NET backend seam. |
| [architecture/0001-bff-lite-decision-dtos.md](reference/architecture/0001-bff-lite-decision-dtos.md) | ADR — BFF-lite endpoints + decision DTOs (backend decides, FE renders). |
| [architecture/0002-user-groups-and-bounded-contexts.md](reference/architecture/0002-user-groups-and-bounded-contexts.md) | ADR — user groups as actors; identity vs authorization. |
| [architecture/0003-cibg-huisstijl.md](reference/architecture/0003-cibg-huisstijl.md) | ADR — adopt CIBG Huisstijl (vendored Bootstrap 5.2) + the token bridge. |
| [fp-tea-atomic-design.md](reference/fp-tea-atomic-design.md) | Long-form learning guide: FP + The Elm Architecture + atomic design. |
| [wcag-checklist.md](reference/wcag-checklist.md) | Manual WCAG checks automation can't catch (tab order, focus traps, reflow). |
| [ui-ux-audit.md](reference/ui-ux-audit.md) | Early UI/UX audit against NL Design System (predates ADR-0003 — read in that light). |
## `project/` — administration
| Doc | What it is |
| --- | --- |
| [backlog/README.md](project/backlog/README.md) | The work-package backlog index (WP-01…WP-28) — the live tracker. |
| [prd/0001-mijn-aanvragen-en-wizardstatus.md](project/prd/0001-mijn-aanvragen-en-wizardstatus.md) | PRD — "Mijn aanvragen": running wizards, application status, document preview. |
| [prd/0002-attribute-based-access-control.md](project/prd/0002-attribute-based-access-control.md) | PRD — attribute-based access control in the UI. |
| [SHOWCASE-ROADMAP.md](project/SHOWCASE-ROADMAP.md) | Superseded roadmap (absorbed into `project/backlog/`) — kept for history. |
+1 -1
View File
@@ -1,6 +1,6 @@
# Showcase roadmap — superseded # Showcase roadmap — superseded
**This roadmap is superseded by [`docs/backlog/`](backlog/README.md)** (2026-07-02). **This roadmap is superseded by [`docs/project/backlog/`](backlog/README.md)** (2026-07-02).
The backlog absorbs and corrects this document: its Storybook-as-curriculum track became The backlog absorbs and corrects this document: its Storybook-as-curriculum track became
WP-14/15 (+ per-invariant MDX pages in WP-05/07/08/13/17), its enforcement track became WP-14/15 (+ per-invariant MDX pages in WP-05/07/08/13/17), its enforcement track became
+1 -1
View File
@@ -5,7 +5,7 @@ design-system fidelity, DDD/FP consistency, Storybook as curriculum, and WCAG co
with automated gates. Source: the architecture/CIBG/a11y audit of 2026-07-02 (plan: with automated gates. Source: the architecture/CIBG/a11y audit of 2026-07-02 (plan:
"Showcase hardening"). "Showcase hardening").
This backlog **supersedes `docs/SHOWCASE-ROADMAP.md`**. This backlog **supersedes `docs/project/SHOWCASE-ROADMAP.md`**.
## Session protocol ## Session protocol
+1 -1
View File
@@ -55,7 +55,7 @@ build", so every story added or changed by later WPs is automatically covered.
6. Run locally against a fresh `build-storybook`; triage violations: fix trivial ones 6. Run locally against a fresh `build-storybook`; triage violations: fix trivial ones
(labels, roles, contrast via `--rhc-*` tokens); anything structural gets the escape (labels, roles, contrast via `--rhc-*` tokens); anything structural gets the escape
hatch + comment + WP cross-ref. hatch + comment + WP cross-ref.
7. Update `docs/backlog/README.md`: GREEN now includes `npm run test-storybook:ci`. 7. Update `docs/project/backlog/README.md`: GREEN now includes `npm run test-storybook:ci`.
## Acceptance criteria ## Acceptance criteria
@@ -11,7 +11,7 @@ Phase 0 — a pure move of wiring, no behavior change.
## Read first ## Read first
- `CLAUDE.md` §1, `docs/ARCHITECTURE.md` - `CLAUDE.md` §1, `docs/reference/architecture/ARCHITECTURE.md`
- `eslint.config.mjs` - `eslint.config.mjs`
- `src/app/registratie/ui/registratie-wizard/registratie-wizard.component.ts` (lines - `src/app/registratie/ui/registratie-wizard/registratie-wizard.component.ts` (lines
~18-19: `BrpAdapter`/`parseBrpAddress`, `DuoAdapter`/`parseDuoLookup`) ~18-19: `BrpAdapter`/`parseBrpAddress`, `DuoAdapter`/`parseDuoLookup`)
@@ -38,7 +38,7 @@ Phase 0 — a pure move of wiring, no behavior change.
lookups move behind a registratie `application/` facade (command or store methods) lookups move behind a registratie `application/` facade (command or store methods)
- `src/app/herregistratie/ui/intake-wizard/intake-wizard.component.ts` — policy resource - `src/app/herregistratie/ui/intake-wizard/intake-wizard.component.ts` — policy resource
moves behind **new** `src/app/herregistratie/application/` (folder doesn't exist yet) moves behind **new** `src/app/herregistratie/application/` (folder doesn't exist yet)
- `docs/ARCHITECTURE.md` — fix "three contexts, four layers" → six contexts - `docs/reference/architecture/ARCHITECTURE.md` — fix "three contexts, four layers" → six contexts
(shared, auth, registratie, herregistratie, brief, showcase), five layers (+ contracts); (shared, auth, registratie, herregistratie, brief, showcase), five layers (+ contracts);
add the showcase sanction add the showcase sanction
- `CLAUDE.md` §1 — add `brief` to the context list; note the showcase sanction - `CLAUDE.md` §1 — add `brief` to the context list; note the showcase sanction
@@ -10,7 +10,7 @@ principle (every response through a hand-written `parse*` returning `Result`).
## Read first ## Read first
- `CLAUDE.md` §3 + §4; `docs/architecture/0001-bff-lite-decision-dtos.md` - `CLAUDE.md` §3 + §4; `docs/reference/architecture/0001-bff-lite-decision-dtos.md`
- `src/app/registratie/infrastructure/applications.adapter.ts` (+ its spec — the pattern - `src/app/registratie/infrastructure/applications.adapter.ts` (+ its spec — the pattern
to copy) to copy)
- The three offenders below - The three offenders below
+2 -2
View File
@@ -5,7 +5,7 @@ Phase: 1 — FP/DDD core
## Why ## Why
Docs (`docs/fp-tea-atomic-design.md`, ARCHITECTURE §2c) teach `createStore` as THE Docs (`docs/reference/fp-tea-atomic-design.md`, ARCHITECTURE §2c) teach `createStore` as THE
wiring, but the wizard pages hand-wire `signal(model)` + local `dispatch()` — juniors see wiring, but the wizard pages hand-wire `signal(model)` + local `dispatch()` — juniors see
two idioms and copy the wrong one. Machine naming also drifts: two idioms and copy the wrong one. Machine naming also drifts:
`change-request.machine.ts` exports bare `State`/`Msg`; `upload.machine.ts` exports `change-request.machine.ts` exports bare `State`/`Msg`; `upload.machine.ts` exports
@@ -14,7 +14,7 @@ two idioms and copy the wrong one. Machine naming also drifts:
## Read first ## Read first
- `src/app/shared/application/store.ts` (`createStore`) + its spec - `src/app/shared/application/store.ts` (`createStore`) + its spec
- `docs/fp-tea-atomic-design.md` Part on TEA-in-Angular - `docs/reference/fp-tea-atomic-design.md` Part on TEA-in-Angular
- All `*.machine.ts` files (6) and their UI wiring sites (wizard components/pages) - All `*.machine.ts` files (6) and their UI wiring sites (wizard components/pages)
## Decisions (pre-made, don't relitigate) ## Decisions (pre-made, don't relitigate)
+1 -1
View File
@@ -59,7 +59,7 @@ no spec despite "domain and pure logic must have a spec" (CLAUDE.md §5).
(`draft-sync.spec.ts`, `submit-change-request.spec.ts`). (`draft-sync.spec.ts`, `submit-change-request.spec.ts`).
- [x] `map3` removed (found in `shared/application/remote-data.ts`, not - [x] `map3` removed (found in `shared/application/remote-data.ts`, not
`shared/kernel/fp.ts` as the WP text guessed — updated the three docs that `shared/kernel/fp.ts` as the WP text guessed — updated the three docs that
mentioned it: CLAUDE.md, `docs/ARCHITECTURE.md`, `remote-data.mdx`). The mentioned it: CLAUDE.md, `docs/reference/architecture/ARCHITECTURE.md`, `remote-data.mdx`). The
`variant` input on `confirmation.component.ts` no longer exists — already `variant` input on `confirmation.component.ts` no longer exists — already
cleaned up before this WP ran; nothing to do. cleaned up before this WP ran; nothing to do.
- [x] CLAUDE.md rule added (`Conventions` — DatePipe in templates, `formatDatumNl` in - [x] CLAUDE.md rule added (`Conventions` — DatePipe in templates, `formatDatumNl` in
@@ -35,7 +35,7 @@ the list-family rationale to document.
## Read first ## Read first
- `docs/architecture/0003-*.md` (ADR-0003 — the token-bridge rationale this extends) - `docs/reference/architecture/0003-*.md` (ADR-0003 — the token-bridge rationale this extends)
- The audit's gap list (below) - The audit's gap list (below)
## Decisions (pre-made, don't relitigate) ## Decisions (pre-made, don't relitigate)
+3 -3
View File
@@ -41,7 +41,7 @@ Three app-level gaps close the WCAG story:
- `src/app/app.config.ts` + a new small `shared/` focus-on-navigation service - `src/app/app.config.ts` + a new small `shared/` focus-on-navigation service
- `eslint.config.mjs`, `package.json` (angular-eslint) - `eslint.config.mjs`, `package.json` (angular-eslint)
- Any template the new lint rules flag - Any template the new lint rules flag
- New `docs/wcag-checklist.md` - New `docs/reference/wcag-checklist.md`
- New `src/docs/a11y.mdx` — title `Foundations/Accessibility` - New `src/docs/a11y.mdx` — title `Foundations/Accessibility`
## Steps ## Steps
@@ -50,7 +50,7 @@ Three app-level gaps close the WCAG story:
focus lands on the new page's heading). focus lands on the new page's heading).
2. Add angular-eslint; enable the template rules on inline templates; plant a bad 2. Add angular-eslint; enable the template rules on inline templates; plant a bad
pattern, see it fail, remove it; fix real hits. pattern, see it fail, remove it; fix real hits.
3. `docs/wcag-checklist.md`: manual checks per page (dashboard, wizards, brief, login) — 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, keyboard walk & focus order, no traps, 200% zoom/reflow, NVDA or VoiceOver pass,
visible focus, error announcement; status columns (page × check). visible focus, error announcement; status columns (page × check).
4. `src/docs/a11y.mdx`: the layered approach — axe gate (WP-01) + template lint + play 4. `src/docs/a11y.mdx`: the layered approach — axe gate (WP-01) + template lint + play
@@ -75,7 +75,7 @@ Three app-level gaps close the WCAG story:
setup. Less code to hand-maintain, same coverage plus more. setup. Less code to hand-maintain, same coverage plus more.
- The dashboard's checklist pass surfaced a **real bug**: `aanvraag-block`'s warning - 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 `app-alert` (two `app-button` actions) overflows the viewport at 320px — its
`.feedback` flex row doesn't wrap. Documented in `docs/wcag-checklist.md` with the `.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 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 "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. WP's own deliverable. Flagged here so it isn't lost.
@@ -30,9 +30,9 @@ a real foundation to extend.
## Read first ## Read first
- `docs/architecture/0002-user-groups-and-bounded-contexts.md` (the `Principal` - `docs/reference/architecture/0002-user-groups-and-bounded-contexts.md` (the `Principal`
union, identity-vs-authorization split — see the deviation noted below) union, identity-vs-authorization split — see the deviation noted below)
- `docs/prd/0002-attribute-based-access-control.md` §5a, §6, §7, §9-P1 - `docs/project/prd/0002-attribute-based-access-control.md` §5a, §6, §7, §9-P1
- `backend/src/BigRegister.Api/Domain/Authorization/Authz.cs` (new — the single - `backend/src/BigRegister.Api/Domain/Authorization/Authz.cs` (new — the single
authorization helper) authorization helper)
- `backend/src/BigRegister.Api/Data/BriefStore.cs` (`Review` — now delegates its - `backend/src/BigRegister.Api/Data/BriefStore.cs` (`Review` — now delegates its
@@ -31,7 +31,7 @@ speculatively.
`static Dictionary` + `lock` `static Dictionary` + `lock`
- `backend/src/BigRegister.Api/Data/SeedData.cs` (current in-memory seed — becomes - `backend/src/BigRegister.Api/Data/SeedData.cs` (current in-memory seed — becomes
a first-run DB seed) a first-run DB seed)
- `docs/architecture/0001-bff-lite-decision-dtos.md` (confirm this WP doesn't touch - `docs/reference/architecture/0001-bff-lite-decision-dtos.md` (confirm this WP doesn't touch
the decision-DTO contracts — persistence is purely behind the existing store the decision-DTO contracts — persistence is purely behind the existing store
interfaces) interfaces)
@@ -14,11 +14,11 @@ WP-25, editor WP-26) reads what this WP serves.
## Read first ## Read first
- `docs/prd` — the Brief v2 PRD §2a/§3 (two axes, OrgTemplate model, invariants) - `docs/project/prd` — the Brief v2 PRD §2a/§3 (two axes, OrgTemplate model, invariants)
- `backend/src/BigRegister.Api/Data/BriefStore.cs` (store idiom + `BriefSeed`) - `backend/src/BigRegister.Api/Data/BriefStore.cs` (store idiom + `BriefSeed`)
- `backend/src/BigRegister.Api/Domain/Authorization/Authz.cs` (emit+enforce single source) - `backend/src/BigRegister.Api/Domain/Authorization/Authz.cs` (emit+enforce single source)
- `backend/src/BigRegister.Api/Data/AppDbContext.cs` (JSON-column precedent, WP-22) - `backend/src/BigRegister.Api/Data/AppDbContext.cs` (JSON-column precedent, WP-22)
- `docs/backlog/WP-18-abac-capability-spine.md` (how the capability spine works) - `docs/project/backlog/WP-18-abac-capability-spine.md` (how the capability spine works)
## Decisions (pre-made, don't relitigate) ## Decisions (pre-made, don't relitigate)
+1 -1
View File
@@ -15,7 +15,7 @@ domain model, `brief.machine.ts`, and every `BriefMsg` stay byte-identical.
- PRD Brief v2 §2b (fidelity note), §4, §10; the sample `voorbeeldbrief-inschrijving.pdf` - PRD Brief v2 §2b (fidelity note), §4, §10; the sample `voorbeeldbrief-inschrijving.pdf`
- `src/app/brief/ui/letter-composer/letter-composer.component.ts` (the `canEdit` pivot) - `src/app/brief/ui/letter-composer/letter-composer.component.ts` (the `canEdit` pivot)
- `src/app/brief/ui/letter-preview/letter-preview.component.ts` (rendering that migrates in) - `src/app/brief/ui/letter-preview/letter-preview.component.ts` (rendering that migrates in)
- `docs/backlog/WP-23-org-template-backend.md` (the `orgTemplate` on `BriefViewDto`) - `docs/project/backlog/WP-23-org-template-backend.md` (the `orgTemplate` on `BriefViewDto`)
## Decisions (pre-made, don't relitigate) ## Decisions (pre-made, don't relitigate)
@@ -12,7 +12,7 @@ same composition is archived with the brief, making sent letters immutable.
## Read first ## Read first
- PRD Brief v2 §2b, §8; `docs/backlog/WP-24-letter-canvas.md` (the `letter.css` contract) - PRD Brief v2 §2b, §8; `docs/project/backlog/WP-24-letter-canvas.md` (the `letter.css` contract)
- `backend/src/BigRegister.Api/Program.cs` — upload `content` endpoint (binary house - `backend/src/BigRegister.Api/Program.cs` — upload `content` endpoint (binary house
pattern: `.ExcludeFromDescription()` + hand-written FE fetch) pattern: `.ExcludeFromDescription()` + hand-written FE fetch)
- `src/app/shared/upload/upload.adapter.ts` (hand-written transport precedent) - `src/app/shared/upload/upload.adapter.ts` (hand-written transport precedent)
@@ -12,7 +12,7 @@ content a read-only sample). PRD Brief v2 §5, §7h.
## Read first ## Read first
- PRD Brief v2 §5, §7h; `docs/backlog/WP-23/24/25` (endpoints, canvas, proefbrief) - PRD Brief v2 §5, §7h; `docs/project/backlog/WP-23/24/25` (endpoints, canvas, proefbrief)
- `src/app/shared/application/access.store.ts` (`can('orgtemplate:edit')`) - `src/app/shared/application/access.store.ts` (`can('orgtemplate:edit')`)
- `.claude/skills/form-machine` — the house form idiom this editor follows - `.claude/skills/form-machine` — the house form idiom this editor follows
- `src/app/shared/ui/upload/single-upload` (logo upload reuse) - `src/app/shared/ui/upload/single-upload` (logo upload reuse)
@@ -22,7 +22,7 @@ that keep CLAUDE.md and the backlog truthful.
already covers all `/api/` calls, the new endpoints included), and already covers all `/api/` calls, the new endpoints included), and
`POST /brief/reset`. The demo script documents the mapping; no new interceptor `POST /brief/reset`. The demo script documents the mapping; no new interceptor
cases, no scenario code. cases, no scenario code.
- Demo script lives at `docs/prd/0003-brief-v2-demo-script.md` and follows the §6 - Demo script lives at `docs/project/prd/0003-brief-v2-demo-script.md` and follows the §6
choreography (compose → preview → switch sub-org seed → "two axes, one render"). choreography (compose → preview → switch sub-org seed → "two axes, one render").
- One e2e spec, not a suite: drafter composes on canvas → submit → approve → send - One e2e spec, not a suite: drafter composes on canvas → submit → approve → send
pins the org-template version; admin publishes → drafter canvas reflects it. pins the org-template version; admin publishes → drafter canvas reflects it.
@@ -31,10 +31,10 @@ that keep CLAUDE.md and the backlog truthful.
## Files ## Files
- `docs/prd/0003-brief-v2-demo-script.md` (new) - `docs/project/prd/0003-brief-v2-demo-script.md` (new)
- `e2e/brief-v2.spec.ts` (new) - `e2e/brief-v2.spec.ts` (new)
- story gap-fill where WP-24..27 left holes - story gap-fill where WP-24..27 left holes
- `docs/backlog/README.md` (statuses), `CLAUDE.md` (roles/routes touch-up) - `docs/project/backlog/README.md` (statuses), `CLAUDE.md` (roles/routes touch-up)
## Steps ## Steps
+1 -1
View File
@@ -10,7 +10,7 @@ professional logs in, sees their registration, and can apply for
re-registration — "herregistratie"). re-registration — "herregistratie").
> New to functional programming or The Elm Architecture? Start with the progressive > New to functional programming or The Elm Architecture? Start with the progressive
> learning guide [`fp-tea-atomic-design.md`](./fp-tea-atomic-design.md), which teaches > learning guide [`fp-tea-atomic-design.md`](../fp-tea-atomic-design.md), which teaches
> the concepts (with Elm ↔ this-app examples) and the recipes; this document is the > the concepts (with Elm ↔ this-app examples) and the recipes; this document is the
> reference deep-dive it points back to. > reference deep-dive it points back to.
+7 -7
View File
@@ -17,7 +17,7 @@ feature." A senior can skim Parts 1–4 and jump to **Part 5** (FP × atomic des
defined in plain words on first use and again in the **glossary** (Part 8). defined in plain words on first use and again in the **glossary** (Part 8).
This guide is the _teaching_ layer. For the reference deep-dives it points to This guide is the _teaching_ layer. For the reference deep-dives it points to
[`ARCHITECTURE.md`](./ARCHITECTURE.md) and [`ARCHITECTURE.md`](./architecture/ARCHITECTURE.md) and
[ADR-0001](./architecture/0001-bff-lite-decision-dtos.md) rather than repeating them. [ADR-0001](./architecture/0001-bff-lite-decision-dtos.md) rather than repeating them.
Every code snippet below is real code from this repo, with its file path. Every code snippet below is real code from this repo, with its file path.
@@ -127,7 +127,7 @@ Two kinds of type do most of the work:
The decisive move is choosing types so that **illegal states can't be written down**. The decisive move is choosing types so that **illegal states can't be written down**.
Compare three booleans (2³ = 8 combinations, most nonsense) with one union of the 4 real Compare three booleans (2³ = 8 combinations, most nonsense) with one union of the 4 real
states — see [`ARCHITECTURE.md` §2a](./ARCHITECTURE.md#2a-remotedata--one-value-instead-of-three-booleans) states — see [`ARCHITECTURE.md` §2a](./architecture/ARCHITECTURE.md#2a-remotedata--one-value-instead-of-three-booleans)
for the full `RemoteData` treatment and diagram. The wizard's own Model is the same for the full `RemoteData` treatment and diagram. The wizard's own Model is the same
idea (`herregistratie.machine.ts`): idea (`herregistratie.machine.ts`):
@@ -216,7 +216,7 @@ This app implements TEA with Angular **signals**. There is no extra state librar
important shape difference from textbook Elm: **state is per-wizard, not one global important shape difference from textbook Elm: **state is per-wizard, not one global
Model** — each flow (`herregistratie`, `intake`, `registratie`) has its own little Model** — each flow (`herregistratie`, `intake`, `registratie`) has its own little
store. Cross-page state that _must_ be shared lives in one root singleton store. Cross-page state that _must_ be shared lives in one root singleton
(`BigProfileStore`, see [`ARCHITECTURE.md` §2e](./ARCHITECTURE.md#2e-optimistic-update--rollback-and-shared-state-across-pages)). (`BigProfileStore`, see [`ARCHITECTURE.md` §2e](./architecture/ARCHITECTURE.md#2e-optimistic-update--rollback-and-shared-state-across-pages)).
### 4a. The store — TEA's runtime in ~10 lines ### 4a. The store — TEA's runtime in ~10 lines
@@ -424,7 +424,7 @@ Molecules compose atoms; organisms compose molecules — exactly like composing
functions, where the composite is still pure. `address-fields` is pure because the functions, where the composite is still pure. `address-fields` is pure because the
`form-field` and `text-input` it's built from are pure. Each atomic level only uses the `form-field` and `text-input` it's built from are pure. Each atomic level only uses the
level(s) below it (see the hierarchy diagram in level(s) below it (see the hierarchy diagram in
[`ARCHITECTURE.md` §1](./ARCHITECTURE.md#1-the-big-picture-three-contexts-four-layers)). [`ARCHITECTURE.md` §1](./architecture/ARCHITECTURE.md#1-the-big-picture-three-contexts-four-layers)).
### 5c. Pages / containers are the TEA runtime (the shell) ### 5c. Pages / containers are the TEA runtime (the shell)
@@ -474,7 +474,7 @@ Each property maps to a tangible benefit you can point at in this repo:
- **Illegal states won't compile.** `Submitting` carries `Valid` data and has no `errors` - **Illegal states won't compile.** `Submitting` carries `Valid` data and has no `errors`
field, so "submit with errors showing" is unwritable. A whole bug class disappears field, so "submit with errors showing" is unwritable. A whole bug class disappears
before runtime — contrast the 8-state boolean soup in before runtime — contrast the 8-state boolean soup in
[`ARCHITECTURE.md` §2a](./ARCHITECTURE.md#2a-remotedata--one-value-instead-of-three-booleans). [`ARCHITECTURE.md` §2a](./architecture/ARCHITECTURE.md#2a-remotedata--one-value-instead-of-three-booleans).
- **Pure presentational components.** `address-fields` is tested by inputs → DOM and - **Pure presentational components.** `address-fields` is tested by inputs → DOM and
reused in two call-sites (the registratie wizard and the change-request form) with no reused in two call-sites (the registratie wizard and the change-request form) with no
@@ -500,7 +500,7 @@ Each recipe follows the existing pattern and naming, and ends with the same remi
### Recipe A — Add an atomic component (atom / molecule / organism) ### Recipe A — Add an atomic component (atom / molecule / organism)
**When:** you genuinely need a new building block (not a one-off; reuse must earn it — **When:** you genuinely need a new building block (not a one-off; reuse must earn it —
see [CLAUDE.md §2](../CLAUDE.md)). see [CLAUDE.md §2](../../CLAUDE.md)).
**Where:** `shared/ui/` if generic; a context's `ui/` if domain-specific. Pick the level **Where:** `shared/ui/` if generic; a context's `ui/` if domain-specific. Pick the level
by composition: composes nothing → **atom**; composes atoms → **molecule**; composes by composition: composes nothing → **atom**; composes atoms → **molecule**; composes
@@ -634,7 +634,7 @@ _This is the same loop, again — now nested inside the wizard._
--- ---
_See also:_ [`ARCHITECTURE.md`](./ARCHITECTURE.md) (reference deep-dive on RemoteData, _See also:_ [`ARCHITECTURE.md`](./architecture/ARCHITECTURE.md) (reference deep-dive on RemoteData,
the store, parse-don't-validate, and the .NET backend seam) and the store, parse-don't-validate, and the .NET backend seam) and
[ADR-0001](./architecture/0001-bff-lite-decision-dtos.md) (the BFF-lite + decision-DTO [ADR-0001](./architecture/0001-bff-lite-decision-dtos.md) (the BFF-lite + decision-DTO
decision). Live demo: `/concepts` in the running app. decision). Live demo: `/concepts` in the running app.
+1473 -1026
View File
File diff suppressed because one or more lines are too long
@@ -21,7 +21,7 @@ type Err = Error | undefined;
* The dashboard data now comes from ONE screen-shaped ("BFF-lite") call that * The dashboard data now comes from ONE screen-shaped ("BFF-lite") call that
* returns registration + person + server-computed `decisions`. One request → one * returns registration + person + server-computed `decisions`. One request → one
* consistent snapshot, instead of stitching three independently loading/erroring * consistent snapshot, instead of stitching three independently loading/erroring
* resources together client-side. See docs/architecture/0001-bff-lite-decision-dtos.md. * resources together client-side. See docs/reference/architecture/0001-bff-lite-decision-dtos.md.
*/ */
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class BigProfileStore { export class BigProfileStore {
@@ -3,7 +3,7 @@
* *
* In production this is GENERATED from the OpenAPI/TypeSpec spec and served by our * In production this is GENERATED from the OpenAPI/TypeSpec spec and served by our
* own backend, which talks to the BRP behind an adapter. The frontend never sees * own backend, which talks to the BRP behind an adapter. The frontend never sees
* the BRP's own wire format. See docs/architecture/0001-bff-lite-decision-dtos.md. * the BRP's own wire format. See docs/reference/architecture/0001-bff-lite-decision-dtos.md.
* *
* "Geen adres bekend" is a first-class outcome (`gevonden: false`), not an error — * "Geen adres bekend" is a first-class outcome (`gevonden: false`), not an error —
* the wizard falls back to manual entry (PRD §7). Slice 1 ships only the happy * the wizard falls back to manual entry (PRD §7). Slice 1 ships only the happy
@@ -10,7 +10,7 @@
* In production these types are GENERATED from the OpenAPI/TypeSpec spec (one * In production these types are GENERATED from the OpenAPI/TypeSpec spec (one
* source of truth for both sides), and the `decisions` block is computed BY THE * source of truth for both sides), and the `decisions` block is computed BY THE
* BACKEND — never recomputed on the client. The frontend renders decisions; it * BACKEND — never recomputed on the client. The frontend renders decisions; it
* does not own the rules. See docs/architecture/0001-bff-lite-decision-dtos.md. * does not own the rules. See docs/reference/architecture/0001-bff-lite-decision-dtos.md.
* *
* One screen-shaped call replaces the previous three (BIG-register + BRP + …), * One screen-shaped call replaces the previous three (BIG-register + BRP + …),
* so the page always sees one consistent snapshot instead of three independently * so the page always sees one consistent snapshot instead of three independently
@@ -25,7 +25,7 @@ const meta: Meta<AanvraagBlockComponent> = {
parameters: { parameters: {
// Structural: app-aanvraag-block's host sits between the keuzelijst <ul> and its <li> // Structural: app-aanvraag-block's host sits between the keuzelijst <ul> and its <li>
// — axe's list/listitem rule needs them adjacent regardless of `display:contents`. // — axe's list/listitem rule needs them adjacent regardless of `display:contents`.
// WP-11 (CIBG markup fidelity) reworks this markup; see docs/backlog/WP-11-markup-fidelity.md. // WP-11 (CIBG markup fidelity) reworks this markup; see docs/project/backlog/WP-11-markup-fidelity.md.
a11y: { disable: true }, a11y: { disable: true },
}, },
}; };
@@ -15,7 +15,7 @@ const meta: Meta<ChoiceLinkComponent> = {
parameters: { parameters: {
// Structural: app-choice-link's host sits between the <ul> and its <li> — axe's // Structural: app-choice-link's host sits between the <ul> and its <li> — axe's
// list/listitem rule requires them adjacent regardless of `display:contents`. // list/listitem rule requires them adjacent regardless of `display:contents`.
// WP-11 (CIBG markup fidelity) reworks this markup; see docs/backlog/WP-11-markup-fidelity.md. // WP-11 (CIBG markup fidelity) reworks this markup; see docs/project/backlog/WP-11-markup-fidelity.md.
a11y: { disable: true }, a11y: { disable: true },
}, },
}; };
@@ -21,7 +21,7 @@ const meta: Meta<ChoiceListComponent> = {
}), }),
parameters: { parameters: {
// Structural: app-choice-link's host sits between the <ul> and its <li> — // Structural: app-choice-link's host sits between the <ul> and its <li> —
// fixed by the WP-11 markup rework. See docs/backlog/WP-11-markup-fidelity.md. // fixed by the WP-11 markup rework. See docs/project/backlog/WP-11-markup-fidelity.md.
a11y: { disable: true }, a11y: { disable: true },
}, },
}; };
@@ -14,7 +14,7 @@ const meta: Meta<TaskListComponent> = {
parameters: { parameters: {
// Structural: app-choice-link's host sits between the keuzelijst <ul> and its <li> // Structural: app-choice-link's host sits between the keuzelijst <ul> and its <li>
// — axe's list/listitem rule needs them adjacent regardless of `display:contents`. // — axe's list/listitem rule needs them adjacent regardless of `display:contents`.
// WP-11 (CIBG markup fidelity) reworks this markup; see docs/backlog/WP-11-markup-fidelity.md. // WP-11 (CIBG markup fidelity) reworks this markup; see docs/project/backlog/WP-11-markup-fidelity.md.
a11y: { disable: true }, a11y: { disable: true },
}, },
}; };
+2 -2
View File
@@ -30,7 +30,7 @@ what the ones below/above it can't.
`-desc`/`-error` in the right order; `alert.stories.ts` asserts `role="alert"` for `-desc`/`-error` in the right order; `alert.stories.ts` asserts `role="alert"` for
errors vs `role="status"` for info/ok/warning. These run as part of the same errors vs `role="status"` for info/ok/warning. These run as part of the same
`test-storybook:ci` gate as the axe checks, so a regression fails CI, not just a panel. `test-storybook:ci` gate as the axe checks, so a regression fails CI, not just a panel.
4. **Manual WCAG checklist** (`docs/wcag-checklist.md`) — what none of the above can see: 4. **Manual WCAG checklist** (`docs/reference/wcag-checklist.md`) — what none of the above can see:
tab order across a whole page, focus traps, 200%-zoom reflow, and how a real screen tab order across a whole page, focus traps, 200%-zoom reflow, and how a real screen
reader narrates a flow. A living per-page checklist, not a one-time audit — it already reader narrates a flow. A living per-page checklist, not a one-time audit — it already
caught a real bug (a dashboard alert overflowing at 320px) that no automated layer here caught a real bug (a dashboard alert overflowing at 320px) that no automated layer here
@@ -65,5 +65,5 @@ the same way (`withInMemoryScrolling`), both wired once in `app.config.ts` — n
fails it on a real axe violation. Both can be locally disabled — the lint rule via a fails it on a real axe violation. Both can be locally disabled — the lint rule via a
normal ESLint disable comment, axe via `parameters: { a11y: { disable: true } }` — but normal ESLint disable comment, axe via `parameters: { a11y: { disable: true } }` — but
only with a comment naming _why_ and a cross-reference to the WP expected to remove the only with a comment naming _why_ and a cross-reference to the WP expected to remove the
skip (see `docs/backlog/WP-13-cibg-gap-register.md`'s marker convention, reused here). skip (see `docs/project/backlog/WP-13-cibg-gap-register.md`'s marker convention, reused here).
Grep `a11y: { disable: true }` in `*.stories.ts` for the current list. Grep `a11y: { disable: true }` in `*.stories.ts` for the current list.
+1 -1
View File
@@ -106,7 +106,7 @@ Periodically we audit for near-duplicate blocks. Some collapse into one; a few *
similar but earn their separation. This table records the "don't merge these" verdicts so similar but earn their separation. This table records the "don't merge these" verdicts so
the next person doesn't spend an afternoon re-deciding. (Deliberate CIBG-specific deviations the next person doesn't spend an afternoon re-deciding. (Deliberate CIBG-specific deviations
live in [CIBG gaps](?path=/docs/foundations-cibg-gaps--docs); the FE⇄DS "same shape, different live in [CIBG gaps](?path=/docs/foundations-cibg-gaps--docs); the FE⇄DS "same shape, different
context" cases in [Layers](?path=/docs/foundations-layers--docs).) context" cases in [Domain-driven design](?path=/docs/foundations-domain-driven-design--docs).)
| Pair | Why kept separate | | Pair | Why kept separate |
| --- | --- | | --- | --- |
+75
View File
@@ -0,0 +1,75 @@
import { Meta } from '@storybook/addon-docs/blocks';
<Meta title="Foundations/BDD" />
# Behaviour-driven tests
Tests here read as **specifications of behaviour**, not checks of implementation. A test
says what the system *does* — in the domain's own words — so a failing test names a broken
behaviour, and the suite doubles as living documentation. This is the BDD half of the
[Testing strategy](?path=/docs/foundations-testing-strategy--docs) (which owns *what to
test, by layer*); BDD owns *how each test is phrased and scoped*.
## Three rules
### 1. `describe` = the subject, `it` = one observable behaviour
The `describe()` block names the unit under test; each `it()` states a single behaviour in
**declarative present tense** — the implicit subject is "it". No `should`, no
Given/When/Then ceremony: present-tense declaration already reads as a spec.
```ts
describe('parsePostcode', () => {
it('normalises to "1234 AB" (uppercase, single space, trimmed)', () => { … });
it('rejects malformed input', () => { … });
});
```
Read top-to-bottom it *is* the spec: "parsePostcode — normalises to 1234 AB; rejects
malformed input."
### 2. One behaviour per test
A test asserts **one behaviour**, not one `expect()`. Several assertions that pin down the
*same* behaviour belong together; assertions about *different* behaviours belong apart.
| Keep together (one behaviour) | Split apart (separate behaviours) |
| --- | --- |
| A `Result`'s `.ok` then its `.value` | The `ok` branch **and** the `err` branch of a transition |
| A whole-object `toEqual` | An invalid-input case **and** a valid-input case |
| A loop asserting one rule over many inputs | Two independent state transitions |
| A truth-table (`draft` → true, `approver` → false) of one rule | An authorization check **and** a rendering check |
A title that needs `/`, `;`, "then" or "and" to join two behaviours is the smell — split it,
and each half gets its own present-tense name.
### 3. Speak the ubiquitous language (the DDD tie-in)
Test names use the **domain vocabulary**, not technical jargon — the same words as the
[bounded contexts](?path=/docs/foundations-domain-driven-design--docs): a *behandelaar*
drafts, a *beoordelaar* approves, a *herregistratie* is *ingediend*. The test name is
readable by someone who knows the domain but not the code.
```ts
it('drafter cannot approve or reject even when submitted', …);
it('confirmed dutch proficiency requires taalvaardigheid proof', …);
```
## How it fits TDD & DDD
- **TDD** — the loop is red → green → refactor: write the behaviour as a failing `it`, make
it pass, then clean up. Because tests describe behaviour (not internals), a refactor that
preserves behaviour keeps them green. Pure domain logic is tested directly — no `TestBed`
(see [Testing strategy](?path=/docs/foundations-testing-strategy--docs)).
- **DDD** — behaviour is expressed in the ubiquitous language, so the spec and the code
share one vocabulary. Domain rules (reducers, value-object parsers, policies) are the
richest specs; the wire boundary is tested as "rejects malformed input", the UI as
Storybook stories.
## Where to look
Canonical behaviour specs in the repo: `registratie/domain/value-objects/postcode.spec.ts`
(parser behaviour), `registratie/domain/registratie-wizard.machine.spec.ts` (one transition
per test), and backend `AuthzTests.cs` (rule truth-tables). The
[Testing strategy](?path=/docs/foundations-testing-strategy--docs) page maps which layer
gets which kind of test.
+1 -1
View File
@@ -7,7 +7,7 @@ import * as AsyncStories from '../app/shared/ui/async/async.stories';
The components in this library are the _view_. Behind them, three small functional tools do The components in this library are the _view_. Behind them, three small functional tools do
the heavy lifting — all so that **illegal states can't be represented**. This page is the the heavy lifting — all so that **illegal states can't be represented**. This page is the
Storybook front door; the full narrative lives in `docs/fp-tea-atomic-design.md`, and a Storybook front door; the full narrative lives in `docs/reference/fp-tea-atomic-design.md`, and a
side-by-side "before/after" runs at the app's **`/concepts`** route. side-by-side "before/after" runs at the app's **`/concepts`** route.
## 1. `RemoteData<E,T>` — async has four states, not a boolean soup ## 1. `RemoteData<E,T>` — async has four states, not a boolean soup
+7 -5
View File
@@ -1,12 +1,14 @@
import { Meta } from '@storybook/addon-docs/blocks'; import { Meta } from '@storybook/addon-docs/blocks';
<Meta title="Foundations/Layers & Contexts" /> <Meta title="Foundations/Domain-Driven Design" />
# Layers & contexts # Domain-driven design: bounded contexts & layers
This sidebar is laid out to **be** the architecture, not just document it: **Foundations** This project is **domain-driven**: the code is organised first by **bounded context**
(this curriculum) → **Design System** (reusable, domain-free) → **Domein** (the six DDD (a business capability with its own language) and then by **layer** inside each context,
contexts). If a component lives under a context's `ui/`, it's in Domein; everything else with dependencies pointing inward. The Storybook sidebar is laid out to **be** that
architecture, not just document it: **Foundations** (this curriculum) → **Design System**
(reusable, domain-free) → **Domein** (the six DDD contexts). If a component lives under a context's `ui/`, it's in Domein; everything else
in `shared/ui`/`shared/layout` is Design System. See [Atomic design](?path=/docs/foundations-atomic-design--docs) in `shared/ui`/`shared/layout` is Design System. See [Atomic design](?path=/docs/foundations-atomic-design--docs)
for the Atoms → Molecules → Organisms → Templates ladder inside Design System. for the Atoms → Molecules → Organisms → Templates ladder inside Design System.
+35
View File
@@ -0,0 +1,35 @@
import { Meta } from '@storybook/addon-docs/blocks';
<Meta title="Foundations/Overview" />
# Foundations
The **why** behind this codebase, as a short curriculum. Each page is a condensed,
cross-linked take on one idea; the long-form source lives in `docs/reference/`
(see the repo's `docs/README.md`). Read them in roughly this order.
## Architecture & domain
- [Domain-driven design](?path=/docs/foundations-domain-driven-design--docs) — bounded
contexts + layers, dependencies pointing inward (the folder structure *is* the architecture).
- [Atomic design](?path=/docs/foundations-atomic-design--docs) — Atoms → Molecules →
Organisms → Templates; a new page is composition, not new building blocks.
## Functional core
- [FP in the UI](?path=/docs/foundations-fp-in-the-ui--docs) — the three functional tools behind the view.
- [State machines (TEA)](?path=/docs/foundations-state-machines-tea--docs) — every form/wizard as Model → Msg → pure `reduce`.
- [RemoteData & Async](?path=/docs/foundations-remotedata-async--docs) — the four async states as one value.
- [Parse, don't validate](?path=/docs/foundations-parse-dont-validate--docs) — narrow untrusted `unknown` at the boundary into domain types.
## Design system
- [Design tokens](?path=/docs/foundations-design-tokens--docs) — semantic `--rhc-*` tokens; no hand-written colours.
- [CIBG gap register](?path=/docs/foundations-cibg-gap-register--docs) — where we diverge from the CIBG Huisstijl (ADR-0003).
## Quality & process
- [Accessibility](?path=/docs/foundations-accessibility--docs) — four layered a11y tools, each catching a different bug class.
- [Testing strategy](?path=/docs/foundations-testing-strategy--docs) — what to test, by layer grain.
- [BDD](?path=/docs/foundations-bdd--docs) — how each test is phrased and scoped: one behaviour, in the domain's language.
- [Internationalization](?path=/docs/foundations-internationalization--docs) — `$localize` for every user-visible string; the locale seam.
+3 -1
View File
@@ -6,7 +6,9 @@ import { Meta } from '@storybook/addon-docs/blocks';
Tests follow the same grain as the architecture: **push the logic down to where it's pure, Tests follow the same grain as the architecture: **push the logic down to where it's pure,
test it there directly, and keep the layers above thin.** No single tool covers everything, test it there directly, and keep the layers above thin.** No single tool covers everything,
so each layer gets the cheapest test that catches its class of bug. so each layer gets the cheapest test that catches its class of bug. This page owns *what to
test, by layer*; how each test is **phrased and scoped** — one behaviour, in the domain's
language — is [BDD](?path=/docs/foundations-bdd--docs).
## What gets tested where ## What gets tested where