27 Commits
Author SHA1 Message Date
ehoandClaude Opus 5 44dcc69811 refactor: ui/dashboard becomes ui/overzicht-secties (RD-36)
RD-03 moved the dashboard page to overzicht/ui/overzicht.page.ts and left four
sections in registratie/ui/dashboard/. The folder was named after a page that
lives in another context. A reader who opened it found four sections that are
not the dashboard.

The folder is now overzicht-secties/ — registratie's sections for the overzicht
page. The alias does not change, because the sections stay in the registratie
context. The story titles do not change, because they name the context.

Five documents cited registratie/ui/dashboard.page.ts, a file that RD-03
renamed. They now name overzicht.page.ts, or the section that owns the
behaviour they describe.

The /dashboard route keeps its path. It is a user-visible URL.

npm run ci --full passes: 67 and 45 storybook suites, 306 axe tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 16:21:15 +02:00
ehoandClaude Opus 5 241fe1b57b docs: cite symbols, not lines, in ARCHITECTURE 2g
RD-31 applied the symbols-not-lines rule to section 6a only. Section 2g has
the same rot. Four of its five citation groups point at the wrong code:
herregistratie.machine.ts L138-142 lands on `resolve`, not `setField`;
draft-sync.ts L34 is a blank line; the wizard's L78 is an input attribute;
brief.store.ts L157 is a comment.

One link was also dead. text-input.component.ts moved to
libs/shared/src/ui/atoms/ in RD-27.

Every link in the document now resolves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 23:12:56 +02:00
ehoandClaude Opus 5 d31e054504 docs: cite symbols, not lines, in ARCHITECTURE 6a (RD-31)
Section 6a cited line numbers and two paths from before the monorepo
split (WP-67). A line number goes stale on the next edit; a symbol
survives it. Replace every L<n> citation in the section with the
named symbol, fix the two dead paths (environment.ts now lives under
libs/shared, proxy.conf.json is now one file per app), and point the
read walkthrough at mijn-registratie.section.ts, where the RD-03
split moved the dashboard's <app-async> block.

Mark RD-31 done and update its README row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 23:11:03 +02:00
ehoandClaude Sonnet 5 d15943bb36 docs: teach the effect map, not the deleted submit method (RD-09)
RD-05 through RD-08 replaced the hand-called submit method with
createStore's effect map. Two teaching documents still showed the old
method in a code block, as the answer to "how does a submit happen?".
Both blocks also called a function that no longer exists.

Rewrite the code block in ARCHITECTURE.md section 2d and its
fp-tea-atomic-design.md counterpart. Both now show the effect map, keyed
on the Submitting tag, using the same herregistratie worked example with
its optimistic begin/confirm/rollback calls. Both use draftSync.submit,
the call the two herregistratie wizards make today.

State the two properties the old idiom lacked, since they are the reason
for the change: entering a state runs its effect, so a dispatch cannot
skip it; and double-submit protection is structural, because the effect
fires only on a tag transition. Add one sentence on the Seed exemption: a
mount or restore message must not trigger a submit.

Fix the one runIfSubmitting() hop in the write walkthrough at
ARCHITECTURE.md's line 574. The rest of section 6a stays stale on
purpose — RD-31 owns it, including its line citations and dead paths.
fp-tea-atomic-design.md's broken pre-monorepo paths stay stale too —
RD-32 owns those.

Set RD-09's Status to done and its README row to done in the same
commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-04 17:26:52 +02:00
ehoandClaude Opus 5 9520d6c24e refactor(shared): move upload/ into infrastructure/domain/application (RB-24)
libs/shared/src/upload/ held a network adapter, an Elm machine, and two
application-layer coordinators outside the folder-per-layer convention every
other context follows. The dependency-cruiser rule carved an exception around
the misplaced adapter instead of the violation being fixed.

Move all five files to the layer each belongs to (git mv), update every
import across 24 consumer files, then delete the carve-out clause from
.dependency-cruiser.base.js. No export renamed, no file split, no spec
content changed.

