Catalog declared in code (Domain/Features/FeatureFlags.cs, build-validated), on/off state
persisted in SQLite (FeatureFlagStore + migration). GET /flags (drives FE gating) + admin
PUT /admin/flags/{key} (new flags:manage capability + FlagsAdmin gate). Enforced end-to-end:
the `inschrijving-open` flag hides the Inschrijven nav item + dashboard action (FE) AND makes
POST /applications for a registratie 403 when off (backend). FE FeatureFlagStore mirrors
AccessStore (enabled() deny-by-default); admin toggle page at /beheer/functies in ADMIN_LINKS.
+4 backend tests, /me cap-list updated, client regenerated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The WP-41 GET /admin/audit trail now has an FE view: a beheer audit page (domain
AuditEntry + adapter/parse + store) rendering the data-minimised trail as a read-only
table, capability-gated on cases:manage. Added to ADMIN_LINKS (header nav + dashboard
Beheer section) and to the role.interceptor ROLE_AWARE list so the admin-gated call
carries X-Role. Closes WP-42's audit half.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Admin-only overview of all cases across owners + an admin delete, gated by a new
`cases:manage` capability (Authz role→cap + CanManageCases + CasesAdmin gate;
FE capability + guard + nav + role.interceptor prefix — the org-template/stamdata
recipe). Backend adds ApplicationStore.ListAll()/DeleteAny() and GET /admin/cases +
DELETE /admin/cases/{id}; admin delete removes ANY case incl. submitted. Page lives
in registratie/ui (owns the Aanvraag aggregate; reuses aanvraag-view + parse),
routed /beheer/zaken; delete guarded by a native confirm, optimistic with rollback.
Typed client regenerated (documents the new endpoints + owner field).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`npm run format:check` (a CI gate) had drifted red across 44 files — pre-existing
files plus recently-added ones committed without formatting. Ran `prettier --write .`;
no logic changes. Also regenerates documentation.json (compodoc reflects the reformatted
component sources).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Close the last-mile autosave gap: a debounced edit made in the final <600ms
before leaving a page was lost — the wizard draft-sync timer is cleared on
destroy without flushing, and root stores keep an armed timer the teardown
ignores.
New `shared/application/pending-saves.ts`: a root `PendingSaves` registry every
autosave owner joins (BriefStore, OrgTemplateStore, each createDraftSync). Two
seams flush through it — `flushPendingGuard` (CanDeactivate, on the five
autosave routes) awaits the pending write before an in-app route change; a
`beforeunload` handler (provideUnloadFlush) fires it best-effort and raises the
browser's native unsaved-changes prompt. ponytail: the HTTP seam is Angular
HttpClient (no keepalive/sendBeacon), so a hard-close flush can't be guaranteed
— hence the prompt; upgrade path noted in a comment. Each owner now nulls its
timer handle on fire so `hasPendingSave()` is accurate, and exposes
`flushPending()`.
Verified live against the running stack: navigating away 91ms after a keystroke
(well inside the debounce) fires one PUT /brief before the route changes; a
dirty reload raises the prompt, a clean reload does not. FE lint / check:tokens
/ 299 tests (+11) / build / build-storybook green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Realizes ADR-0004's "future low-code editor that commits a PR": an
admin-only stamdata maintenance editor built on the stamdata-as-code
foundation.
Backend: `professions` moves from a hardcoded C# dictionary to an embedded
`professions.json` data-file (typed as `ProfessionMapping`) with valid-time
(geldigVan/geldigTot, half-open). A generic, reflection-driven
StamdataCatalog/StamdataTable/StamdataFile describes every table so one
endpoint pair + one grid editor serve all of them; add a table in one line.
Two read-only, admin-gated endpoints (GET /stamdata, GET /stamdata/{table}
?peildatum=) — no runtime write path. Generic build gate
`Every_catalog_table_is_valid` (keys non-blank, no overlapping validity,
well-formed windows).
Frontend: new `beheer` context (route beheer/stamdata, capabilityGuard
'stamdata:edit'). A schema-driven grid editor edits rows locally; download()
emits {table}.json for the admin to commit as a reviewed PR (no mutation
command — the CI build + StamdataValidationTests stay the authority).
Full gate GREEN both sides; gen:api leaves no drift; new stamdata story
passes axe. See WP-29 + ADR-0004.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Edit the letter's org identity in place on the same canvas the drafter composes
on (editableRegions='template'): letterhead/signature/footer become inline
controls, content a read-only sample. Margins (bounded), logo upload (reuses the
shared upload transport + single-upload), version history + rollback, proefbrief,
and publish-with-impact-confirmation. House form-machine idiom
(org-template.machine.ts) + root store with debounced save. Capability-gated
(orgtemplate:edit) with a deny-by-default alert; route /brief/huisstijl.
Backend + generated client were already in place (WP-23). Also fixes a
pre-existing red check:tokens (WP-24 canvas hex fallbacks) and threads the
published logo through to the drafter's canvas.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
- 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>
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>
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>
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>
A teaching page pairing each pattern's impossible-state-permitting "before"
with the "after" the type system enforces: discriminated unions, the
RemoteData fold, parse-don't-validate (live), and the wizard state machine.
Composition-only — no new atoms. Linked from the dashboard.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Demonstrates the atomic-design payoff: a whole new flow (route + page + nav
link) reuses page-layout, heading, alert, form-field, text-input, button and
link with no new component files.
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>