Move working docs (backlog, prd, roadmap) under docs/project/ and durable
docs (architecture ADRs, guides, audits) under docs/reference/; add a
docs/README.md index. Update every path reference in code comments, CLAUDE.md,
READMEs, and the new-ssp skill. Expand the Storybook Foundations curriculum
(Overview, BDD, i18n; rename Layers→Domain-Driven Design) and reorder the sidebar.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Compose the herregistratie letter from the besluit instead of a library hunt:
the behandelaar picks positief/negatief (+ reden-checkboxes for a negatief) and
the kern's standaardteksten follow the selection live.
Front-end (this increment):
- Kern is recomposed reactively from the besluit selection (new BesluitSelected
machine msg + composeKern); the "Genereer conceptbrief" button is gone. The
drafter's free text is preserved across a selection change.
- The editor shows only the editable sections; the locked aanhef/slot render in
the preview, not the authoring surface. Slot is a case-type template section
(per templateId), documented as such.
- The panel re-seeds from the letter via inferSelection() — the besluit + redenen
are read back off the kern's passage blocks, so the selection survives reload
with no new wire fields (derive, don't store).
- letter-section drops the now-redundant per-section passage picker (besluit owns
standaardteksten); keeps free-text + block edit/move/remove.
Fix: app-checkbox now falls back to a unique per-instance id when checkboxId is
omitted. The CIBG styled checkbox routes clicks through the label, so the shared
id="undefined" made every reason label toggle the first input — the second
checkbox could never be checked. Verified live (Playwright): each reason toggles
independently.
Backend/seam (brief v3 WIP): besluit/reason passage tags on the wire + seed,
carried through the adapter parse boundary.
Specs updated (besluit, brief.machine) and the affected stories re-pointed at the
new API. FE lint + build + 253 vitest specs green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fix: address-fields shipped a scoped `fieldset { padding:0; margin:0 }` that
tied on specificity with CIBG's `.form-horizontal fieldset` and, injected later,
won — flattening the grey-box padding on "Mijn gegevens" and the registratie
wizard address step. Removed the override (kept the legend rule).
Audit cleanup (minimise atoms, converge or document near-duplicates):
- delete dead `app-card` (+ story; zero consumers, superseded by app-data-block)
and the unused `.app-info-box` utility
- review-section raw <h2> -> app-heading; registratie-wizard lone <dl> -> app-data-block
- keep application-link's raw <h3> (vendored `.applications li a h3` chain needs
the bare element) with an inline justification
- document the deliberate "don't merge these" verdicts in a new Convergence
decisions table in atomic-design.mdx; record the card removal in cibg-gaps.mdx
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
npm update brought every package to the latest version its existing package.json
range allows (Angular tooling 22.0.2/22.0.4 -> 22.0.5, prettier 3.8.4 -> 3.9.4,
typescript-eslint 8.62.0 -> 8.62.1); package.json itself needed no range changes.
Auditing actual deprecation warnings (not just outdated versions) found nothing
further to fix: @angular/platform-browser-dynamic and @angular-devkit/build-angular
are deprecated by Angular but still required peer dependencies of the latest
published @storybook/angular (10.4.6 — peer range still `>=18.0.0 < 22.0.0`,
already why .npmrc sets legacy-peer-deps); jest-process-manager/expect-playwright
are transitive-only through @storybook/test-runner's latest stable (0.24.4). No
newer version of either Storybook package exists yet that drops them. The
remaining npm audit advisory (@babel/core, low severity) is the same
already-documented, deliberately-left issue in README.md (fixing it downgrades
Angular). Left package.json's overrides untouched.
The prettier bump alone changed formatting opinions on files this session didn't
otherwise touch (a stale markdown italics marker, a few object-literal wrap
points) — reformatted everything so `format:check` (part of CI) doesn't regress.
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>
Wires text-input's aria-describedby to the form-field description div
(the BSN hint was rendered but never announced), pins desc-before-error
ordering, and switches alert to role=alert for errors vs role=status
for info/ok/warning. Composition contract enforced by story play tests
(form-field+text-input, alert per variant) run in the WP-01 CI gate.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Retitle all 49 stories into a sidebar that makes the DDD seam visible:
Foundations (curriculum) -> Design System (Atoms/Molecules/Organisms/
Templates/Devtools, everything in shared/ui + shared/layout) -> Domein
(Registratie/Herregistratie/Auth/Brief, everything in a context's ui/).
Pin the order via storySort. Add layers.mdx explaining the split and
linking the enforcing eslint rules; document the story-title convention
in CLAUDE.md. Fix a stale "status banner" reference in atomic-design.mdx
left over from WP-13's upload-status-banner deletion.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Mark every hand-rolled shared/ui surface with a `// CIBG-GAP EXTENSION:`
comment + `cibgGap` story parameter (skeleton, spinner, rich-text-editor,
wizard-shell's error summary, application-link's non-navigating row,
debug-state, status-badge, card, placeholder-chip) so deviations from the
CIBG design system are auditable. Add the register MDX
(Foundations/CIBG Gap Register), cross-linked from ADR-0003. Delete the
near-identity upload-status-banner wrapper; its one consumer now uses
<app-alert> directly (a story added to keep the info-banner state covered).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fix button atom's dead .btn-outline-primary → .btn-secondary; add 'ghost'
variant (.btn-ghost, CIBG-documented). RTE toolbar drops invented
.btn-outline-secondary/.btn-sm for .btn-ghost. file-input already used the
correct vendored .btn-upload pattern from the earlier CIBG UI fidelity
pass — no change needed there (documented as a deviation).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Make AsyncLoadedDirective generic with a static ngTemplateContextGuard for
AsyncComponent's own internal typing. That can't propagate to consumer
`<ng-template appAsyncLoaded let-p>` sites though -- Angular only infers a
structural directive's type parameter from an input bound on that same
node, not from a sibling input on the parent component -- so the ~9
root-cause consumers (dashboard, registration-detail, aanvraag-detail,
registratie-wizard) instead unwrap the RemoteData Success value via a
typed computed() and narrow it locally with `@if (x(); as p)`. The
remaining union-narrowing casts (registration-summary, showcase concepts
page) are replaced with a stable @let binding and a direct resource read,
respectively. Documented as a deviation in WP-06's backlog file.
One-time prettier --write so the new format:check CI gate starts green.
.prettierignore excludes generated (api-client.ts, documentation.json),
vendored (public/cibg-huisstijl), and backend (dotnet format owns it).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Datablock entries had no separators: CIBG ships the border on dt/dd with a
:last-of-type reset, but the one-row-per-<div> grouping (for axe) made every
dt/dd a last-of-type, stripping them all. Carry the separator on the row
(:host:not(:last-of-type)) in the CIBG line colour (#f1f5f9 = cool-grey-200).
- Upload "Bestand toevoegen" button read as a pale outline: btn-outline-primary
forced a transparent background over .btn-upload's accent fill. Use
btn-primary btn-upload (solid #01689b, white text, folder glyph) and drop the
redundant centred background-image folder.
GREEN: lint, tokens, 183 tests, build, 137 axe stories. Verified via Storybook
screenshots (datablock separators, filled blue upload button).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Aanvragen rows now show what the aanvraag is for (purpose subtitle) and an
explicit status label (In behandeling / Goedgekeurd / Afgewezen) alongside the
reference + submit date, via an expanded aanvraag-view (purposeLabel,
statusLabel, referentie, detailRows) + spec.
- Rows link to a new /aanvraag/:id case-detail page, so the CIBG chevron shows
and each aanvraag opens as a (stub) case — it lists soort/waarvoor/status/
referentie/ingediend in a Datablock, with a note that full handling is future.
GREEN: lint, tokens, 183 tests, build, 137 axe stories. Verified visually
(dashboard aanvragen rows, upload drop-zone, datablock) via Storybook screenshots.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The upload suite now wraps the vendored CIBG file-upload classes instead of
hand-rolling from tokens:
- file-input becomes a .file-picker-drop-area with drag-and-drop + a real
.btn-upload button and a visually-hidden, focusable <input>; renders an
always-visible instruction (allowed types + max size) linked to the input via
aria-describedby (pattern requirement).
- The file list is a semantic ul.file-list; single-upload is an
li[app-single-upload] .file-container (native <li> child) with .actions
(retry/.icon-remove) + progress; document-chip renders the .file block
(status glyph, .file-name link, .file-meta size/status).
- Category validation moves ABOVE the block as .upload-validation > .feedback.
- Instruction text uses the default (not subtle) foreground for WCAG AA contrast
on the grey drop-area.
DocumentUpload's public inputs/outputs are unchanged — both wizards keep working.
GREEN: lint, tokens, 181 tests, build, 137 axe stories.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- application-link switches to a `li[app-application-link]` attribute selector
(native <li> child of the <ul> — axe-clean list) and drops the invented,
dead `.application` / `.application-title` classes for the real vendored
`.dashboard-block.applications li a` chain (h3.h3 / .subtitle / .status / .cta).
Content stacks in a flex column; a non-navigating row mirrors the card surface
from tokens. Re-enables a11y on the application-link/list stories.
- Dashboard "Mijn aanvragen" now renders through app-application-list +
<li app-application-link> rows (was a keuzelijst), mapped by a new pure
submittedRow() view helper (+ spec). Concepts stay the resumable melding.
- aanvraag-block is now concept-only (submitted mapping moved to aanvraag-view).
WP-11 grep gate clean. GREEN: lint, tokens, 181 tests, build, 136 axe stories.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adopt the vendored CIBG Datablock (.data-block / .block-wrapper) as the way to
show application data:
- New app-data-block molecule (grey surface + white panel + projected rows,
optional heading, stacked variant, aria-label) + stories.
- data-row switches to a `div[app-data-row]` attribute selector so the <dl>'s
direct child is a native <div> (HTML5.1 dl > div > dt+dd). This makes the
definition list axe-clean — a bare custom element between <dl> and its dt/dd
trips axe's definition-list rule regardless of display:contents, a defect the
dashboard shipped live. Re-enables a11y on the data-row / review-section /
registration-summary stories (previously disabled pending this rework).
- review-section folds onto app-data-block (drops its hand-carried classes).
- registration-summary + dashboard "Persoonsgegevens (BRP)" drop app-card and
render as datablocks; both wizards' review rows + the beroep row convert to
the div selector.
GREEN: lint, check:tokens, 178 tests, build, build-storybook, 136 axe stories.
Co-Authored-By: Claude Opus 4.8 (1M context) <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>
A Concept status now renders as a CIBG "melding" (warning) with its own
verwijderen/openen actions, matching the real CIBG pattern for an in-progress
application, instead of sharing the keuzelijst card shape used by resolved
statuses. Alert atom switches from a hand-rolled surface to the vendored
`.feedback` classes with a visually-hidden icon label per CIBG's a11y
requirement.
"Annuleren" rendered as a separate line below the grey card — visually disconnected
from the choice it belongs to. Fixing this properly means the card can't just be one
big <a> anymore: a <button> can't nest inside an anchor (invalid HTML, broken a11y),
and choice-link's [choiceActions] slot needs Annuleren to sit inside the same box.
choice-link.component.ts now makes the card a <div> always, with the title wrapped in
a vendored Bootstrap `.stretched-link` (its ::after overlay keeps the whole card
clickable, same as before) instead of the whole box being the anchor. The projected
action gets its own `position:relative;z-index:2` (in aanvraag-block.component.ts,
which owns that markup) to stay clickable above the stretched-link overlay. Added
`:focus-within` on the card to restore the focus-accent CIBG's `:focus` rule would
have given the card itself, since focus now lands on the inner title link.
Verified: lint/check:tokens/test/build green; drove it end-to-end — clicking anywhere
on a card body still resumes the wizard, clicking Annuleren cancels without navigating.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both sections offer a set of choices the user picks between to proceed, matching
designsystem.cibg.nl/componenten/keuzelijst rather than the "aanvragen" row pattern
("Wat wilt u doen?" keeps that look — it's a static nav list, not a choice list).
- New shared/ui molecules: choice-list (heading + keuzelijst__list, wired via
aria-labelledby per CIBG's a11y guidance) and choice-link (one keuzelijst__link
choice; routerLink, imperative-clickable, or a plain non-interactive block).
- choice-link's non-interactive block needed a `--static` modifier: CIBG's
`.keuzelijst__link:after`/`:hover`/`:focus` key off the bare class (keuzelijst
assumes every item is a link), unlike `.applications li a::after` which is scoped
to the anchor — without it, a non-actionable aanvraag row inherited a chevron and
hover accent it shouldn't have.
- task-list.component.ts now composes choice-list/choice-link internally; public
API unchanged except a new required `listHeading` input (the heading moves inside
the list for the aria-labelledby link, so dashboard.page.ts stops rendering it
separately — same fix applied to "Mijn aanvragen").
- aanvraag-block.component.ts moves from application-link to choice-link, combining
its separate status/subtitle text into one instructions paragraph (keuzelijst has
no cta field — the row itself is the action). Only a resumable Concept renders as
a real choice; InBehandeling/Goedgekeurd/Afgewezen stay non-interactive, unchanged
from before.
Verified: lint/check:tokens/build green, 178 tests pass, build-storybook succeeds,
and manually driven end-to-end (dashboard renders both sections as keuzelijst cards,
confirmed via screenshot that non-actionable rows have no chevron after the fix).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Re-skins the app's layout on top of the CIBG Huisstijl theme (previous commit) so it
matches designsystem.cibg.nl, not just its colour tokens — magenta ("robijn") header,
horizontal nav, and the CIBG component markup for forms/wizards/dashboard.
- Header: logo block + robijn titlebar (breadcrumb + user menu) + grey horizontal nav
(4 links) replacing the dashboard side-nav; breadcrumb restyled for the titlebar
(no background of its own — CIBG's global `header nav` rule otherwise bleeds a grey
fill into it, fixed by scoping an override inside BreadcrumbComponent).
- Forms: form-field/radio-group/checkbox rebuilt on CIBG's horizontal `form-group row`
/ `form-check.styled` markup (label col-md-4, control col-md-8); same input() APIs.
- Wizards: stepper rebuilt as the CIBG "stappenindicator" (numbered circles, visited
steps clickable for back-nav, title merged in); wizard-shell adopts the CIBG
procesnavigatie button row. Back-navigation wired into all three wizard machines
(registratie-wizard already had it; added `GaNaarStap` to intake/herregistratie
machines, pure + spec'd).
- New shared/ui molecules: confirmation (animated bevestiging checkmark, replaces
plain alerts on submit), review-section (controlestap sections with "Wijzigen"),
application-list/application-link (CIBG "aanvragen" rows, replace the dashboard's
card grid and aanvraag-block).
- Cleanup: delete side-nav and now-unused styles.scss utilities (.app-overview,
.app-form-panel, .app-card-grid); correct design-tokens.mdx (it referenced tokens
that no longer exist) and document the CIBG-value token bridge.
Verified: build/lint/check:tokens green, 178 tests pass (4 new GaNaarStap cases), and
manually driven end-to-end (dashboard, a full herregistratie submission through to the
confirmation screen, mobile width, keyboard focus).
Co-Authored-By: Claude Fable 5 <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>
- brief.machine: reducer refuses edits to locked (predefined) sections as
defense-in-depth; LetterSection gains a `locked` flag
- rich-text: paragraphs gain optional `list` kind; editor gets bullet/numbered
list buttons, keyboard shortcuts, and backspace-deletes-adjacent-chip
- placeholder chips distinguish auto-resolvable (grey) vs manual (yellow), in
both the editor and the read-only preview
- fix: preview chip now renders matching {…} braces (was a one-sided ⌗ glyph),
aligned with the editor's chip styling
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New `brief` context — a letter-composition feature with a drafter/approver
approval workflow, built as a teaching vertical slice on the repo's existing
FP + Elm + atomic-design patterns (see plan in ~/.claude/plans).
Domain (pure):
- Rich text as a serialisable value tree (placeholders are first-class nodes),
moved to @shared/kernel/rich-text.ts so the shared editor can use it.
- lintPlaceholders: a pure, total content -> Diagnostic[] linter, derived never stored.
- brief.machine.ts: status sum-type with guarded transitions; frozen-snapshot =
deep value copy; derived diagnostics/editability. Full specs.
Backend (.NET stub):
- BriefStore + seed, GET/PUT /brief and submit/approve/reject/send endpoints,
role via X-Role header (mirrors X-Admin), transition + approver!=drafter guards,
audit logging. Regenerated typed client via gen:api. +6 backend tests.
Seam:
- brief.adapter.ts maps flat wire unions <-> domain discriminated unions at the
parse boundary (+ spec).
UI (atomic):
- shared atoms: checkbox, placeholder-chip; molecule: rich-text-editor (no-dep
contenteditable, DOM<->RichTextBlock round-trip tested).
- brief/ui: letter-block, passage-picker, diagnostics-panel, rejection-comments,
letter-section, letter-composer, letter-preview, brief.page + /brief route.
- Dev-only ?role=drafter|approver toggle + roleInterceptor; dashboard nav link.
Enforcement: @brief/* alias + eslint layer boundary (brief depends only on shared).
Also included (same session):
- Value-object specs (postcode/uren/big-nummer) — closes the "domain must have a spec" gap.
- src/docs/ Storybook MDX foundation pages (atomic design, tokens, FP-in-UI).
- .storybook/tsconfig.json: add @angular/localize to types (Storybook was fully
broken — $localize unresolved — dev + build).
Verified: 168 FE tests, 68 backend tests, lint/build/check:tokens green,
Storybook boots, end-to-end HTTP smoke (self-approve 403, approver 200, full flow).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Categories stay server-owned (ADR-0001); the FE sends its answers to
/uploads/categories and re-fetches reactively when they change:
- Diplomabewijs required only for a handmatig diploma (DUO is verified digitally;
nothing required before a diploma is chosen).
- Bewijs Nederlandse taalvaardigheid required only when the applicant answers "ja"
to the nl-taalvaardigheid (B2) policy question.
CategoriesFor(wizardId, diplomaHerkomst, taalvaardigheid) decides; Find uses the
maximal set so uploads still validate. CategoriesLoaded drops orphaned uploads
when a category disappears. Also: show the foreground-only upload banner only when
there is at least one category.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- animate.enter/leave on aanvraag cards and upload rows (native Angular, no @angular/animations)
- reduced-motion: skip animation → instant removal
- api-client: null-body statuses (204/205/304) must pass null to Response()
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-opening a Concept wizard now lets the user preview/download what they already
uploaded (PRD 0001 goal 2).
- document-chip: optional previewUrl input → a "Voorbeeld / Download" link (opens
the stored bytes; server serves inline for pdf/image, attachment otherwise).
- previewUrlFor callback threaded document-upload → document-category → single-upload
(which builds the URL from a completed upload's documentId). Keeps URL-building out
of the presentational atoms.
- registratie + herregistratie wizards supply previewUrlFor via UploadAdapter.contentUrl,
returning undefined for dev-simulation `demo-*` ids (no stored bytes → no link).
- Story: DocumentChip/WithPreview.
Gates green: vitest 128, lint, ng build, check:tokens; backend dotnet 56.
(build-storybook has a pre-existing Compodoc/$localize issue in untouched files.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- upload-slow/upload-fail scenarios simulated in the adapter (XHR POST bypasses the
HTTP interceptor); categories/status/delete already honour the global slow/error
- file-input gets a per-category accessible name (aria-label + visible button text)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wrap every user-facing Dutch string in Angular's first-party i18n — `i18n`/
`i18n-<attr>` in templates, `$localize` in TS (value-objects, machines, commands,
label constants, shared-component defaults). Source locale stays nl; a second
locale is now a translation file, not a code change.
- M3: ~145 strings localized with stable @@ ids across registratie,
herregistratie, auth, shared/ui, shared/layout. Skipped: showcase, debug-state,
scenario interceptor, generated client, specs/stories, raw status enum tags,
internal parse* diagnostics.
- M4: single shared JA_NEE (localized labels) in radio-group; both wizard copies
removed.
Gate green: lint, check:tokens, build, test 77/77.
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>
Chrome: two-tier Rijksoverheid header (white brand bar + lint-blue
breadcrumb bar, route-driven), dark multi-column footer, white page
surface. Session shown via a shared SESSION_PORT token (keeps shared/
free of the auth context).
Overview ("Mijn overzicht") rebuilt to the NL Design System #392 pattern:
side-nav + "Wat moet ik regelen" task list (derived) + "Mijn registratie"
cards. New shared components: card, task-list, side-nav; pure
tasksFromProfile (+spec).
Wizards: grey form panel, connected numbered stepper, form-field
"(verplicht)" markers + styled description/error, full-width inputs.
Propagated to login, detail, change-request, address-fields.
Bug fixes:
- wizard-shell: add FormsModule so NgForm intercepts submit (wizards now
advance; no native GET leaking choices into the URL).
- wizard-shell: error-summary links focus the field instead of navigating
(a fragment href resolved against <base href="/"> reloaded to "/" and
bounced to login).
- wizard-shell: error-summary focus only on the rising edge, so typing
while errors are shown no longer scrolls the page up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A floating, read-only panel that renders the current root-store state
(SessionStore + BigProfileStore) live via the json pipe. Whole thing is
gated by isDevMode() so it never renders or ships in production.
- Observer only — no new store/library/state pattern (PRD prime directive).
- BigProfileStore resolved lazily on first open; its httpResources fetch
eagerly on construction, so we avoid a personal-data fetch on pages that
don't use it.
- bsn masked before render; no persistence/logging/network in the feature.
- maskBsn has a unit spec; UI exercised via a Devtools Storybook story,
per repo convention (no TestBed component tests).
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>
- radio-group: the Utrecht radio paints its dot only with the
`utrecht-radio-button--checked` class (not the native :checked); we set
[checked] but not the class, so a selected radio looked empty. Bind the class
too. The intake step-1 choice now visibly selects.
- data-row: drop the border-block-end on the last row (:host:last-child) so a
summary ends cleanly instead of showing a trailing empty row (visible in the
concepts discriminated-unions card).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A second wizard demonstrating a BRANCHING flow: the visible steps are derived
from the answers by a pure `visibleSteps` function rather than stored, so
answering "buiten Nederland gewerkt? -> ja" or reporting few hours adds steps
and the progress denominator changes live. Same Elm-style store + RemoteData
patterns as the fixed wizard; answers persist to localStorage.
- intake.machine.ts: IntakeState union + Answers + visibleSteps + pure reduce (+spec)
- intake-wizard organism, intake.page, submit-intake command
- new radio-group atom (ControlValueAccessor) in shared/ui
- /intake route + dashboard link + concepts showcase section
- tighten Aantekening.type to a 'Specialisme' | 'Aantekening' union
- README + ARCHITECTURE updated
Verified live end-to-end (branches add steps 4->5->6, review, submit) with no
console errors; build, unit tests, and Storybook all green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reorganise from atomic-design-only folders into bounded contexts
(auth / registratie / herregistratie) over a shared kernel, each split into
domain / application / infrastructure / ui layers. Dependencies point inward;
the domain layer is framework-free. Path aliases (@shared/@auth/@registratie/
@herregistratie) make import direction explicit.
State management (Elm-style, native TS, no new deps):
- shared/application/store.ts — createStore(init, update): pure reducer + signal
- shared/application/remote-data.ts — add map/map2/map3/andThen combinators so
several services fold into one RemoteData; <app-async> gains an [rd] input
- registratie/application/big-profile.store.ts — root singleton combining the
BIG-register and BRP services via map2 into one state; holds the optimistic
herregistratie flag shared with the dashboard
- herregistratie: machine gains a WizardMsg union + pure reduce; submit is a
command that calls infra and dispatches the result, with optimistic update +
rollback against the shared store
- auth: SessionStore + DigiD adapter + functional route guard; login establishes
the session, protected routes use canActivate
Rich domain: registration.policy.ts (statusColor/label, herregistratie
eligibility, invariants); BigNummer/Postcode/Uren value objects with smart
constructors. status-badge is now domain-free (colour/label inputs).
Specs for the reducer, RemoteData combinators, and eligibility policy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>