Deleting the carve-out exposed a second, pre-existing rule violation:
ui-not-infrastructure had never fired against upload.adapter.ts because its
old path did not match /infrastructure/. Three UI components injected
UploadAdapter directly for its one-line contentUrl() wrapper. Route each
through the existing pure uploadContentUrl() function via the application
layer (upload-controller's new previewUrlFor, OrgTemplateStore's new
previewUrlFor) instead — the same idiom brief.store.ts already used.

npm run ci passes; dep:check is clean for both apps with the carve-out gone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 20:40:41 +02:00
ehoandClaude Opus 5 25a5d415a5 docs(adr): land ADR-C-001, ADR-C-003, ADR-C-007 and ADR-C-009
The architect approved the four ADR-fix tickets. All four change what the
architecture documents claim. No code changes.

ADR-0001, ADR-C-001: the worked example claimed the POC has no real backend.
It rewrites against `backend/src/BigRegister.Api`. Every path it named is
repointed. The out-of-scope list drops two discharged bullets: 33 `parse*`
boundaries exist, and `npm run gen:api` is real.

ADR-0001, ADR-C-003: a new section states that the generated client is the wire
contract. A hand-written `contracts/*.dto.ts` is the exception for two cases
only. The four survivors stay, because NSwag emits every property as optional
and flattens `RegistrationStatusDto` into five optional strings. The `parse*`
trust boundary stays mandatory, because a generated type is a compile-time
claim about the wire and not a runtime guarantee.

ADR-0003, ADR-C-007: four paths moved in WP-67 and are repointed. Point 4 kept
the principle and changed its example to `skeleton` and `spinner`. Two of its
claims were false and the amendment says so: `app-alert` wraps the vendored
`.feedback` classes, and `site-header` composes the vendored `.titlebar`.

ADR-0004, ADR-C-009: the exception section states a four-part test instead of
one named exception. `OrgTemplateStore` and `FeatureFlagStore` both pass it. RB-07
gated this ticket, because clause 4 needs an audited allow path. RB-07 landed
that, so the ADR does not ratify a control that the code lacks.

Three tickets need a matching CLAUDE.md correction in the same diff. CLAUDE.md
section 2 loses the false `alert` example. Section 4 gets the generated-client
rule and the four-part test.

Two findings were wrong. ADR-C-001 asked to keep an out-of-scope bullet that
reads "SessionStore is in-memory". The session persists to `localStorage` now,
so the bullet covers multi-tab sync only. ADR-C-007 flagged one half of point 4
and missed that the other half is equally false.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 18:29:05 +02:00
ehoandClaude Opus 5 f19185ed81 refactor(auth): land Session -> Principal, add MedewerkerAdapter (RB-13)
ADR-0002 SS3 models Zorgverlener/Medewerker as different Principal
variants with different login flows. Actor #2 (apps/behandelportal)
landed in WP-61/67 and the union never followed: grep -rn "Principal"
returned one hit, a comment. Both apps' auth/domain/session.ts stayed
byte-identical (`{ bsn, naam }`), so the backoffice's Behandelaar
carried a BSN and logged into the backoffice as a citizen, by DigiD,
under a fabricated citizen's name (login.page.ts). The divergence
ADR-0002 predicted took an orthogonal side door instead
(medewerker.interceptor.ts's X-Medewerker/X-Rollen stamp, which never
touches SessionStore) -- which is why ssp/auth and bhp/auth still
measured as 100%/84% duplicated after ADR-C-006 shared the route
guards. RB-09 (landed the day before) made the backend's
IIdentityProvider able to say "no identity" and fail closed; this
ticket is its named FE half.

