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>
Reshape the adreswijziging form into a contact-change form: the BRP address is
authoritative and shown read-only (you change it at the gemeente), and the phone
number becomes the editable/submittable field. New Telefoonnummer value object
(parse-don't-validate); backend RejectPhoneChange re-validates as authority.
POST /change-requests now carries { telefoon } (typed client regenerated).
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>
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.
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>
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>
Two dashboard bugs from the just-built feature.
1. Cancel didn't reflect until a browser refresh. ApplicationsStore now OWNS the
list in a writable RemoteData signal instead of projecting a resource() through
fromResource; cancel removes the row synchronously (guaranteed disappear, no
dependence on CD timing / HTTP cache / the reloading gap), then confirms the
DELETE (rollback on failure, no resync). Adapter gains list(); applicationsResource()
removed. Shared fromResource/remote-data.ts deliberately untouched.
2. Duplicate / inconsistent Concepts per type. createDraftSync.resume() now: a
?aanvraag link wins; else it resumes THIS type's existing Concept (loads its
draft); else fresh. ensureId is gated behind resume so a fast typist can't create
a duplicate before the lookup lands. restart()/reset() deletes the current Concept
(submitted → 409, kept) so there's at most one active Concept per type. A non-Concept
id can't reopen as an editable draft. Backend unchanged.
Gates green: lint, vitest 128, build, check:tokens, backend dotnet 56.
Wiring is not unit-covered — needs live verification (see plan).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Regenerate the NSwag client against the new backend endpoints (application +
content methods, Aanvraag DTOs) — clears the API-client drift.
- registratie/domain/aanvraag.ts: FE domain view — AanvraagType + AanvraagStatus
discriminated union (illegal states unrepresentable) + Aanvraag/AanvraagDetail.
Lives in registratie: the dashboard consumes it, downstream wizards produce it.
- ApplicationsAdapter (infrastructure, the only new network surface): list resource
+ create/syncDraft/cancel/submit commands, with a hand-written parse* boundary
(parseAanvraagStatus/parseApplicationSummary/parseApplications/parseApplicationDetail)
mapping untrusted DTO -> domain, per ADR-0001. Spec covers each status tag + rejects.
- UploadAdapter.contentUrl(id): direct href for preview/download (browser opens it).
Gates green: dotnet test 56, vitest 122, lint, build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Implement-now:
- G1: keep PII out of persistent storage — never persist BSN (only `naam`);
move both wizard drafts (address/email, work data) localStorage → sessionStorage
so they clear on tab close.
- G2: validate storage reads before trusting the cast — shape/tag guard in every
restore() (mirrors the parse* HTTP boundary); corrupt/foreign shape → start fresh.
- G3: already satisfied (debug-state redacts via mask.ts).
Show-the-seam (hook + doc, not fully built):
- G4: problemFieldErrors() maps a server validation envelope (ASP.NET
ValidationProblemDetails `errors`) to the field-keyed map the wizards already
render; returns {} until the backend sends it. +spec.
- G5: documented the retry/backoff seam at the adapter GET loader; reads may
retry, mutating submits never do.
Out of scope (named): unsaved-changes warning (persistence prevents data loss),
real auth/tokens, axe-core in CI.
Gate green: lint, check:tokens, build, test 79/79.
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>
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>