Runnable `npm run gen:value-object` / `gen:form-machine` (plop) that scaffold the two
pure-TS house patterns with a co-located spec: a branded value object + parseX (mirrors
postcode/bsn), and an Elm-style form/wizard machine (Draft/Valid/Errors + Editing/
Submitting/Submitted/Failed union + initial/pure reduce/assertNever). Prompts take
context + PascalCase name (positional-arg bypass); a post-action reminds to add the
English target for the generated $localize id. Templates in plop-templates/ (prettier-
ignored). Skills (value-object, form-machine) point at the generators. ui-component +
bff-endpoint stay skill-driven (Angular {{}} / backend + gen:api).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a language switcher matching the CIBG Taal-instellen pattern: a <nav> region
(sr-only heading + aria-label) with an endonym link per locale (lang/hreflang, the
active one aria-current + non-link), mounted right after the skip link in the shell.
Compile-time $localize means each locale is its own bundle under /<locale>/, so the
switch is a full navigation to the sibling bundle — active locale read from the baked
base href. Pure localeLinks() (+spec) builds path-preserving targets. Since `ng serve`
serves nl-only at /, add `npm run serve:i18n` (localized build + a tiny static server
with per-locale SPA fallback) so the switch is demoable. +story.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Anti-drift snippets: `?raw` isn't supported by Angular's esbuild build, so real code
is exposed via // #region showcase:<name> markers in source (registration/remote-data/
postcode/change-request.machine/intake.machine) → scripts/gen-snippets.mjs → committed
snippets.generated.ts, with a CI drift gate so the shown code is the shipped code. The
/concepts page renders the 5 real snippets (union/fold/parse/machine/steps) with a source
caption + a tiny dependency-free highlightTs (+spec); deliberately-wrong illustrations stay
authored. Teaching motion: active state node pops on transition + parse result animates in
(existing reduced-motion-safe toolkit). Generated file prettier-ignored (like api-client).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Enable coverage via the @angular/build:unit-test builder's first-class options: a
`coverage` configuration on the test target (v8 provider, text-summary/html/lcov,
excludes for specs/stories/contracts/generated client) + `npm run test:coverage`.
Report-only (no thresholds) — visibility first. CI's test step now runs test:coverage
(strict superset: fails on a failing test, never on coverage). Baseline ~71% stmts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adopt dependency-cruiser as the single declarative source for bounded-context +
atomic-layer boundaries, replacing the per-context no-restricted-imports blocks that
had to be hand-copied (and had left herregistratie uncovered). `.dependency-cruiser.js`
encodes context direction (everyone→shared, herregistratie→registratie, showcase→*),
domain-purity, contracts-import-nothing, ui↛infrastructure, ApiClient confinement, and
no-circular. `npm run dep:check` enforces (wired into ci-local.sh + the frontend CI job);
`npm run dep:graph` emits a committed mermaid context×layer graph. ESLint slimmed to
no-explicit-any + template a11y. Docs + new-context skill updated to the single source.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three fixes so Gitea CI is green and stays that way:
- **ng build --localize**: `messages.en.xlf` (hand-maintained, matched by id) had drifted
~93 units behind the source since WP-24 — `beheer.*`, `brief.canvas.*`, `brief.diff.*`,
`orgTemplate.*`, `brief.besluit.*`, etc. With `i18nMissingTranslation: "error"` that fails
the frontend job. Added English `<target>`s for every missing unit (interpolation `<x/>`
tags preserved); both nl + en bundles build clean.
- **docker npm noise**: the web container's `npm ci` now runs `--no-fund --loglevel=error`,
so `docker compose up` boots without npm 11's deprecation / allow-scripts / funding
warnings (verified in the running container: 0 warning lines). The underlying Angular
webpack-builder deprecation is upstream; migrating to @angular/build is out of scope.
- **local CI check**: `scripts/ci-local.sh` + `npm run ci` mirror the CI jobs (lint,
format:check, check:tokens, test, build --localize, audit, backend format+test,
api-client drift; `--full` adds storybook-a11y) so a red build is caught before pushing.
Opt-in pre-push hook at scripts/githooks/pre-push. Documented in CLAUDE.md.
Verified: `npm run ci` passes end-to-end; storybook-a11y 169/169 (the local parallel-worker
timeouts were the docker stack starving chromium, not a regression).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- fix: wire remark-gfm into addon-docs so GFM pipe tables in *.mdx render
(previously raw text in cibg-gaps/layers/atomic-design docs)
- add src/docs/i18n.mdx (Foundations/Internationalization): the $localize
locale seam + how to test languages without coupling to copy
- add src/docs/testing.mdx (Foundations/Testing strategy): per-layer spec
matrix, house style, Storybook a11y gate, GREEN gate
- add .claude/skills/test-strategy skill
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
angular.json gains an i18n block (sourceLocale nl, en translation file) and
an `en` build/serve configuration with i18nMissingTranslation: "error" so a
new $localize string without an English unit fails the build, not silently
falls back. CI now runs `ng build --localize` to build both locales every
run. Verified end-to-end, not just "the build succeeded": the nl bundle
ships "Inloggen met DigiD", the en bundle ships "Log in with DigiD".
Incidental: prettier/compodoc regen noise in docs/wcag-checklist.md,
src/docs/a11y.mdx, documentation.json from the same working session.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a happy-path spec (login → dashboard → registratie wizard, including
a real identity-document upload → real submit) and a degraded-path spec
(?scenario=error → <app-async> error slot → retry), both driving the real
app against the real .NET backend, plus a CI job that boots both.
Writing the retry spec surfaced a real bug: AsyncComponent's retry() only
reloads a [resource]-fed instance, so every real page (all [data]-fed via
a store's RemoteData) had a silently no-op retry button. Added a
retryClicked output and wired it on the dashboard's two async blocks.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds route-change focus management (new page's h1, afterNextRender) plus
scroll-position restoration wired once in app.config.ts; angular-eslint's
templateAccessibility bundle linting every inline template via
processInlineTemplates (verified firing with a planted violation, one real
hit fixed in rich-text-editor); docs/wcag-checklist.md and Foundations/
Accessibility MDX tying the four a11y layers (axe, lint, play tests,
manual checklist) together. The checklist pass already earned its keep —
it found a real 320px overflow in aanvraag-block's warning alert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Move the guard to scripts/check-tokens.sh; regex now catches hex +
rgb()/hsl() (was hex-only) across ALL src/app components (was three
ui/layout dirs). `token-ok` marker suppresses justified false positives;
px stays out of scope (documented in the script).
- Zero exclusions: debug-state's dark code-editor palette moves to
--app-devpanel-* tokens in styles.scss (the one exempt file), dropping its
--exclude hole.
- Tokenize remaining hits: site-footer border via color-mix; three brief
border widths via --rhc-border-width-* (new --rhc-border-width-lg: 3px).
Verified: planted violation fails the guard; GREEN + test-storybook:ci.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Turn the interactive Storybook a11y addon into a build gate:
- @storybook/test-runner + axe-playwright over the static build
(.storybook/test-runner.ts reads the a11y tags from story context)
- test-storybook / test-storybook:ci scripts; storybook-a11y CI job
- triage: escape-hatch a11y.disable on stories whose display:contents
wrapper splits <ul>/<li> or <dl>/<dt>/<dd> (structural, deferred to
WP-11/WP-12, each with justification + cross-ref)
- fix trivial violations: footer/wizard-shell contrast, text-input label,
wizard stories missing provideApiClient
Verified: broken story fails the gate; 133 stories pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The portal now adheres to the CIBG design system (designsystem.cibg.nl) — a customized
Bootstrap 5.2 build — replacing the Rijkshuisstijl-Community / Utrecht theme. See ADR-0003.
- Vendor @cibg/huisstijl@3.22.0 under public/cibg-huisstijl/, loaded via a <link> in
index.html (Storybook serves it via staticDirs). Drop the two @rijkshuisstijl-community deps.
- Token bridge in styles.scss: redefine the app's ~54 --rhc-* tokens onto CIBG/--bs-* values,
so components keep referencing tokens (no 300+ site rewrite). System-font stack; licensed
RO/Rijks text fonts intentionally not shipped.
- Re-skin every shared atom to Bootstrap/CIBG classes (btn, form-control, form-check-*,
table, breadcrumb, …) keeping their input() APIs. alert is hand-rolled (CIBG drops .alert);
local .card/.badge renamed to avoid Bootstrap collisions.
- Domain pages: drop stray rhc-*/utrecht-* classes; registration-table → table table-striped.
Verified: build green, check:tokens OK, lint clean, 174 tests pass, build-storybook OK,
and the served build loads the vendored CSS (200, .btn-primary present).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Domain reference data already lives in the backend (per ADR-0001); the only
residual "move" was ~90 inlined Dutch UI strings with no i18n layer.
- Wire @angular/localize ($localize) — Angular first-party, no third-party lib
- Pin the pattern on shared/ui/async: Dutch fallbacks → language-agnostic
input()s with localizable $localize defaults (English-shared-UI rule)
- CLAUDE.md: drop i18n (now in scope) + stale "real backend"/"OpenAPI codegen"
(both already shipped); document the $localize convention
Bulk string sweep deferred to Step 2 to avoid touching ~36 files twice.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the authoritative business rules off the frontend into a real backend,
realising the BFF-lite + decision-DTO design (ADR-0001) that until now lived
only in static mock JSON.
Backend (backend/):
- ASP.NET Core (.NET 10) minimal API, contract-first, Swagger UI at /swagger.
- DDD Domain/ rules layer: profession derivation + applicable policy questions
(DiplomaRules), herregistratie eligibility + reason (HerregistratieRule),
scholing threshold (IntakePolicy), submit rejections + reference generation
(SubmissionRules). In-memory seeded data, ProblemDetails (RFC 7807) errors.
- 27 xUnit tests: rule units + endpoint integration incl. BRP no-address and
DUO not-found fallbacks and 422 submit paths.
Frontend (only infrastructure/ + contracts/ change, as the architecture promised):
- NSwag-generated typed client (api-client.ts), routed through Angular HttpClient
via a small fetch adapter so the ?scenario= interceptor still applies.
- GET adapters use resource({ loader: client.x }); submit commands call the client
and map ProblemDetails -> err. The hardcoded uren==0 / manual-diploma rules are
deleted (now server-side). Domain, stores, UI and format validators unchanged.
- Deleted the now-dead public/mock/*.json.
Tooling/docs:
- npm start proxies /api -> backend; npm run gen:api regenerates the client;
docker compose up runs both (bind mounts use :z for SELinux/Fedora).
- backend/README.md walkthrough: adding a policy question is a one-file backend
change, no FE change, no client regen. Updated CLAUDE.md + ARCHITECTURE.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
Atoms/molecules/organisms/templates/pages composing the NL Design System
(Utrecht) CSS themed Rijkshuisstijl via @rijkshuisstijl-community tokens.
Login -> dashboard -> registration detail, mock JSON over HttpClient, Storybook
organized by atomic layer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>