Each app's auth/domain/session.ts becomes principal.ts, holding the
one Principal variant that app actually has an actor for: ssp keeps
`{ kind: 'zorgverlener', bsn, naam }` (G1 still strips the BSN before
persisting); behandelportal gets `{ kind: 'medewerker', medewerkerId,
naam, rollen }` (no BSN to strip -- G2 shape validation only). A new
MedewerkerAdapter replaces DigidAdapter in behandelportal, resolving
the existing MEDEWERKER_ID/currentRollen() dev stand-in into a
Principal; because there is no credential to check, it returns
Principal directly rather than a Result whose error variant could
never occur. login.page.ts stops being a BSN/wachtwoord form -- one
explainer line and an "Inloggen met SSO" button -- and its dead
error-handling branch goes with the Result wrapper that justified it.

Measured with tools/baseline-scan.mjs --dup: auth duplication drops
from 168/168 (ssp) and 168/200 (bhp) to 32/179 and 32/259 -- under the
backlog's <40 target. What remains is the ADR-C-006 route-guard
re-export (deliberately identical), generic test/story-file
boilerplate, and one shared fragment of the root-singleton-store
idiom -- not re-converged identity or login-flow logic. SS3's
prediction that the two actors would authenticate differently enough
to justify not sharing auth has now actually been tested, not just
asserted, and held.

Also: renamed Session.bsn to Principal.bsn in two doc comments
(libs/shared/src/infrastructure/subject.ts, subject.interceptor.ts)
that cited the old type name; regenerated
libs/shared/docs/behaviour-spec.mdx (generated file, per its own
banner); recorded the resolution in ADR-0002 as a new amendment,
replacing its "Known debt" section.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 16:54:26 +02:00
ehoandClaude Opus 5 4debf6614f docs(adr-0002): accept, and record the unbuilt Principal union as debt
ADR-C-005 from the ADR-conformance pass.

Status Proposed -> Accepted. Two apps have shipped against this ADR and its
structural rulings run in CI at severity: error with 0 violations; the other
five ADRs are all Accepted. A decision CI enforces is not "Proposed".

Drops two "out of scope, not built" bullets that have since shipped
(WP-61..67) — the Behandeling backoffice, and the backend status lifecycle +
authz DTOs: AanvraagStatusTag, GET /me (Program.cs:578), Domain/
Authorization/Authz.cs. Real authentication is the one that genuinely stays.

Replaces the `Session -> Principal` deferral with a Known debt section. The
deferral was conditional on the backoffice not existing yet; it does now, and
the union did not follow. `grep -rn "Principal" apps libs` returns one hit,
a comment. Consequently the two auth contexts are byte-identical (diff -rq:
zero content differences), and behandelportal's Behandelaar still carries a
bsn and logs in through DigiD — a backoffice user authenticating as a
citizen, which is what §3 was written to prevent. The divergence that did
happen took an orthogonal side door (medewerker.interceptor.ts) that never
touches Session.

The section says explicitly that the WP-67 amendment's "expected to diverge"
reasoning still holds but has never been tested, so the identical copies are
evidence §3 is unexecuted — not evidence §3 was wrong. Without that, a future
reader is likely to "simplify" the duplication away and cement the citizen
login into the backoffice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 17:48:48 +02:00
ehoandClaude Sonnet 5 6fa27d1c53 test: close the remaining FE/BE seams (WP-75)
Three seams WP-71 documented but left unguarded.

Deletes the FE's isHerregistratieEligible and isStatusConsistent — both
uncalled, the first dead by its own doc-comment. Their tests used fixtures
completely disjoint from the backend's (the backend even had an exact-window
boundary case the FE lacked), so the two sides could diverge indefinitely
without failing anything. CLAUDE.md's policy of keeping server-owned rules
as FE "reference impls" is what kept them alive, so it is amended: the FE may
mirror a server-supplied value for instant feedback, never reimplement the
algorithm. registration.policy.ts keeps its three live exports.

check-seam.sh now also guards the Besluit tag list — the C# enum and the TS
BESLUIT_TAGS array are identical ordered name lists with nothing linking
them, and Enum.TryParse fails at request time rather than build time. Anchored
on the full declaration so it avoids the "greps all matches" trap WP-69 hit.

