ehoandClaude Opus 5 97d9697a84 docs(readable-codebase): scaffold the arc backlog (RD-01)
Convert the approved readable-codebase plan into the ticket protocol this
repo already uses for long arcs (docs/project/backlog/README.md, 75 work
packages driven to done through it).

Adds docs/project/readable-codebase/ with:
- README.md — the ledger: session protocol, GREEN definition, restart
  recovery, the agent loop, and an Order table of 35 tickets with their
  dependencies and status.
- PLAN.md — the design record, committed so the repository holds the
  complete state. A fresh session needs nothing outside it.

Two properties make the arc restart-safe. Each ticket updates its own
Status line and its README row in the same commit as its code, so the
ledger and git log cannot desync. And the plan lives in the repository,
so recovery is three commands and needs no prior context.

Records one correction found while scaffolding: the 22 abandoned agent
checkouts under .claude/worktrees/ are live registered git worktrees, not
orphaned directories. RD-15 must use `git worktree remove` and verify each
branch tip is an ancestor of main first. Spot-checked four; all are
already merged via 637d500.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 16:09:00 +02:00
2026-09-04 15:17:10 +02:00

BIG-register Portals — Atomic Design POC

A two-app Angular monorepo showing how atomic design plus domain-driven boundaries make a frontend cheap to build, reuse and extend. The domain is the BIG-register (the Dutch register of healthcare professionals, run by CIBG): a citizen self-service portal and a case-handler backoffice, sharing one design system and one backend.

It is styled with the CIBG Huisstijl (a customized Bootstrap 5.2 build, vendored — ADR-0003) and built around one idea: make illegal states unrepresentable — in the UI's async states, in the domain types, and in the tests.

Demo / POCno real login (DigiD is faked) and synthetic seed data. But the business rules and data are served by a real ASP.NET Core backend (backend/) through a generated typed client, so the BFF + DDD design is demonstrable rather than hand-waved. A system-font stack stands in for the licensed Rijksoverheid font, and a text wordmark for the logo.

New here? Run npm run storybook and open Foundations → Learning Path — a paced, hands-on three-day route through the codebase, written for a strong programmer who is new to frontend functional programming. For everything else, docs/README.md is the documentation index.


Quick start

Everything at once — API, both portals:

docker compose up
# self-service portal  → http://localhost:4200
# behandelportal       → http://localhost:4201
# API + Swagger        → http://localhost:5000/swagger

This does not start OpenZaak. The BFF uses a local, in-memory case store instead. To run against a real OpenZaak, see backend/openzaak/README.md.

Or run the pieces yourself:

npm install
npm start                     # self-service portal  → :4200 (proxies /api → backend)
npm run start:behandelportal  # case-handler portal  → :4201

# in another terminal:
cd backend && dotnet run --project src/BigRegister.Api   # API → :5000/swagger

npm run storybook             # the design system + Foundations curriculum
npm run e2e                   # Playwright — starts the backend and app itself

