Commit Graph

105 Commits

Author SHA1 Message Date
e276629107 feat(fp): WP-20 — second locale proof (nl/en build seam)
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>
2026-07-04 18:16:11 +02:00
26c2c5acd0 feat(fp): WP-19 — Playwright e2e smoke against the real FE+backend
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>
2026-07-04 10:13:40 +02:00
e272869f00 feat(fp): WP-17 — app-level a11y: route focus, template lint, WCAG checklist
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>
2026-07-04 08:26:01 +02:00
f3de30b72c feat(fp): WP-16 — component a11y: description wiring + alert role
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>
2026-07-04 08:15:24 +02:00
85c805b8bd docs(backlog): backfill WP-15 commit hash
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 23:38:07 +02:00
0cfb01f12c feat(fp): WP-15 — missing stories: shell + brief components
Add the 7 stories CLAUDE.md's testing rule ("UI is exercised via Storybook
stories") was missing: shared/layout/shell (Design System/Templates/Shell)
and all six previously-unstoried brief components (passage-picker,
rejection-comments, diagnostics-panel, letter-block, letter-preview,
letter-section — Domein/Brief/*), each with a default state plus the
meaningful variants (locked/editable, findings/clean, show/entry, etc).
Every *.component.ts in the repo now has a co-located story; *.page.ts
files stay unstoried, matching the existing norm.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 23:37:58 +02:00
ac1f0b6aeb docs(backlog): backfill WP-14 commit hash
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 23:32:26 +02:00
8b19fad558 feat(fp): WP-14 — Storybook taxonomy reorg + Layers MDX
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>
2026-07-03 23:32:19 +02:00
cbf697b8fa docs(backlog): backfill WP-10/WP-13 commit hashes
Some checks failed
CI / storybook-a11y (push) Successful in 4m2s
CI / backend (push) Successful in 1m2s
CI / codeql (csharp) (push) Failing after 1m40s
CI / frontend (push) Failing after 54s
CI / codeql (javascript-typescript) (push) Failing after 1m23s
CI / api-client-drift (push) Successful in 1m30s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 22:42:26 +02:00
9d58f597ea feat(fp): WP-13 — CIBG-gap register + hygiene + MDX
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>
2026-07-03 22:42:13 +02:00
69880efd38 feat(fp): WP-10 — CIBG button fidelity
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>
2026-07-03 22:34:28 +02:00
8078c499cb feat(fp): WP-09 — pure-logic closure: dates + missing command specs
Consolidate four hand-rolled nl-NL date formatters (tasks.ts, aanvraag-
block, letter-preview, aanvraag-view -- one more than the WP found) into
one shared/kernel/datum.ts::formatDatumNl, spec-pinned and empty-safe.
Add the two missing command specs CLAUDE.md's testing rule calls for:
draft-sync.spec.ts (debounce coalescing + trailing-call + submit Result
shape, via fake timers) and submit-change-request.spec.ts. Remove the
unused RemoteData.map3 (updating the three docs that mentioned it); the
variant input on confirmation.component.ts was already gone. Documents
both stale-WP-text corrections in the backlog file.

This closes out backlog Phase 1 (FP/DDD core, WP-05..09).
2026-07-03 22:02:50 +02:00
0d623f90e8 feat(fp): WP-08 — one store idiom + machine naming + TEA MDX
Rename change-request.machine.ts's bare State/Msg to ChangeRequestState/
ChangeRequestMsg (the last machine not context-prefixed), document the
createStore-is-the-idiom + naming convention in CLAUDE.md §3, and add the
Foundations/State Machines (TEA) curriculum page. The wizard pages already
wired createStore (confirmed by reading each and by git log) -- the WP's
"hand-wired signal(model)" premise was stale; recorded as a deviation.
2026-07-03 21:50:53 +02:00
e3cd908f4f feat(fp): WP-07 — brief on the shared idioms + RemoteData MDX
Collapse brief.store's busy signal + nullable lastError into one Idle |
Busy | Failed union (saveState gets matching tag-object style), and route
brief.page's load through RemoteData + <app-async> instead of a hand-rolled
@switch, via a BriefStore.remoteData projection of the machine's existing
loading/failed tags -- the machine keeps owning the letter's own status
lifecycle untouched. New brief.store.spec.ts covers the Busy->Idle/Failed
transitions; new Foundations/RemoteData & Async MDX page documents the
pattern and the WP-06 typed-loaded-slot fallback. Deviation from the
original plan recorded in the WP file.
2026-07-03 21:39:29 +02:00
199cbe1f8c feat(fp): WP-06 — kill $any() in templates (18x)
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.
2026-07-03 21:27:01 +02:00
34d34512b3 feat(fp): WP-05 — parse-don't-validate closure + MDX
Close the three remaining unvalidated `as <DomainType>` casts at the wire
boundary (intake-policy, big-register aantekening type, brief passage scope),
each replaced by a Result-returning parser with a rejection-case spec, plus
the Foundations/Parse, don't validate curriculum page.
2026-07-03 21:02:15 +02:00
5d6a78d4ec docs(backlog): record WP-18 commit hash
Some checks failed
CI / frontend (push) Successful in 1m22s
CI / backend (push) Successful in 1m3s
CI / api-client-drift (push) Successful in 1m36s
CI / storybook-a11y (push) Successful in 4m4s
CI / codeql (csharp) (push) Failing after 1m45s
CI / codeql (javascript-typescript) (push) Failing after 1m25s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 20:32:03 +02:00
7ec13d8b59 feat(brief): WP-18 — ABAC capability spine (PRD-0002 phase P1)
Replace the FE-computed authorization anti-pattern in BriefStore.editable
(derived from the unverified X-Role header) with server-computed decision
flags, mirroring the existing HerregistratieDecisionsDto pattern:

- Backend: Authz.cs is the single authorization helper — the SAME check
  (Authz.CanActOn) both gates BriefStore.Review's mutations and computes
  the BriefDecisionsDto flags shipped on every brief response, so emit
  and enforce can never drift. New GET /me returns coarse, role-derived
  capabilities (PRD-0002 SS6).
- Every brief endpoint (including send, previously ungated on HttpContext)
  now returns a fresh BriefViewDto so decisions never go stale after a
  mutation.
- FE: brief.store.ts reads canEdit/canApprove/canReject/canSend off the
  loaded decisions instead of computing them from currentRole(); the
  brief.machine carries decisions through every status transition.
- New shared/domain/capability.ts + shared/application/access.store.ts +
  shared/infrastructure/me.adapter.ts: the general capability-spine
  infrastructure (AccessStore.can(), capabilityGuard) for future routes.

Deviates from the original WP-18 draft by NOT renaming auth/domain's
Session to a Principal union — ADR-0002 explicitly defers that refactor
until a second actor exists, and the brief workflow's drafter/approver
identity turned out to be a separate axis from the SSP login session
entirely. See docs/backlog/WP-18-abac-capability-spine.md for the full
as-built record.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 20:31:53 +02:00
cbb8ae548c docs(backlog): add WP-18..22 (productie-volwassenheid phase)
Some checks failed
CI / storybook-a11y (push) Successful in 4m12s
CI / backend (push) Successful in 1m6s
CI / api-client-drift (push) Successful in 1m37s
CI / frontend (push) Successful in 1m31s
CI / codeql (csharp) (push) Failing after 1m51s
CI / codeql (javascript-typescript) (push) Failing after 1m24s
Gap analysis found the POC's designed-but-unbuilt strategic gaps: ABAC
authorization (ADR-0002/PRD-0002 phase P1), no e2e coverage, unproven
i18n second-locale seam, thin resilience seams (correlation-id,
idempotency, retry), and in-memory-only persistence. Each WP is grounded
in the current code (file paths + line numbers), not just the analysis.

Also corrects PRD-0001's stale 'Proposed' status header — the Mijn
aanvragen vertical is fully built.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-03 20:07:05 +02:00
bf920696ac ci: harden workflow + add security scanning and format gates
Some checks failed
CI / storybook-a11y (push) Successful in 4m3s
CI / backend (push) Successful in 1m1s
CI / codeql (csharp) (push) Failing after 39m18s
CI / codeql (javascript-typescript) (push) Failing after 1m22s
CI / frontend (push) Successful in 1m25s
CI / api-client-drift (push) Successful in 1m34s
- permissions: contents:read (least privilege), concurrency cancel,
  scope push to main+tags (was: every branch, double-running with PRs),
  per-job timeout-minutes.
- security: npm audit --omit=dev, CodeQL SAST (TS + C#), Dependabot
  (npm/nuget/actions).
- format: npm run format:check + dotnet format --verify-no-changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 13:39:31 +02:00
1137f59f7b style: format backend with dotnet format
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 13:39:31 +02:00
e82309786d style: format frontend, docs and skills with prettier; add .prettierignore
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>
2026-07-03 13:39:31 +02:00
546097434d fix(ci): point backend test at BigRegister.slnx (solution renamed from .sln)
All checks were successful
CI / frontend (push) Successful in 1m12s
CI / storybook-a11y (push) Successful in 4m5s
CI / backend (push) Successful in 43s
CI / api-client-drift (push) Successful in 1m31s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 11:43:44 +02:00
4ba0a020f3 docs: regenerate Compodoc documentation.json
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 11:36:26 +02:00
922f9ec8cf docs: README + Storybook mdx describe the CIBG Huisstijl setup
Replace stale @rijkshuisstijl-community package/theming claims with the
vendored CIBG Huisstijl + token-bridge reality (ADR-0003); system-font
stack instead of Fira Sans; embed the now-existing document-upload story
in atomic-design.mdx.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 10:44:00 +02:00
cf44bda0ce docs(skills): extract house recipes as Claude Code skills for SSP templating
Some checks failed
CI / frontend (push) Successful in 2m16s
CI / storybook-a11y (push) Successful in 4m6s
CI / backend (push) Failing after 48s
CI / api-client-drift (push) Successful in 1m32s
8 template-generic skills in .claude/skills/ (new-feature, new-context,
value-object, form-machine, bff-endpoint, mutation-command, ui-component,
new-ssp), condensed from CLAUDE.md/ARCHITECTURE/fp-tea/ADRs and pointing at
this repo's worked examples. CLAUDE.md gains a pointer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 10:10:38 +02:00
0f360c5939 fix(cibg): datablock row separators + filled Bestand-upload button
- 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>
2026-07-03 08:46:06 +02:00
05314afd98 docs(backlog): WP-11 + WP-12 done; note WP-13 upload correction
The CIBG UI fidelity pass completed WP-11 (aanvragen/application-link) and WP-12
(Datablock), and reworked the upload suite to wrap vendored CIBG classes rather
than mark it as a gap (WP-13's assumption corrected).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 21:56:18 +02:00
ccc0184342 feat(aanvragen): richer rows (purpose + status) linking to a case-detail page
- 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>
2026-07-02 21:55:21 +02:00
b5c5d30a65 feat(cibg): adopt the vendored CIBG Bestand-upload component
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>
2026-07-02 21:47:25 +02:00
98fd7e4bcd feat(cibg): WP-11 — render "Mijn aanvragen" as the CIBG Aanvragen component
- 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>
2026-07-02 21:34:21 +02:00
82fc3c493d feat(cibg): WP-12 — CIBG Datablock for application data
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>
2026-07-02 21:25:57 +02:00
947d5fa90a docs(backlog): record WP-04 commit hash
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 21:01:49 +02:00
035e785c95 feat(boundaries): WP-04 — ui ↛ infrastructure + showcase sanction
Move the two wizard lookups behind application-layer facades so ui/ no longer
injects infrastructure adapters directly:
- RegistratieLookupStore (BRP address + DUO diplomas): owns the resources,
  runs the trust-boundary parse, exposes adresStatus/prefillAdres/duoLookup.
- IntakePolicyStore (scholing threshold): owns the policy resource, exposes
  the derived threshold.

Add the lint rule ui/ + layout/ ↛ **/infrastructure/** (@typescript-eslint
variant so it composes with the base direction rules; stories/specs exempted
as test scaffolding). Add the documented showcase sanction (may read every
context). Fix the docs' inventory: 6 contexts / 5 layers, +brief, +contracts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 21:01:42 +02:00
3a5c8f157a fix(wizard): don't animate silent ?aanvraag stamp — Firefox double-click on Next
The inschrijven/herregistratie wizards needed two clicks on "Volgende" to advance
in Firefox (and Zen). Root cause: picking a field (e.g. correspondentie) triggers
draft-sync's debounced create, which router.navigate()s to stamp ?aanvraag=<id>
into the URL. withViewTransitions() animated that same-route navigation, and for
the transition's duration Firefox's ::view-transition overlay swallows pointer
events (confirmed: elementFromPoint over the button returns the overlay, not the
button). Chrome sets pointer-events:none on the overlay so clicks pass through —
hence Firefox-only.

Fix: skip the view transition for same-route navigations (compare leaf routeConfig)
so the silent id-stamp doesn't animate; genuine page-to-page transitions still fade.

Verified in Firefox: page navs still animate (skipped:0), the ?aanvraag stamp is
skipped, and Post → single Next advances step 1 → 2. GREEN + a11y.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 20:51:11 +02:00
6224501e0a docs(backlog): record WP-03 commit hash
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 20:19:58 +02:00
f9b76e7f6a feat(boundaries): WP-03 — contracts purity + ApiClient confinement
Lint-enforce two architecture rules that were only documented (ADR-0001),
landing the rules with the fixes so the build stays green:

- contracts/ imports nothing: dashboard-view.dto.ts is now pure wire shapes
  (inline string-union enums, no domain imports). The DashboardView FE-view
  type moves to the adapter, which maps wire → domain (compiler-enforced seam).
- ApiClient lives only in infrastructure: change-request-form (UI) no longer
  injects ApiClient — a new ChangeRequestAdapter owns the client and the submit
  becomes a createSubmitChangeRequest() command factory (createDraftSync shape).
  draft-sync's wire-DTO import becomes type-only (allowed via allowTypeImports).
- Role type moves to shared/domain/role.ts; the ?role= reader stays in
  shared/infrastructure/role.ts.
- eslint: contracts import-ban + @typescript-eslint/no-restricted-imports on
  api-client (value-only; type imports permitted; infra + shared/upload exempt).

Also fixes a PRE-EXISTING bug found while verifying the flow: change-request-form
never imported FormsModule, so (ngSubmit) didn't bind and the submit button did a
native form submit (page reload) instead of submitting. Verified end-to-end in the
running app: submit → command → adapter → backend → reference, success alert shown.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 20:19:58 +02:00
be3a64f6cf docs(backlog): record WP-02 commit hash
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 19:32:51 +02:00
88442b0616 feat(gates): WP-02 — harden check:tokens to whole-app colour guard
- 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>
2026-07-02 19:32:51 +02:00
348422afc9 docs(backlog): record WP-01 commit hash
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 19:13:26 +02:00
97f7de4590 feat(a11y): WP-01 — axe-on-every-story CI gate
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>
2026-07-02 19:13:18 +02:00
b4fb0be769 feat(dashboard): render a lopende aanvraag as a CIBG melding
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.
2026-07-02 17:08:04 +02:00
f769242f76 docs: replace SHOWCASE-ROADMAP.md with docs/backlog/ (17 WPs)
Turns the prior roadmap sketch into ordered, gated work packages (enforcement
gates, FP/DDD consistency, CIBG fidelity, Storybook curriculum, a11y) from the
2026-07-02 showcase-hardening audit.
2026-07-02 17:07:59 +02:00
a2ed3ae5b8 fix(dashboard): move Annuleren inside the keuzelijst card
"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>
2026-07-02 16:15:22 +02:00
7ac14557dd feat(dashboard): restyle Mijn aanvragen / Wat moet ik regelen as a CIBG keuzelijst
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>
2026-07-02 15:54:02 +02:00
6257d7ede3 feat(design): adopt CIBG component patterns (header, forms, wizards, dashboard)
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>
2026-07-02 14:33:05 +02:00
7887355ca3 docs(prd): add PRD-0002 — attribute-based access control (ABAC)
Specifies fine-grained, app-owned access control layered on the AD roles: capability
gating, data-scoping, field/PII-level, and step-up/SoD. Backend-authoritative (per
ADR-0001), UI mirrors decisions; extends ADR-0002. Privacy-by-design: data-minimized
decision DTOs, server-side PII redaction, audit, deny-by-default.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 12:33:12 +02:00
b443f1fc04 feat(design): re-theme to CIBG Huisstijl (Bootstrap 5.2), replacing RHC/Utrecht
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>
2026-07-02 12:32:50 +02:00
180c08d4ee fix(registratie): silence NG8102 by moving ?? '' guard into a method
`draft().antwoorden[q.id]` types as `string` (Record index, no noUncheckedIndexedAccess),
so the template `?? ''` tripped NG8102's "redundant nullish coalescing" diagnostic — but a
missing key IS `undefined` at runtime, so the guard is real. Moved it into an `antwoord(id)`
component method: same runtime safety, no template-only diagnostic. Build now warning-clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 12:32:13 +02:00
7f55577864 chore(tsconfig): enable strict + strictTemplates
Adds the `strict` umbrella flag (on top of the existing individual flags) and
Angular's `strictTemplates`. Build and full test suite pass with no new errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 08:50:22 +02:00