The phone-format divergence turned out to be real, not latent as recorded:
the backend returned 422 for +31612345678 and (06) 12345678, both of which
the FE's own parseTelefoonnummer accepts. A grep check would have compared
the shared ^0\d{9}$ regex and reported all clear — the difference was in
stripping. RejectPhoneChange now strips what the FE strips, pinned by a
contract test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-19 16:32:22 +02:00
ehoandClaude Sonnet 5 5d73ca21f6 feat(backend): enforce the scholing threshold server-side (WP-69)
ADR-0001's own canonical "config value" example was unenforced: GET
/intake/policy echoed ScholingThreshold, but no request DTO carried a
scholing answer, so the server had nothing to re-validate. A crafted
POST could skip a requirement the wizard presents as mandatory.

IntakePolicy.RejectIncompleteScholing is the authority — three-valued
completeness (below threshold an answer is required; "nee" is legal and
still submits; punten only belong to a followed scholing), living in the
class that owns the constant so scripts/check-seam.sh keeps guarding the
FE/BE literal pair. Both submit paths call it; a violation 400s with
ProblemDetails and leaves the aanvraag a Concept. Gated on
Type == "intake" (the endpoint's switch lumps herregistratie with
intake, which has no scholing question), and guarded by `reject is null`
so a zero-uren submission is still decided on its merits.

Also fixes a live FE bug in the same rule: validateStep required punten
whenever scholingGevolgd was 'ja' regardless of lageUren, while the
template renders those fields only when lageUren — so answering 'ja'
then raising uren either blocked the user on an invisible field or
emitted aanvullendeScholing: undefined alongside punten. punten now
derives from aanvullendeScholing, so that combination is unrepresentable
in ValidIntake.

Note: EndpointTests' Worked_hours_submission_succeeds was itself
asserting the vulnerable payload ({ uren: 40 }, no answer) and needed a
complete answer added; the zero-hours rows are the ordering regression
net and are unmodified.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 22:42:14 +02:00
ehoandClaude Sonnet 5 3652ff8d3f docs(test): make Given/When/Then the default BDD structure (WP-71)
bdd.mdx previously banned "Given/When/Then ceremony" outright, which
directly contradicted WP-70's own acceptance tests (Acceptance/
BesluitLifecycleTests.cs already used // Given/When/Then comments) and
the backend's organically-evolved PascalCase_snake_sentence convention,
which the doc gave zero guidance for. Reverses that rule: every test is
now structured Given -> When -> Then, with a genuinely empty phase
omitted rather than faked; present-tense declarative naming and the
one-behaviour-per-test rule are unchanged. ADR-0006 gets a cross-reference
so both documents agree everywhere, not just in acceptance tests.

Also closes out the doc's other named-but-unenforced rules found by the
audit: fixes the 5 files asserting rendered $localize copy instead of
the underlying tag/message-id (the compliant pattern already existed in
werkvoorraad-item-view.spec.ts), splits the multi-behaviour titles the
doc itself calls a smell (";", "and", "/"), and fixes bdd.mdx's own false
citation of registratie-wizard.machine.spec.ts as "one transition per
test" by actually splitting that test into one-transition-per-test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 20:25:30 +02:00
ehoandClaude Sonnet 5 a82332fa20 docs: ADR-0006 test-data builders, close out WP-70
Writes up the principle behind WP-70's three tracks ("build test data
through the same door production code uses") as ADR-0006, with a decision
table for which fixture idiom fits which test type. Updates the
test-strategy skill (adds the Fixtures rule, fixes its stale pre-monorepo
src/app/... worked-example paths) and the shared Storybook testing.mdx page
to match. Closes WP-70 with the signatures/counts as actually shipped.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 15:31:20 +02:00
ehoandClaude Sonnet 5 e7156c5132 feat(WP-67): merge behandelportal into this repo as a monorepo
Restructures into apps/ssp + apps/behandelportal (two Angular projects)
plus libs/shared + libs/beheer (cross-app libraries), replacing WP-61's
separate sibling repo. That split had already produced real drift: a
hand-vendored copy of the backend's OpenAPI doc, a shared/ui+layout tree
forked and silently diverging (7 files), and beheer + the styles.scss
token bridge duplicated byte-for-byte across both repos.

