ehoandClaude Opus 5 637d500c96
CI / changes (push) Successful in 12s
CI / lint (push) Successful in 2m45s
CI / frontend (push) Failing after 11m9s
CI / backend (push) Successful in 2m22s
CI / e2e (push) Successful in 3m25s
CI / semgrep (push) Successful in 1m11s
CI / api-client-drift (push) Successful in 1m55s
CI / storybook-a11y (push) Failing after 15m10s
Merge refactor/adr-c-006-shared-route-guards — RB-01..RB-33 + 4 ADR-fixes
Closes the CD refactor backlog (docs/project/refactor-backlog-setup/refactor-backlog/99-backlog.md).
All 33 code tickets and the four gated ADR-fixes (ADR-C-001, ADR-C-003,
ADR-C-007, ADR-C-009) are merged, one commit per ticket, across six CD
batches plus the ADR-fix batch. npm run ci is green after every merge in
the arc, each verified independently.

Highlights: RB-01/02 fixed a BSN leak in the persisted audit trail and an
unauthorized document-content endpoint. RB-09/13 landed Session -> Principal
per ADR-0002. RB-12 added a route-table authz gate as a CI safety net.
RB-19 reordered the backend's 940-line Program.cs into reads-then-writes,
verified as a pure move by comparing every (route, gate, handler) triple
before and after. RB-24..30 moved libs/shared/upload into its proper
layers and made every layer testable. RB-31 found and fixed a real
ADR-0006 violation: two tests asserted a wizard state the real reducer
cannot produce.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 13:48: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

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%