Two backlog trees are complete: `docs/project/backlog/` (75 files, every WP done) and `docs/project/refactor-backlog-setup/` (the arc before it). Move both under `docs/project/archive/` with `git mv`, so history stays intact through `git log --follow`. `SHOWCASE-ROADMAP.md` moves with them, because it points at the now-archived backlog README. Add `docs/project/archive/README.md`. It states that these trees are historical and names the two directories that are still live. Repoint every inbound reference named in RD-30's Files table: CLAUDE.md, the root README, both backend READMEs, `LetterHtml.cs`, `a11y.mdx`, the `document-feature` and `new-ssp` skills, and the readable-codebase PLAN, README, and RD-19 ticket. Fix two upward-relative links inside the moved WP files (WP-68, WP-69) that gained a directory level and would otherwise break. Repoint `.prettierignore`'s two agent-prompt exclusions to their new path, so prettier keeps leaving those files' exact wording alone. Mark RD-30 done and check off its acceptance criteria; flip its README row to done. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
177 lines
10 KiB
Markdown
177 lines
10 KiB
Markdown
# 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 / POC** — **no 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`](docs/README.md)** is
|
|
> the documentation index.
|
|
|
|
---
|
|
|
|
## Quick start
|
|
|
|
Everything at once — API, both portals:
|
|
|
|
```bash
|
|
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](backend/openzaak/README.md).
|
|
|
|
Or run the pieces yourself:
|
|
|
|
```bash
|
|
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](docs/reference/roles-and-access.md).
|
|
|
|
---
|
|
|
|
## Where to find things
|
|
|
|
| I want to… | Go to |
|
|
| --------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
| learn the codebase from scratch | `npm run storybook` → **Foundations → Learning Path** |
|
|
| find any document | **[docs/README.md](docs/README.md)** — the full index |
|
|
| understand the architecture | [ARCHITECTURE.md](docs/reference/architecture/ARCHITECTURE.md) |
|
|
| know _why_ a decision was made | [the ADRs](docs/reference/architecture/) — BFF-lite, contexts, huisstijl, stamdata, ZGW, test data |
|
|
| work on the backend / BFF | [backend/README.md](backend/README.md) |
|
|
| run OpenZaak locally | [backend/openzaak/README.md](backend/openzaak/README.md) |
|
|
| see what shipped, or pick up work | [docs/project/archive/backlog/README.md](docs/project/archive/backlog/README.md) |
|
|
| build a feature the house way | [`.claude/skills/`](.claude/skills/) — invocable recipes (`new-feature`, `form-machine`, …) |
|
|
| know the import rules | [dependencies.md](docs/reference/architecture/dependencies.md) — enforced by `dep:check` |
|
|
| work on this repo as an AI agent | [CLAUDE.md](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
|
|
|
|
```bash
|
|
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](libs/shared/docs/atomic-design.mdx) and
|
|
[Domain-driven design](libs/shared/docs/layers.mdx) 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 for** — `dep: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.
|