Merge refactor/machine-wiring-in-application — RD-30..33, RD-36, RD-37, RD-39

Closes the readable-codebase arc's phase 5. RD-37 removed the last five axe
suppressions by making the keuzelijst row the <li>. RD-30 archived the two
finished backlogs. RD-31, RD-32, RD-33 and RD-36 repaired the documents that
describe the refactored tree.

RD-34 and RD-35 stay open. Both are marked optional in the backlog.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
eho
2026-09-09 16:21:30 +02:00
co-authored by Claude Opus 5
195 changed files with 1003 additions and 216 deletions
+3 -3
View File
@@ -20,11 +20,11 @@ The repo splits docs by kind (`docs/README.md`). Pick one — don't write all th
_decision/pattern_ (a tradeoff, a "we chose X over Y"). One format, no drift:
`# ADR-NNNN — Title`, then `Status: Accepted · Date: YYYY-MM-DD`, then
Problem / Options / Decision / Consequences.
- **WP** — `docs/project/backlog/WP-NN-<slug>.md`. Tracked work; use the template at the
bottom of `docs/project/backlog/README.md`.
- **WP** — `docs/project/archive/backlog/WP-NN-<slug>.md`. Tracked work; use the template at the
bottom of `docs/project/archive/backlog/README.md`.
Then **add the index row**: `docs/README.md` table for a reference doc/ADR,
`docs/project/backlog/README.md` for a WP. A doc with no index row is invisible.
`docs/project/archive/backlog/README.md` for a WP. A doc with no index row is invisible.
## Adjusted feature → update what already describes it
+3 -3
View File
@@ -62,7 +62,7 @@ Work through that checklist, keeping the GREEN gate below passing at every step.
re-target it at an arbitrary new context, so it's deleted alongside `registratie`, along
with its three wiring lines in `shell.component.ts` (import, `imports:` entry, template tag).
- The `dashboard` route is **not** deleted even though it currently imports
`@registratie/ui/dashboard.page` — too much else hardcodes `/dashboard` (login's post-auth
`@overzicht/ui/overzicht.page` — too much else hardcodes `/dashboard` (login's post-auth
redirect, `authGuard`'s fallback, header nav/logo, breadcrumb trail, several stories/specs).
The script rewrites its `loadComponent` to point at the freshly scaffolded `--context` page
instead (a `TODO(create-frontend)` stopgap landing page, not a real overview).
@@ -85,8 +85,8 @@ Work through that checklist, keeping the GREEN gate below passing at every step.
placeholder path and creates an empty `public/<name>-huisstijl/` — it cannot generate a
real house style. Vendor your CSS there, then re-point the `--rhc-*` bridge in
`src/styles.scss` (ADR-0003 pattern: bridge, don't rewrite tokens), then `npm run check:tokens`.
- `docs/project/backlog/` WPs, PRDs, and memory-specific docs — new portal, new backlog
(keep `docs/project/backlog/README.md`'s WP process/template if you like the workflow).
- `docs/project/archive/backlog/` WPs, PRDs, and memory-specific docs — new portal, new backlog
(keep `docs/project/archive/backlog/README.md`'s WP process/template if you like the workflow).
`docs/reference/scaffolding.md` also names `BigRegister.Api` in prose — update by hand.
- `e2e/*.spec.ts` (`smoke.spec.ts`, `brief-v2.spec.ts`, `error-state.spec.ts`): full
BIG-register user-flow tests (BSN login → registration wizard → submission assertions).
+10 -8
View File
@@ -5,13 +5,13 @@ description: Add a shared UI building block (atom, molecule, organism) with its
# UI component (atom / molecule / organism)
First: check `shared/ui/` and `shared/layout/` — a new page should be composition of
existing blocks. Only add a block when nothing fits.
First: check `libs/shared/src/ui/` and `libs/shared/src/layout/` — a new page should be
composition of existing blocks. Only add a block when nothing fits.
## Rules
- **Folder = atomic layer**: `shared/ui/` atoms → molecules → organisms;
`shared/layout/` templates. Each level only uses levels below.
- **Folder = atomic layer**: `libs/shared/src/ui/` atoms → molecules → organisms;
`libs/shared/src/layout/` templates. Each level only uses levels below.
- Standalone component, **English name** (shared = language-agnostic), signal
`input()`s only, `inject()` over constructor DI.
- **Atoms are thin wrappers over CIBG Huisstijl (Bootstrap 5.2) classes** (`btn`,
@@ -21,7 +21,8 @@ existing blocks. Only add a block when nothing fits.
- **Tokens only** — `var(--rhc-*)` / `var(--app-*)`, never hardcoded colors
(`npm run check:tokens` fails the build; escape hatch: `token-ok` marker + reason).
- **No hardcoded Dutch** in shared components — expose copy as `input()`s with
`$localize` defaults; the domain caller supplies the text (see `shared/ui/async`).
`$localize` defaults; the domain caller supplies the text (see
`libs/shared/src/ui/molecules/async`).
- Components with content-projected slots export a spread constant so callers import
one thing: `export const ASYNC = [AsyncComponent, AsyncLoadedDirective, …] as const;`
@@ -41,9 +42,10 @@ UI test surface.
## Worked examples
- Atom: `src/app/shared/ui/button/` — typed variant API over `btn` classes.
- Molecule: `src/app/shared/ui/async/` — slot directives, localizable input defaults, spread constant.
- Template: `src/app/shared/layout/wizard-shell/` — the canonical wizard outline.
- Atom: `libs/shared/src/ui/atoms/button/` — typed variant API over `btn` classes.
- Molecule: `libs/shared/src/ui/molecules/async/` — slot directives, localizable input
defaults, spread constant.
- Template: `libs/shared/src/layout/wizard-shell/` — the canonical wizard outline.
## Verify
+2 -2
View File
@@ -28,5 +28,5 @@ plop-templates/
backend/
# Agent prompts — their exact wording is the input, reflowing markdown edits the prompt
docs/project/refactor-backlog-setup/agents/
docs/project/refactor-backlog-setup/refactor-backlog/final-prompts/
docs/project/archive/refactor-backlog-setup/agents/
docs/project/archive/refactor-backlog-setup/refactor-backlog/final-prompts/
+30 -13
View 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
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
persist to a SQLite file via EF Core (WP-22) — `docs/project/backlog/WP-22-durable-persistence.md`.
persist to a SQLite file via EF Core (WP-22) — `docs/project/archive/backlog/WP-22-durable-persistence.md`.
**Monorepo (WP-67):** two Angular projects share one backend + one shared library —
`apps/ssp` (Zorgverlener self-service, this doc's main subject) and `apps/behandelportal`
@@ -72,7 +72,7 @@ so this doesn't depend on a human remembering to run `/model` at the right momen
`git status`/`grep`, verifying a file exists. No Edit/Write access.
Delegate to the matching agent only when the _current_ session isn't already on that
model — don't add indirection for its own sake. `docs/project/backlog/README.md`'s
model — don't add indirection for its own sake. `docs/project/archive/backlog/README.md`'s
session protocol is the worked example of this in practice.
## The decisions (non-negotiable working agreements)
@@ -81,10 +81,11 @@ session protocol is the worked example of this in practice.
`apps/<app>/src/app/<context>/<layer>/` for an app-local context; `libs/<lib>/src/<layer>/`
for a cross-app library (WP-67). Two apps today: `apps/ssp` (Zorgverlener self-service —
contexts `auth`, `registratie`, `herregistratie`, `brief` (letter-composition teaching
slice), `showcase` (teaching page, not a feature; **sanctioned** to read every context in
its own app — nothing imports it)) and `apps/behandelportal` (Behandelaar backoffice,
ADR-0002 — contexts `auth`, `behandeling`). Two cross-app libraries: `libs/shared` (the
contexts `auth`, `overzicht` (the portal home; composes `registratie`'s dashboard sections
plus its own cross-context nav sections), `registratie`, `herregistratie`, `brief`
(letter-composition teaching slice), `showcase` (teaching page, not a feature; **sanctioned**
to read every context in its own app — nothing imports it)) and `apps/behandelportal`
(Behandelaar backoffice, ADR-0002 — contexts `auth`, `behandeling`). Two cross-app libraries: `libs/shared` (the
design system + kernel + generated API client — no business logic) and `libs/beheer` (the
admin/stamdata context, identical for both apps today — WP-67 folded a silently-diverging
duplicate copy back into one). `auth` is deliberately **not** shared even though today it's
@@ -106,10 +107,11 @@ depend on `libs/beheer` either — it stays the base). `ui`/`layout` never impor
lint-enforced (per app, since each app is cruised against its own tsconfig — WP-67's
`.dependency-cruiser.base.js` + one thin `.dependency-cruiser.<app>.js` per app). An app
may not import the other app's source directly. Cross-context only
`herregistratie → registratie → libs/shared|beheer`, `auth → libs/shared|beheer`,
`brief → libs/shared|beheer` (ssp); `behandeling → libs/shared|beheer`, `auth →
libs/shared|beheer` (behandelportal). Imports use aliases as direction statements:
`@shared/* @beheer/* @auth/* @registratie/* @herregistratie/* @brief/*` (ssp) —
`overzicht → registratie → libs/shared|beheer`, `herregistratie → registratie →
libs/shared|beheer`, `auth → libs/shared|beheer`, `brief → libs/shared|beheer` (ssp);
`behandeling → libs/shared|beheer`, `auth → libs/shared|beheer` (behandelportal). Imports use
aliases as direction statements:
`@shared/* @beheer/* @auth/* @overzicht/* @registratie/* @herregistratie/* @brief/*` (ssp) —
`@shared/* @beheer/* @auth/* @behandeling/*` (behandelportal); each app's own
`tsconfig.json` declares its full map (the root `tsconfig.json` intentionally has no
`paths` — see its comment). `domain/` imports nothing from Angular.
@@ -129,6 +131,18 @@ the design system does the visuals. (Where CIBG lacks a class — e.g. `skeleton
`// CIBG-GAP EXTENSION:` marker; see ADR-0003. `alert` is **not** such a case: it wraps the
vendored `.feedback feedback-*` classes.)
**The step-component contract.** A wizard step follows the same rule as
`address-fields.component.ts`: values in, events out, no internal state. Three clauses:
1. **Inputs down.** A step reads its data only from `input()`s the container passes it.
2. **One narrow output up.** A step emits one specific event, not the container's whole
`dispatch`.
3. **`dispatch` is never passed down.** The container owns the Model and decides what a
step's event means; a step never calls `dispatch` itself.
Corollary: a step gets **no** story of its own. The wizard's own story already mounts every
step, because it seeds the machine.
### 3. State: make illegal states unrepresentable
Default reflex — **if you're about to add a second/third boolean to track state,
@@ -136,7 +150,7 @@ model a discriminated union instead.** Three tools, all in `libs/shared/src/appl
- **`RemoteData<E,T>`** (`remote-data.ts`) — `Loading | Empty | Failure{error} | Success{value}`.
Combine sources with `map`/`map2`/`andThen` (Failure > Loading > Success).
Render it via the `<app-async>` molecule (`libs/shared/src/ui/async`) — one of four
Render it via the `<app-async>` molecule (`libs/shared/src/ui/molecules/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
@@ -242,7 +256,7 @@ organism doesn't get its own `Organisms/` bucket).
(`` $localize`:@@context.key:Tekst` ``). Source locale is `nl`; a second locale is a
translation file, not a code change (the seam). Shared/English components must **not**
hardcode Dutch — expose copy as `input()`s with localizable defaults; the domain caller
supplies the text (see `libs/shared/src/ui/async`). Format-validation messages in
supplies the text (see `libs/shared/src/ui/molecules/async`). Format-validation messages in
`domain/value-objects/` stay co-located but are still `$localize`-wrapped.
- **Forms = one idiom.** Any form with validation or submission uses a `*.machine.ts`
(Model/Msg/reduce) + value objects + a `submit-*` command returning `Result` — the
@@ -272,7 +286,10 @@ organism doesn't get its own `Organisms/` bucket).
- Prettier; `.editorconfig`. tsconfig: `noImplicitReturns`,
`noPropertyAccessFromIndexSignature`, `noFallthroughCasesInSwitch`, `isolatedModules`.
- **Enforced, not just hoped-for:** `npm run lint` (`eslint.config.mjs`, scoped to
`{apps,libs}/**`) fails the build on `any`; `npm run dep:check`
`{apps,libs}/**`) fails the build on `any`; the same config's `max-lines` rule caps every
`{apps,libs}/**/*.{page,component,section,step}.ts` file at 250 lines
(`skipBlankLines: true`, `skipComments: true`). 250 is reachable, not a style-guide
default — the dashboard page lands at 42 lines. `npm run dep:check`
(`.dependency-cruiser.base.js` + one `.dependency-cruiser.<app>.js` per app, WP-67) fails
on illegal imports — `domain/` importing Angular, a context importing "upward" (the
`herregistratie → registratie → shared`, `auth → shared` direction), an app importing the
+1 -1
View File
@@ -66,7 +66,7 @@ Admin pages (`/beheer/*`, `/brief/huisstijl`) need the `admin` role — see
| 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/backlog/README.md](docs/project/backlog/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) |
@@ -263,10 +263,13 @@ export class HerregistratieWizardComponent {
constructor() {
// An explicit seed (stories/tests) wins; otherwise resume the backend draft
// (`?aanvraag=<id>`) or start fresh. Persistence is the draftSync controller's job.
const seeded = this.seed();
queueMicrotask(() =>
seeded !== initial ? this.dispatch({ tag: 'Seed', state: seeded }) : this.draftSync.resume(),
);
// Read `seed()` INSIDE the microtask: Angular binds inputs after the constructor
// runs, so an eager read here always returns the `initial` default.
queueMicrotask(() => {
const seeded = this.seed();
if (seeded !== initial) this.dispatch({ tag: 'Seed', state: seeded });
else void this.draftSync.resume();
});
}
/** Reset the wizard to a fresh, empty start. */
@@ -26,14 +26,30 @@ const buitenlandJa: IntakeState = {
scholingThreshold: 1000,
};
/** Mount with a seed and let the constructor's microtask apply it. */
async function mountSeeded(state: IntakeState) {
TestBed.configureTestingModule({
providers: [provideHttpClient(), provideApiClient()],
});
const fixture = TestBed.createComponent(IntakeWizardComponent);
fixture.componentRef.setInput('seed', state);
await Promise.resolve(); // the seed is applied in a queueMicrotask
fixture.detectChanges();
return fixture;
}
describe('IntakeWizardComponent', () => {
it('renders each field group as its own grey <fieldset>', () => {
TestBed.configureTestingModule({
providers: [provideHttpClient(), provideApiClient()],
});
const fixture = TestBed.createComponent(IntakeWizardComponent);
fixture.componentInstance.dispatch({ tag: 'Seed', state: buitenlandJa });
fixture.detectChanges();
// Regression: the constructor must read `seed()` INSIDE its microtask. Angular binds
// inputs after the constructor runs, so an eager read silently yields the `initial`
// default and every seeded story renders step 1 instead of the state it asked for.
it('honours the seed input', async () => {
const fixture = await mountSeeded(buitenlandJa);
expect(fixture.componentInstance.state()).toEqual(buitenlandJa);
});
it('renders each field group as its own grey <fieldset>', async () => {
const fixture = await mountSeeded(buitenlandJa);
const fieldsets: HTMLElement[] = Array.from(
fixture.nativeElement.querySelectorAll('form.form-horizontal fieldset'),
@@ -212,10 +212,13 @@ export class IntakeWizardComponent {
constructor() {
// An explicit seed (stories/tests) wins; otherwise resume the backend draft
// (`?aanvraag=<id>`) or start fresh. Persistence is the draftSync controller's job.
const seeded = this.seed();
queueMicrotask(() =>
seeded !== initial ? this.dispatch({ tag: 'Seed', state: seeded }) : this.draftSync.resume(),
);
// Read `seed()` INSIDE the microtask: Angular binds inputs after the constructor
// runs, so an eager read here always returns the `initial` default.
queueMicrotask(() => {
const seeded = this.seed();
if (seeded !== initial) this.dispatch({ tag: 'Seed', state: seeded });
else void this.draftSync.resume();
});
// Apply the server-owned threshold into machine state as it arrives. Track
// only the policy value; untrack the dispatch (it reads the state signal
// internally, which would otherwise make this effect loop on its own write).
@@ -1,15 +1,15 @@
import { Component } from '@angular/core';
import { PageShellComponent } from '@shared/layout/page-shell/page-shell.component';
import { MijnAanvragenSection } from '@registratie/ui/dashboard/mijn-aanvragen.section';
import { WatMoetIkRegelenSection } from '@registratie/ui/dashboard/wat-moet-ik-regelen.section';
import { MijnRegistratieSection } from '@registratie/ui/dashboard/mijn-registratie.section';
import { SpecialismenSection } from '@registratie/ui/dashboard/specialismen.section';
import { MijnAanvragenSection } from '@registratie/ui/overzicht-secties/mijn-aanvragen.section';
import { WatMoetIkRegelenSection } from '@registratie/ui/overzicht-secties/wat-moet-ik-regelen.section';
import { MijnRegistratieSection } from '@registratie/ui/overzicht-secties/mijn-registratie.section';
import { SpecialismenSection } from '@registratie/ui/overzicht-secties/specialismen.section';
import { WatWiltUDoenSection } from './wat-wilt-u-doen.section';
import { BeheerLinksSection } from './beheer-links.section';
/** Page: "Mijn overzicht" — the portal home, following the NL Design System "Mijn
omgeving" pattern. Composition only: each section below answers its own data
question (own store, own async state) — four sections stay in `registratie/ui/dashboard/`
question (own store, own async state) — four sections stay in `registratie/ui/overzicht-secties/`
(they render registratie data), two live here (cross-context navigation). */
@Component({
selector: 'app-overzicht-page',
@@ -17,57 +17,17 @@ const meta: Meta<AanvraagBlockComponent> = {
title: 'Domein/Registratie/Aanvraag Block',
component: AanvraagBlockComponent,
decorators: [applicationConfig({ providers: [provideRouter([])] })],
render: (args) => ({
props: args,
// A row is an <li> — the keuzelijst styling needs the real list context.
template: `<ul class="keuzelijst__list"><app-aanvraag-block [aanvraag]="aanvraag" /></ul>`,
}),
parameters: {
// 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`.
// WP-11 (CIBG markup fidelity) reworks this markup; see docs/project/backlog/WP-11-markup-fidelity.md.
a11y: { disable: true },
},
// A Concept renders as a CIBG melding (block element), not a keuzelijst <li> — no <ul>
// wrapper. Production agrees: mijn-aanvragen.section.ts renders this block for concepten
// only, outside any list.
render: (args) => ({ props: args, template: `<app-aanvraag-block [aanvraag]="aanvraag" />` }),
};
export default meta;
type Story = StoryObj<AanvraagBlockComponent>;
// One story per status variant; the block renders its own body + actions.
// A Concept renders as a CIBG melding (block element), not a keuzelijst <li> — no <ul> wrapper.
// The whole template sits inside `@if (aanvraag().status.tag === 'Concept')`, so this is
// the only status that renders anything. Submitted/resolved aanvragen render through
// application-link, which has its own stories.
export const Concept: Story = {
args: { aanvraag: { ...base, status: { tag: 'Concept', stepIndex: 1, stepCount: 3 } } },
render: (args) => ({ props: args, template: `<app-aanvraag-block [aanvraag]="aanvraag" />` }),
};
export const InBehandelingAuto: Story = {
args: {
aanvraag: {
...base,
status: { tag: 'InBehandeling', referentie: 'BIG-2026-456789', manual: false },
},
},
};
export const InBehandelingManual: Story = {
args: {
aanvraag: {
...base,
type: 'registratie',
status: { tag: 'InBehandeling', referentie: 'BIG-2026-456789', manual: true },
},
},
};
export const Goedgekeurd: Story = {
args: { aanvraag: { ...base, status: { tag: 'Goedgekeurd', referentie: 'BIG-2026-456789' } } },
};
export const Afgewezen: Story = {
args: {
aanvraag: {
...base,
type: 'herregistratie',
status: {
tag: 'Afgewezen',
referentie: 'BIG-2026-456789',
reden: 'Aanvraag afgewezen: geen gewerkte uren geregistreerd.',
},
},
},
};
@@ -66,14 +66,6 @@ export const MetTaken: Story = {
],
}),
],
parameters: {
// 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`.
// Same pre-existing gap as task-list.stories.ts and choice-list.stories.ts. WP-11
// (CIBG markup fidelity) reworks this markup; see
// docs/project/backlog/WP-11-markup-fidelity.md.
a11y: { disable: true },
},
};
export const NietsOpenstaand: Story = {
decorators: [
@@ -233,10 +233,13 @@ export class RegistratieWizardComponent {
constructor() {
// An explicit seed (stories/tests) wins; otherwise resume from the backend draft
// (`?aanvraag=<id>`), or start fresh. Persistence is the draftSync controller's job.
const seeded = this.seed();
queueMicrotask(() =>
seeded !== initial ? this.dispatch({ tag: 'Seed', state: seeded }) : this.draftSync.resume(),
);
// Read `seed()` INSIDE the microtask: Angular binds inputs after the constructor
// runs, so an eager read here always returns the `initial` default.
queueMicrotask(() => {
const seeded = this.seed();
if (seeded !== initial) this.dispatch({ tag: 'Seed', state: seeded });
else void this.draftSync.resume();
});
// Prefill the address from the BRP lookup as it arrives. Track only the facade's
// parsed prefill signal; untrack the dispatch (it reads the state signal, which
// would otherwise make this effect loop on its own write). Don't clobber
+1 -1
View File
@@ -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
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
`docs/project/backlog/WP-22-durable-persistence.md`.
`docs/project/archive/backlog/WP-22-durable-persistence.md`.
## Run
@@ -152,7 +152,7 @@ public static class LetterHtml
// 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/project/backlog/WP-25-letter-preview-html.md) until it finds `public/letter.css`. docker-compose.yml bind-mounts `./public` under the
// docs/project/archive/backlog/WP-25-letter-preview-html.md) until 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.
private static string FindLetterCss()
{
+6
View File
@@ -12,6 +12,8 @@ services:
command: dotnet run --project src/BigRegister.Api --urls http://+:5000
environment:
- ASPNETCORE_ENVIRONMENT=Development
# ponytail: Server GC makes one heap per CPU (22 here); workstation GC makes one.
- DOTNET_gcServer=0
volumes:
# ':z' relabels for SELinux (Fedora/RHEL); harmless on other hosts.
# WP-22: no separate volume needed for the SQLite file — `dotnet run` sets
@@ -41,6 +43,8 @@ services:
# switcher actually switches. ponytail: `--no-fund --loglevel=error` silences npm 11 noise.
command: sh -c "npm ci --no-fund --loglevel=error && npx ng build ssp --configuration development --localize && node scripts/serve-i18n.mjs"
environment:
# ponytail: without a cgroup limit Node sizes its heap from host RAM (16 GB).
- NODE_OPTIONS=--max-old-space-size=2048
- PORT=4200
- API_PROXY_TARGET=http://api:5000
- APP_DIST_ROOT=dist/ssp/browser
@@ -57,6 +61,8 @@ services:
working_dir: /app
command: sh -c "npm ci --no-fund --loglevel=error && npx ng build behandelportal --configuration development --localize && node scripts/serve-i18n.mjs"
environment:
# ponytail: without a cgroup limit Node sizes its heap from host RAM (16 GB).
- NODE_OPTIONS=--max-old-space-size=2048
- PORT=4201
- API_PROXY_TARGET=http://api:5000
- APP_DIST_ROOT=dist/behandelportal/browser
+11
View File
@@ -0,0 +1,11 @@
# Archive — finished backlogs
The trees in this directory are historical. Every ticket in `backlog/` and
`refactor-backlog-setup/` carries `Status: done`. `SHOWCASE-ROADMAP.md` was superseded before
either arc started.
Git holds the rest — `git log --follow` on any file here shows its full history, including the
work that produced it.
The live plan is `docs/project/readable-codebase/`. Product reference material is
`docs/project/prd/`.
@@ -1,6 +1,6 @@
# Showcase roadmap — superseded
**This roadmap is superseded by [`docs/project/backlog/`](backlog/README.md)** (2026-07-02).
**This roadmap is superseded by [`docs/project/archive/backlog/`](backlog/README.md)** (2026-07-02).
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
@@ -59,7 +59,7 @@ The review's remaining findings are listed under "Follow-ups" and are **not** th
## Read first
- `CLAUDE.md` §"The decisions" #3 (make illegal states unrepresentable) and #4 (BFF-lite)
- [ADR-0001 — BFF-lite + decision DTOs](../../reference/architecture/0001-bff-lite-decision-dtos.md)
- [ADR-0001 — BFF-lite + decision DTOs](../../../reference/architecture/0001-bff-lite-decision-dtos.md)
- `backend/src/BigRegister.Api/Data/ApplicationStore.cs` (the `Aanvraag` entity, the store's
lock discipline, `AanvraagStatusTag`, `RecordBesluit`)
- `backend/src/BigRegister.Api/Contracts/Mappers.cs` (`ToStatusDto` — the logic to move)
@@ -39,7 +39,7 @@ re-validates as authority) is unenforced for the one rule it was written to illu
- `backend/src/BigRegister.Api/Domain/Intake/IntakePolicy.cs` (the corrected doc-comment,
WP-68)
- [ADR-0001 — BFF-lite + decision DTOs](../../reference/architecture/0001-bff-lite-decision-dtos.md)
- [ADR-0001 — BFF-lite + decision DTOs](../../../reference/architecture/0001-bff-lite-decision-dtos.md)
§"config value"
- `apps/ssp/src/app/herregistratie/domain/intake.machine.ts` (`lageUren`, `scholingGevolgd`,
`punten` — the wizard's existing FE-side rule and its answers)

Some files were not shown because too many files have changed in this diff Show More