`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>
5.5 KiB
WP-29 — Stamdata beheer editor (low-code, PR-emitting)
Status: done (0e77faf)
Phase: follow-on — ADR-0004 realization (not part of the 2026-07-02 showcase audit)
Why
ADR-0004 (stamdata-as-code) named a future low-code editor that commits a PR as its
mitigation for "a non-developer may need dev assistance to edit C#", and floated a
data-file format as the alternative to typed-C# constants when hand-editing ergonomics
outweigh maximal compile-time safety. This WP realizes both: an admin-only maintenance editor
that reads the stamdata catalog, edits rows in a grid, and produces the edited JSON data-file
the admin drops into the repo — the existing CI build + StamdataValidationTests stay the
authority. No production database, no runtime write path (ADR-0004 unchanged).
Read first
- ADR-0004 (
docs/reference/architecture/0004-stamdata-as-code.md) — the model this obeys. - ADR-0001 (BFF-lite + decision DTOs) — the endpoints are screen-shaped, admin-gated reads.
src/app/brief/**(WP-23/26) — the org-template admin editor is the closest prior art (root store + machine + capability guard + admin role).
Decisions (pre-made, don't relitigate)
- Read-only endpoints only.
GET /stamdata(catalog) andGET /stamdata/{table}?peildatum=(schema + rows). No POST/PUT/DELETE — the edit lands as a reviewed PR, not a write. Thestamdata:editcapability /CanEditStamdatagate the reads (naming is the enforce-twin of a future edit capability; deliberate). - Generic, schema-driven. One endpoint pair + one grid editor serve every table. Columns
are reflected from the typed record (
StamdataTable.Of<T>); the FE renders inputs by column type (date/number/enum/text). A new table is one line inStamdataCatalog— no new endpoint, UI, or test. (Catalog of one today; this is the shape ADR-0004 prescribed.) - Data-file format for
professions.professions.json(embedded resource) replaces the hardcoded C# dictionary, deserialized intoProfessionMapping. This trades compile-time value checking (gate #1) for editor ergonomics — the value gate becomesStamdataValidationTests(gate #2), exactly the trade-off ADR-0004's consequences listed. - Valid-time.
geldigVan/geldigTot(half-open[van, tot)); a table is temporal iff it has both columns.peildatumpreviews "which rows applied on date X".Professions.ByProgrampreserves pre-valid-time behaviour by filtering to rows active today. - Apply path = download → PR. The editor's
download()serializes the draft to{table}.json; the admin commits it.mutation-commandis intentionally not used. - Admin-only, resource-independent authz — same shape as org-template management (role IS the decision), denials audited (no PII).
Files
- Backend:
backend/src/BigRegister.Api/Stamdata/{StamdataCatalog,StamdataTable,StamdataFile,ProfessionMapping}.cs(new),Professions.cs(now loads the data-file),professions.json(new),BigRegister.Api.csproj(embedStamdata\*.json);Program.cs(two GET endpoints +StamdataAdmingate),Contracts/Dtos.cs(3 DTOs),Domain/Authorization/Authz.cs(stamdata:edit+CanEditStamdata); testsStamdataEndpointTests.cs(new),StamdataValidationTests.cs(genericEvery_catalog_table_is_valid). - Frontend:
src/app/beheer/**(contracts / domain + specs / infrastructure + spec / application / ui + organism story);app.routes.ts(guarded lazy route),shared/domain/capability.ts+shared/infrastructure/me.adapter.ts(stamdata:edit),eslint.config.mjs(beheerboundary rules),tsconfig.json(@beheer/*alias); regeneratedbackend/swagger.json+src/app/shared/infrastructure/api-client.ts.
Acceptance criteria
GET /stamdataandGET /stamdata/{table}return admin-only (403 + audit otherwise).professionsserved fromprofessions.json;ProfessionMappingtyped; behaviour ofProfessions.ByProgramunchanged for all-current rows.- Generic build gate:
Every_catalog_table_is_validcovers every catalog table (keys non-blank, no overlapping validity, well-formed windows). beheer/stamdataroute capability-guarded; page shows denial for non-admin; grid renders from reflected schema; edits update dirty/change-count;download()yields a valid{table}.json;peildatumbefore 2000-01-01 → zero rows.- Full gate GREEN both sides;
npm run gen:apileaves no drift; new stamdata story passes axe.
Verification
cd backend && dotnet test && dotnet format --verify-no-changes; npm run lint && npm run check:tokens && npm test && npm run build && npm run build-storybook && npm run test-storybook:ci;
npm run gen:api && git diff --exit-code -- backend/swagger.json src/app/shared/infrastructure/api-client.ts.
Live: /beheer/stamdata?role=admin renders + edits + downloads; without ?role=admin denies.
Out of scope
- Runtime persistence of edits (would contradict ADR-0004) — the download-to-PR path is the design.
- Migrating
PolicyQuestions/ document-categories into the catalog (each is a later one-liner). - A write-back "commit a PR on the admin's behalf" integration (the
download()seam is where it slots in).
Risks
- Data-file weakens compile-time value safety for
professions— mitigated byStamdataValidationTestsrunning in CI (a bad value fails the build, never prod). - Generic reflection assumes the record's first property is the key and camelCase JSON names —
documented conventions in
StamdataTable; covered by the endpoint schema test.