Add registratie wizard, BFF dashboard-view, contracts/value-objects, and architecture docs
Checkpoint of in-progress work: the registration wizard (address prefill, DUO diploma lookup, policy questions), decision-DTO contracts, parse-don't- validate value objects, infrastructure adapters, plus CLAUDE.md and the architecture/ADR docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
123
CLAUDE.md
Normal file
123
CLAUDE.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# CLAUDE.md
|
||||
|
||||
Agent guide for this repo. The *why* lives in `docs/ARCHITECTURE.md` and
|
||||
`docs/architecture/0001-bff-lite-decision-dtos.md`; this file is the *rules*.
|
||||
When a decision below and those docs disagree, the docs win — update this file.
|
||||
|
||||
POC of a Dutch BIG-register self-service portal (healthcare professionals log in,
|
||||
view their registration, apply for re-registration). Angular 22, standalone,
|
||||
signals. No real backend/auth — static mock JSON + fake timers.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
npm start # ng serve → http://localhost:4200
|
||||
npm test # vitest
|
||||
npm run build # ng build (must stay green)
|
||||
npm run storybook # component library by atomic layer
|
||||
```
|
||||
|
||||
`.npmrc` sets `legacy-peer-deps=true` (Storybook's peer range lags Angular 22).
|
||||
Do not run `npm audit fix --force` — it downgrades Angular 22→21. Dev-only
|
||||
advisories are pinned via `package.json` `overrides`; the shipped bundle audits clean.
|
||||
|
||||
## The decisions (non-negotiable working agreements)
|
||||
|
||||
### 1. DDD: contexts then layers, dependencies point inward
|
||||
`src/app/<context>/<layer>/`. Contexts: `shared`, `auth`, `registratie`,
|
||||
`herregistratie`, `showcase` (teaching page, not a feature).
|
||||
|
||||
| Layer | Job | Angular allowed? |
|
||||
|---|---|---|
|
||||
| `domain/` | business rules + data types | **No — pure TS.** Has `.spec.ts` |
|
||||
| `application/` | coordinate state/tasks (stores, commands) | yes (signals) |
|
||||
| `infrastructure/` | where data comes from (HTTP adapters) | yes (HTTP) |
|
||||
| `contracts/` | wire DTOs (the FE⇄BE seam) | no |
|
||||
| `ui/` | how it looks (components, pages) | yes |
|
||||
|
||||
**Dependencies only point inward**: `ui → application → domain`; everyone may use
|
||||
`shared`; never the reverse. Cross-context only `herregistratie → registratie → shared`,
|
||||
`auth → shared`. Imports use aliases as direction statements: `@shared/* @auth/*
|
||||
@registratie/* @herregistratie/*`. `domain/` imports nothing from Angular.
|
||||
|
||||
### 2. Atomic design: folder = layer
|
||||
`shared/ui` atoms → molecules → organisms; `shared/layout` templates (`shell`,
|
||||
`page-shell`); context `ui/` pages. Each level only uses levels below. A new page
|
||||
should be **composition of existing blocks** — adding building blocks is the
|
||||
exception, not the default. Atoms are thin wrappers over Utrecht/RHC CSS classes;
|
||||
we own only a small typed `input()` API, the design system does the visuals.
|
||||
|
||||
### 3. State: make illegal states unrepresentable
|
||||
Default reflex — **if you're about to add a second/third boolean to track state,
|
||||
model a discriminated union instead.** Three tools, all in `shared/application`:
|
||||
|
||||
- **`RemoteData<E,T>`** (`remote-data.ts`) — `Loading | Empty | Failure{error} | Success{value}`.
|
||||
Combine sources with `map`/`map2`/`map3`/`andThen` (Failure > Loading > Success).
|
||||
Render it via the `<app-async>` molecule (`shared/ui/async`) — one of four
|
||||
templates, mutually exclusive by construction. Default loading spinner/skeleton
|
||||
is delay-gated (~250ms) so fast connections don't flash.
|
||||
- **Elm-style store** (`store.ts` → `createStore(initial, reduce)`) — all state in
|
||||
one Model; change only by `dispatch(msg)` → **pure** `reduce(model, msg)`. Models
|
||||
are tagged unions (see `herregistratie.machine.ts`, `intake.machine.ts`). Templates
|
||||
send messages, never mutate.
|
||||
- **`Result<E,T>` + value objects** ("parse, don't validate") — raw input becomes a
|
||||
branded type only via a parser returning `Result` (`registratie/domain/value-objects/`:
|
||||
`Postcode`, `Uren`, `BigNummer`). Once you hold the type, never re-check it.
|
||||
|
||||
**Derive, don't store** what you can compute — e.g. the wizard's visible steps are
|
||||
`visibleSteps(answers)`, not a stored field (`intake.machine.ts`).
|
||||
|
||||
**Side effects stay out of the reducer.** A *command* (`application/submit-*.ts`)
|
||||
does the HTTP, then dispatches a message describing the outcome. Reducer = "what the
|
||||
new state is"; command = "go do it, then say what happened."
|
||||
|
||||
**Shared cross-page state = one root singleton.** Stores are `providedIn: 'root'`
|
||||
(`BigProfileStore`, `SessionStore`). That single instance is the shared state — no
|
||||
NgRx, no extra lib. Optimistic update pattern: `begin*` (flip pending) →
|
||||
`confirm*` (clear + `resource.reload()`) / `rollback*` (undo).
|
||||
|
||||
### 4. BFF-lite + decision DTOs (ADR-0001)
|
||||
`infrastructure/` is the **only** layer that touches the network — the
|
||||
anti-corruption boundary. Each screen gets one screen-shaped endpoint returning a
|
||||
decision-enriched DTO; **the FE renders decisions, it does not recompute business
|
||||
rules.** Per rule, pick: *decision flag* (server computes the boolean — e.g.
|
||||
herregistratie eligibility) or *config value* (server sends threshold, FE applies
|
||||
for instant feedback, server re-validates as authority — e.g. scholing threshold).
|
||||
FE keeps only **format** validation, never as authority.
|
||||
|
||||
DTO lives in `contracts/`; a hand-written `parse*`/`toDomain` in `infrastructure/`
|
||||
validates the untrusted shape and maps DTO → domain. Wiring a real .NET backend
|
||||
touches only `infrastructure/` + `contracts/` (see ARCHITECTURE §6). Server-owned
|
||||
rules stay in `domain/*.policy.ts` as reference impl + unit test, marked server-owned,
|
||||
but the FE doesn't call them.
|
||||
|
||||
### 5. Testing
|
||||
Vitest. Co-locate `*.spec.ts` next to the unit. **Domain and pure logic must have a
|
||||
spec** (reducers, combinators, `visibleSteps`, parsers, boundary `parse*` adapters).
|
||||
Test the pure function directly — no Angular TestBed for domain. UI is exercised via
|
||||
Storybook stories (`*.stories.ts` co-located, titled `Layer/Name`, a11y addon on),
|
||||
not heavy component tests.
|
||||
|
||||
## Conventions
|
||||
- Standalone components only; no NgModules. Signal inputs (`input()`), `inject()` over
|
||||
constructor DI (constructor only for `effect()`/template-ref injection).
|
||||
- Angular-native control flow `@if/@for`; native `httpResource` for fetching;
|
||||
`withViewTransitions()` for page transitions (header/footer have stable
|
||||
`view-transition-name`, excluded from the fade).
|
||||
- Routes: lazy `loadComponent`, persistent `ShellComponent` parent, `canActivate:
|
||||
[authGuard]` on protected routes (`app.routes.ts`).
|
||||
- Theming is one import — `src/styles.scss` pulls the RHC palette; no hand-written theme.
|
||||
- Scenario toggle: `?scenario=slow|loading|empty|error` on data pages
|
||||
(`scenario.interceptor.ts`) to see every async state.
|
||||
- Prettier; `.editorconfig`. tsconfig: `noImplicitReturns`,
|
||||
`noPropertyAccessFromIndexSignature`, `noFallthroughCasesInSwitch`, `isolatedModules`.
|
||||
|
||||
## Adding a feature (recipe)
|
||||
Domain first (types + pure rules + spec, no Angular) → infrastructure (adapter:
|
||||
`httpResource` or command returning `Result`) → application (store if shared state;
|
||||
union + pure reduce) → UI last (compose `shared/ui` atoms, wrap async in `<app-async>`,
|
||||
dispatch messages). Worked example: the intake wizard (`herregistratie/`).
|
||||
|
||||
## Out of scope (POC, don't build unprompted)
|
||||
Real auth/DigiD, real backend, i18n, NgRx, licensed Rijkshuisstijl font/logo,
|
||||
runtime DTO validation on every endpoint, multi-tab session sync, OpenAPI codegen.
|
||||
Reference in New Issue
Block a user