Self-service flow: Login → Dashboard → Mijn gegevens → Registreren → Herregistratie → Intake → Brief. Behandelportal: Login → Werkvoorraad → Beoordeling (approve / reject / ask for more). Admin pages (/beheer/*, /brief/huisstijl) need the admin role — see roles and access.


Where to find things

I want to… Go to
learn the codebase from scratch npm run storybookFoundations → Learning Path
find any document docs/README.md — the full index
understand the architecture ARCHITECTURE.md
know why a decision was made the ADRs — BFF-lite, contexts, huisstijl, stamdata, ZGW, test data
work on the backend / BFF backend/README.md
run OpenZaak locally backend/openzaak/README.md
see what shipped, or pick up work docs/project/backlog/README.md
build a feature the house way .claude/skills/ — invocable recipes (new-feature, form-machine, …)
know the import rules dependencies.md — enforced by dep:check
work on this repo as an AI agent CLAUDE.md

Repo map

Path What lives there
apps/ssp/ Zorgverlener self-service portal (:4200)
apps/behandelportal/ Behandelaar backoffice (:4201)
libs/shared/ Design system, kernel, generated API client, Storybook Foundations docs
libs/beheer/ Admin/stamdata context, used identically by both apps
backend/ ASP.NET Core BFF (.NET 10, EF Core/SQLite), OpenZaak seam
e2e/ Playwright specs
docs/ reference/ (how + why) and project/ (backlog, PRDs)
public/ Vendored CIBG Huisstijl + assets
scripts/ CI gate, drift checks, generators

Commands

npm run ci          # ← run this before pushing: the whole gate, exactly what CI runs
Task Command
Run npm start, npm run start:behandelportal, docker compose up
Test npm test, npm run e2e, npm run test-storybook (axe on every story)
Check npm run lint, typecheck, dep:check, check:tokens, check:seam
Build npm run build, npm run build-storybook
Generate npm run gen:api (typed client), npm run gen (plop: value object, form machine, context)
Docs npm run storybook, npm run dep:graph, npm run gen:behaviour-spec

npm run ci chains lint, typecheck, dependency boundaries, formatting, token and seam drift checks, all four test projects, both localized builds, the backend suite, and the generated-artifact drift gates. The full script list is in package.json; CLAUDE.md explains the traps.


Where atomic design pays off

Two orthogonal axes, and that is the point. On disk, code is grouped by ownership — bounded context, then layer (domain/application/infrastructure/ui/, dependencies pointing inward). In Storybook, the same components are grouped by atomic level — story titles put them under Design System/Atoms|Molecules|Organisms|Templates. So libs/shared/src/ui/ is a flat folder of 26 components, and the atomic ladder lives in the sidebar where you actually browse it. See Atomic design and Domain-driven design in Foundations.

Reuse is the payoff. button, form-field, async, page-shell, site-header appear on essentially every screen across both apps. Change one, every screen follows.

A new page is composition, not new components. The branching intake wizard — the most complex flow in the app — needed exactly one new atom (radio-group) and one new organism (intake-wizard). Everything else was already there.

Theming is one stylesheet and a token bridge. libs/shared/styles.scss maps the app's semantic --rhc-* vocabulary onto CIBG/--bs-* values, so components reference tokens, never colours. Re-point the bridge to re-theme both apps with no component changes (ADR-0003). npm run check:tokens fails the build on a hardcoded colour.


Try it: dev affordances

Append ?scenario= to any data page to force an async state — the states are mutually exclusive by construction, via the <app-async> molecule:

URL What you see
/dashboard real data
/dashboard?scenario=slow skeletons for ~2.5s, then data
/dashboard?scenario=loading the loading state, held open
/dashboard?scenario=empty "geen gegevens" empty state
/dashboard?scenario=error error message + Opnieuw proberen (retry)

Append ?role=drafter|approver|admin to switch the dev role stand-in and unlock the admin pages. Both toggles are dev-only — neither interceptor is wired into production builds.


Tech notes

  • Angular 22 — standalone components, signals, resource(), native control flow, view transitions. No NgRx: shared state is a root singleton store with a pure reducer.
  • Backend — ASP.NET Core (.NET 10), EF Core/SQLite for applications, documents, the brief and the audit trail; BRP/DUO reference data stays in-memory seeded. Screen-shaped ("BFF-lite") endpoints return server-computed decisions the frontend renders rather than recomputes (ADR-0001). Cases can be sourced from OpenZaak/ZGW behind the same seam (ADR-0005).
  • Typed client — NSwag-generated from the backend's OpenAPI doc (npm run gen:api); CI fails on drift.
  • Boundaries are enforced, not hoped fordep:check fails the build if domain/ imports Angular, a context imports upward, or an app reaches into the other app.
  • i18n — every user-facing string is $localize-wrapped with a stable id (source locale nl). ng build --localize builds both nl and en, and i18nMissingTranslation: "error" fails the build if a string ships untranslated.
  • Dependencies — the shipped bundle audits clean (npm audit --omit=dev: 0 vulnerabilities). .npmrc sets legacy-peer-deps=true because Storybook's peer range lags Angular 22. Never run npm audit fix --force — it downgrades Angular 22 → 21.

Deliberately out of scope

Real auth/DigiD, real BRP/DUO upstreams, a production-grade database, NgRx, licensed RO/Rijks fonts and logo. The i18n build seam is proven, but the en translation is demo-quality and locale is a build-time choice, not a runtime switch.

S
Description
No description provided
Readme
5.5 MiB
Languages
TypeScript 61.6%
C# 24.2%
MDX 6.7%
JavaScript 2.2%
Shell 1.9%
Other 3.4%