docs: archive the finished backlogs (RD-30)
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>
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
# RB-20 — route `ApplicationsStore.cancel` / `AdminCasesStore.delete` through `runSubmit`, surface the error
|
||||
|
||||
Status: **implemented** · 2026-08-27 · Source finding: `04-cqrs-light.md` CQ-002 ·
|
||||
`00-baseline.md` BL-007 · `99-backlog.md` RB-20 · SIGN-OFF: consolidation approved
|
||||
2026-08-27, HALT lifted
|
||||
|
||||
## What was wrong
|
||||
|
||||
`ApplicationsStore.cancel` and `AdminCasesStore.delete` both owned an optimistic write next
|
||||
to their `RemoteData` read, and both reached `ApplicationsAdapter` directly instead of going
|
||||
through `runSubmit` (the fold + Idempotency-Key mint every other mutation in the repo uses,
|
||||
including `createSubmitChangeRequest` in the same folder). The failure path was a bare
|
||||
`catch { this.state.set(before); }`: a failed cancel or delete rolled the row back, but the
|
||||
user saw no message at all — no `ActionState`, no ProblemDetails `detail`, nothing. The
|
||||
`Idempotency-Key` on the wire was also a fresh UUID per HTTP attempt (minted by
|
||||
`api-client.provider.ts`'s default), not the per-logical-submit key `runSubmit` promises —
|
||||
harmless today only because `Program.cs` happens to ignore the header outside the `Submit`
|
||||
helper (CQ-005's note).
|
||||
|
||||
## What changed
|
||||
|
||||
CQ-002's option (a) — the smallest fix, applied identically to both stores. No new command
|
||||
factory, no adapter split (CQ-002's own "Not filed" note reserves that split for option (b),
|
||||
which this ticket does not take).
|
||||
|
||||
| File | Change |
|
||||
| --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `apps/ssp/src/app/registratie/application/applications.store.ts` | `cancel` now calls `runSubmit(() => this.adapter.cancel(id), SUBMIT_FAILED)`; added a private `error` signal, exposed read-only as `lastError`. On failure: roll back AND `this.error.set(r.error)`. On the next attempt, the error is cleared before the call so a stale message never survives a fresh action. |
|
||||
| `apps/ssp/src/app/registratie/application/applications.store.spec.ts` (new) | 4 specs: load+parse, optimistic cancel, roll-back-and-surface-error on failure, stale-error-clears-on-next-attempt. No spec file existed for this store before RB-20. |
|
||||
| `apps/ssp/src/app/registratie/application/admin-cases.store.ts` | Same shape as `applications.store.ts`: `delete` through `runSubmit`, `error`/`lastError` signal pair. |
|
||||
| `apps/ssp/src/app/registratie/application/admin-cases.store.spec.ts` | Existing "rolls back … when the delete fails" spec extended to also assert `lastError()`; one new stale-error-clears spec added. |
|
||||
| `apps/ssp/src/app/registratie/ui/dashboard.page.ts` | One `@if (cancelError(); as err) { <app-alert type="error">{{ err }}</app-alert> }` above the aanvragen list, mirroring `brief.page.ts`'s `lastError` rendering. `cancelError` is a `computed(() => this.apps.lastError())`. |
|
||||
| `apps/ssp/src/app/registratie/ui/admin-cases.page.ts` | Same `@if (store.lastError(); as err) { <app-alert type="error">{{ err }}</app-alert> }`, placed above `<app-async>` inside the `canManage()` branch (`store` was already `protected`, so no new exposure needed). |
|
||||
|
||||
`applications.adapter.ts` (`cancel`, `deleteAny`) is **unchanged** — the fix is entirely in
|
||||
the two stores, which now wrap the existing thin adapter calls in `runSubmit` at the call
|
||||
site, exactly as `createSubmitChangeRequest` wraps `ChangeRequestAdapter.changeRequest`. The
|
||||
adapter methods still return a bare `Promise<void>`; `runSubmit` is what folds that into a
|
||||
`Result`.
|
||||
|
||||
Neither UI change introduces a new user-facing string: the rendered text is either the
|
||||
existing `SUBMIT_FAILED` constant (`@@submit.failed`, already translated in
|
||||
`messages.en.xlf` since RB-17) or, when the backend sends one, a ProblemDetails `detail`
|
||||
string carried verbatim from the server — never a new `$localize` id. `messages.en.xlf` did
|
||||
not need a new `<target>`.
|
||||
|
||||
## The tests, and their red failures
|
||||
|
||||
Both specs assert `store.lastError()` after a rejected adapter call, which only the fix can
|
||||
satisfy — the old bare `catch { this.state.set(before) }` never touched an error signal, so
|
||||
`lastError()` stayed `null` forever.
|
||||
|
||||
**Verified red without the fix** (an `Edit` undo of the store method, not `git checkout`, so
|
||||
the rest of the change — imports, the other store, the UI, the specs — stayed in place):
|
||||
|
||||
- `applications.store.ts`: reverted `cancel` to `try { await this.adapter.cancel(id); } catch
|
||||
{ this.state.set(before); }`. Reran `ng test ssp --include applications.store.spec.ts`:
|
||||
2 of 4 failed —
|
||||
`rolls back the removal and surfaces the error when the cancel fails` and
|
||||
`clears a stale error on the next cancel attempt`, both with
|
||||
`AssertionError: expected null to be 'Het indienen is niet gelukt. Probeer het later opnieuw.'`.
|
||||
The other two specs (load, optimistic-cancel-success) stayed green, as expected — they
|
||||
don't touch the error path. Re-applied the fix (`Edit` back to the `runSubmit` version);
|
||||
reran: 4/4 green.
|
||||
- `admin-cases.store.ts`: same procedure on `delete`. Reran
|
||||
`ng test ssp --include admin-cases.store.spec.ts`: 2 of 4 failed with the identical
|
||||
`expected null to be '...'` shape. Reverted to the fix; reran: 4/4 green.
|
||||
|
||||
## Judgement calls
|
||||
|
||||
- **Signal naming**: private backing field `error`, public readonly `lastError` — matching
|
||||
the name `BriefStore`/`OrgTemplateStore` already expose for exactly this purpose (CQ-002's
|
||||
own citation), rather than inventing a new name per store.
|
||||
- **Error cleared at the start of each write**, not only on success, so a second cancel/delete
|
||||
attempt after a failure doesn't leave a stale banner up if the retry itself is still in
|
||||
flight. Covered by the "clears a stale error on the next attempt" spec in each file.
|
||||
- **No `ActionState`/`SaveState` pair** (the fuller shape `BriefStore` uses for busy-state and
|
||||
save-state together) — CQ-002 explicitly scoped option (a) to "one `error` signal", and
|
||||
neither store needs a busy indicator: the row already disappears optimistically the instant
|
||||
the click happens, so there is nothing for a spinner to cover.
|
||||
- **UI placement**: one alert per page, above the list the mutated row belongs to, using the
|
||||
same `@if (x(); as err) { <app-alert type="error">{{ err }}</app-alert> }` shape as
|
||||
`brief.page.ts` — composition of an existing atom, no new building block (CLAUDE.md §2).
|
||||
- **`applications.adapter.ts` left untouched, on purpose** — CQ-002's "Not filed" note ties
|
||||
the read/write file split to option (b) only; taking option (a) means this ticket changes
|
||||
no adapter code at all, matching the ticket's own framing ("(a) touches 2 files plus a UI
|
||||
line each").
|
||||
|
||||
## Ticket accuracy
|
||||
|
||||
CQ-002's description matched the code as found: both stores' `cancel`/`delete` reached the
|
||||
adapter directly with a bare `catch { this.state.set(before); }`, no `Result`, no error
|
||||
channel — no discrepancy to flag.
|
||||
|
||||
## Residuals (not this ticket)
|
||||
|
||||
- RB-18 (key `IdempotencyStore` on `{SubjectId}:{idemKey}`) is unaffected: `cancel`/`delete`
|
||||
now mint a key through `runSubmit` like every other mutation, so it lands on the same
|
||||
write-only call set RB-18 already targets.
|
||||
- RB-21 (extract `createDraftSync`'s read half) is a separate CQRS-light finding in the same
|
||||
context, untouched by this ticket.
|
||||
|
||||
## Verification
|
||||
|
||||
`npm run ci` (foreground, `timeout: 600000`): **green** — `✔ local CI passed`. Lint,
|
||||
typecheck, `dep:check` (342 + 226 modules, 0 violations), `format:check`, `check:tokens`,
|
||||
`check:seam`, tests (ssp 263/263 — 5 more than the pre-RB-20 258, from the new/extended
|
||||
specs above — behandelportal 37/37, shared 138/138, beheer 23/23), `ng build --localize`
|
||||
(both apps), `npm audit` (0 vulnerabilities), backend `dotnet format --verify-no-changes` +
|
||||
`dotnet test --filter "Category!=Integration"` (260/260 — this filter is what keeps the
|
||||
known `OpenZaakIntegrationTests.Admin_cases_…` container-dependent test, which needs a live
|
||||
OpenZaak container, out of `npm run ci` entirely; it is a standing caveat, not introduced by
|
||||
this change, and not exercised by this run), backend dependency audit (0 vulnerable
|
||||
packages), `gen:snippets` / `gen:behaviour-spec` / `gen:api` drift checks all clean once the
|
||||
regenerated `behaviour-spec.mdx` was staged alongside the code (the local gate's
|
||||
`git diff --exit-code` compares the working tree to the index, so it is clean once the file
|
||||
is staged — this is the documented pre-commit behaviour from RB-17's note, not a defect).
|
||||
Reference in New Issue
Block a user