- git mv the SSP's src/app/* into apps/ssp/; fold shared/, beheer/,
  environments/, the Storybook docs/*.mdx, and styles.scss into
  libs/shared + libs/beheer (all confirmed identical between the two
  repos before merging). auth stays deliberately duplicated per
  ADR-0002 (actor-specific, expected to diverge) - amended there.
- One generated API client (libs/shared), no more vendored swagger.json.
- .dependency-cruiser split into a base factory + one config per app,
  and Storybook into .storybook-ssp/.storybook-behandelportal - both
  forced by the @auth/* alias resolving to different directories per app.
- SiteHeaderComponent/ShellComponent gained HEADER_NAV_ITEMS/
  HEADER_ADMIN_LINKS/DEBUG_PANEL injection tokens so each app supplies
  its own nav/admin-links/dev-panel instead of one being hardcoded.
- CLAUDE.md, ARCHITECTURE.md, dependencies.md, and ADR-0002 updated;
  WP-67 backlog entry documents the full decision trail.

npm run ci green (lint, dep:check x2, 360 tests across ssp/
behandelportal/shared/beheer, both localized builds, backend tests,
snippet + api-client drift); both dev servers, both Storybook
instances, and docker compose verified working.

The old sibling repo (/home/eho/repos/behandelportal) is left
untouched, not deleted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-02 21:01:57 +02:00
ehoandClaude Sonnet 5 3ff80c124f feat(openzaak): bounded retry + flagged write divergence (WP-60)
Local aanvraag/document writes and their paired ZGW writes aren't
transactional; a ZGW failure after the local write succeeds used to
diverge silently. ZgwHttpClient now retries transport-shaped failures
(not 500, which can follow a partial commit on the non-idempotent
statussen/rollen POSTs), and a ZGW failure that survives retry sets
Aanvraag.ZgwError plus a zgw:divergence audit row instead of failing
or diverging quietly. No outbox/reconcile job: three request-triggered
write paths don't justify a persisted queue that would also need to
carry citizen PII for the JWT audit claims.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-30 18:11:55 +02:00
ehoandClaude Sonnet 5 de3bff0d7f feat(zgw): OpenZaak create-zaak, first write slice (WP-50)
Extends the IZaakSource seam (WP-49, read-only) with CreateZaak: submitting
an aanvraag now also registers a Zaak + Status + Rol in OpenZaak when
Zgw:Enabled=true, routed through the existing /applications/{id}/submit
endpoint with the FE response DTO unchanged (ADR-0001/ADR-0005 — the
endpoint never branches on the config flag itself, DI already picked the
implementation).

- ZgwOptions gains a Type→zaaktype-URL map + the two RSINs a Zaak needs.
- LocalZaakSource.CreateZaak is a pure passthrough of what the endpoint
  already computes locally (zero behaviour change for the offline default).
- OpenZaakZaakSource.CreateZaak POSTs the zaak (identificatie = the same
  local reference, so both stay in sync), resolves + POSTs the initial
  status and the initiator rol (BSN) via Catalogi lookups, and maps the
  result back into the submit response.
- Marked ponytail shortcuts: first-statustype/roltype-Catalogi-returns
  (no per-type config) and no compensating transaction on partial failure
  — both fine for a first slice against a demo backend.

Verified: full `npm run ci` green, zero api-client drift, 144/144 backend
tests (142 existing + 2 new stub-handler tests asserting the POST bodies
+ type→zaaktype mapping per the acceptance criteria).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 09:03:13 +02:00
ehoandClaude Opus 4.8 1c3c195d32 feat(backend): expand stamdata + OpenZaak-ready cases seam (WP-49)
CI / frontend (push) Successful in 2m59s
CI / backend (push) Successful in 1m27s
CI / semgrep (push) Successful in 58s
CI / e2e (push) Successful in 2m30s
CI / api-client-drift (push) Canceled after 1m14s
CI / storybook-a11y (push) Canceled after 29m8s
Stamdata: add beroepen, opleidingen (temporal), and specialismen tables to the
schema-driven catalog (zero UI code). opleidingen.beroep and specialismen.beroep
both reference beroepen.code — the first stamdata->stamdata references, enforced by
two new StamdataRef entries in the CI gate.

OpenZaak/ZGW (WP-49, slice 1 — read-only zaken): introduce IZaakSource as the cases
read seam. Default LocalZaakSource reads the local SQLite store (offline); an
OpenZaakZaakSource (Zgw/ client: HS256 per-call JWT, ZGW->existing-DTO mapper,
paginating HTTP source) is selected behind Zgw:Enabled (default false). The FE never
changes — same ApplicationSummaryDto, no api-client drift. Unit-tested with fixtures
+ a stub HttpMessageHandler; no live OpenZaak needed.

Docs: ADR-0005, reference/openzaak-integration.md, WP-49..52 roadmap, stamdata.md
update, README index rows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 15:01:06 +02:00
ehoandClaude Opus 4.8 7ee4660d6e docs: regenerate dependency graph
CI / frontend (push) Failing after 1m22s
CI / backend (push) Successful in 1m41s
CI / e2e (push) Successful in 3m30s
CI / storybook-a11y (push) Failing after 6m18s
CI / semgrep (push) Successful in 1m4s
CI / api-client-drift (push) Successful in 2m8s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 13:57:12 +02:00
ehoandClaude Opus 4.8 00c5faacb9 feat(i18n): per-bundle LOCALE_ID + language switching under docker compose
CI / frontend (push) Successful in 3m18s
CI / backend (push) Successful in 2m28s
CI / storybook-a11y (push) Successful in 7m54s
CI / semgrep (push) Successful in 1m25s
CI / e2e (push) Successful in 3m54s
CI / api-client-drift (push) Successful in 2m10s
Fix the hardcoded LOCALE_ID: 'nl' — provide it from $localize.locale (the build-time
locale, 'nl'/'en', undefined→'nl' in dev) and register both nl+en locale data, so the en
bundle formats dates/numbers correctly. Make `docker compose up` serve the LOCALIZED build:
the web service now runs `ng build --localize` then serve-i18n.mjs, which gained a PORT env
+ an /api reverse-proxy (API_PROXY_TARGET → the api container) so both /nl/ and /en/ are
served with the language switcher working end-to-end. Drop the now-unused proxy.conf.docker.json
(serve-i18n proxies /api itself); update ARCHITECTURE. `npm start` stays the nl-only HMR loop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 17:51:46 +02:00
ehoandClaude Opus 4.8 7d2a36ff22 feat(arch): WP-38 — dependency graph + declarative boundaries (dependency-cruiser)
CI / frontend (push) Successful in 2m11s
CI / storybook-a11y (push) Successful in 5m46s
CI / backend (push) Successful in 1m29s
CI / e2e (push) Successful in 2m55s
CI / semgrep (push) Successful in 1m1s
CI / api-client-drift (push) Successful in 2m5s
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>
2026-07-23 13:51:04 +02:00
ehoandClaude Opus 4.8 5761b13dd2 style: format the repo with prettier (green format:check)
`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>
2026-07-21 17:20:42 +02:00
ehoandClaude Opus 4.8 7dfbd4501f docs: fix ARCHITECTURE.md mermaid diagrams + document the autosave last-mile guard
CI / frontend (push) Failing after 1m6s
CI / storybook-a11y (push) Successful in 5m3s
CI / backend (push) Successful in 1m20s
CI / codeql (csharp) (push) Has been cancelled
CI / codeql (javascript-typescript) (push) Has been cancelled
CI / api-client-drift (push) Has been cancelled
CI / e2e (push) Has been cancelled
Rewrite all five mermaid blocks to portable syntax so they render (verified via
a mermaid-native render): split two `classDef`/`class` statements that shared a
line (the "why not just signals" graph); drop the `reduce() — PURE` participant
alias and the `;` inside a Note (§2c sequence); remove `<br/>` and parenthetical
transition labels from the two stateDiagram-v2 blocks (RemoteData, intake),
moving the aside into a note; normalize `<br/>`→`<br>` and drop `<b>` in the
atomic-hierarchy flowchart.

Extend §2g to document the now-closed gap: a CanDeactivate guard flushes the
pending debounce before in-app navigation, and beforeunload warns + best-effort
flushes on hard close (noting the HttpClient/keepalive limitation).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 16:29:09 +02:00
ehoandClaude Opus 4.8 5968ef9030 docs: ADR-0004 stamdata data-file + WP-29 stamdata beheer editor
Amend ADR-0004 to record what shipped in 0e77faf: a table may live as typed
C# or as a typed JSON data-file (the compile-time value check traded for
editor ergonomics, caught by StamdataValidationTests instead); valid-time and
the generic StamdataCatalog/StamdataTable model; the low-code PR-emitting
editor is no longer "future". Add WP-29 backlog file (done) + README row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 13:44:01 +02:00
ehoandClaude Opus 4.8 c459fa0a60 feat(stamdata): extract policy-question text into Stamdata
CI / codeql (javascript-typescript) (push) Failing after 1m30s
CI / frontend (push) Failing after 1m21s
CI / storybook-a11y (push) Failing after 5m27s
CI / backend (push) Successful in 1m35s
CI / codeql (csharp) (push) Failing after 1m50s
CI / api-client-drift (push) Successful in 2m1s
CI / e2e (push) Failing after 3h14m48s
Move the geldigheidsvragen wording out of DiplomaRules into
Stamdata.PolicyQuestions (business-editable text, config-as-code); DiplomaRules
keeps only the rule of which questions apply. Extend StamdataValidationTests
(no blank id/wording, distinct ids in the manual set) and update ADR-0004.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 07:46:38 +02:00
ehoandClaude Opus 4.8 a7f737e18c docs: ADR-0004 stamdata-as-code
CI / frontend (push) Failing after 1m20s
CI / storybook-a11y (push) Failing after 5m27s
CI / e2e (push) Has been cancelled
CI / codeql (csharp) (push) Has been cancelled
CI / codeql (javascript-typescript) (push) Has been cancelled
CI / api-client-drift (push) Has been cancelled
CI / backend (push) Has been cancelled
Document the config-as-code strategy for business-tunable reference data:
typed checked-in config validated at compile time (never a production DB),
where UI text (), reference tables (Stamdata/), and letter content
each live, and why org-templates are the deliberate runtime-editable exception.
Index it in docs/README.md and add a CLAUDE.md pointer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 07:41:21 +02:00
ehoandClaude Opus 4.8 14210fa2b0 docs: document form autosave + FE↔BE request lifecycle in ARCHITECTURE
New §2g explains field persistence (keystroke → model → 600ms snapshot
debounce; blur only marks touched, never saves) and §6a refreshes the stale
backend section with the real request lifecycle (NSwag client, httpClientFetch
seam, read/write traces) — both with relative links to the source files.
Add discovery pointers from the learning path (lesson 2.2 and the capstone).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 21:38:49 +02:00
ehoandClaude Opus 4.8 d4e5a76873 docs: reorganize into project/ + reference/, expand Storybook Foundations
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>
2026-07-20 20:33:32 +02:00
ehoandClaude Opus 4.8 55a0a2d166 test: split multi-assertion specs into single-behavior tests
One behavior per test across FE machine/store specs and backend endpoint
tests, so a failure names exactly what broke.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 20:33:25 +02:00