Two backlog trees are complete: `docs/project/backlog/` (75 files, every WP done) and `docs/project/refactor-backlog-setup/` (the arc before it). Move both under `docs/project/archive/` with `git mv`, so history stays intact through `git log --follow`. `SHOWCASE-ROADMAP.md` moves with them, because it points at the now-archived backlog README. Add `docs/project/archive/README.md`. It states that these trees are historical and names the two directories that are still live. Repoint every inbound reference named in RD-30's Files table: CLAUDE.md, the root README, both backend READMEs, `LetterHtml.cs`, `a11y.mdx`, the `document-feature` and `new-ssp` skills, and the readable-codebase PLAN, README, and RD-19 ticket. Fix two upward-relative links inside the moved WP files (WP-68, WP-69) that gained a directory level and would otherwise break. Repoint `.prettierignore`'s two agent-prompt exclusions to their new path, so prettier keeps leaving those files' exact wording alone. Mark RD-30 done and check off its acceptance criteria; flip its README row to done. Co-Authored-By: Claude Opus 5 <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.