Files
atomic-design-poc/docs/project/archive/backlog/WP-27-brief-ux-layer.md
T
ehoandClaude Opus 5 12f17d9d73 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>
2026-09-08 23:00:38 +02:00

98 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# WP-27 — Brief UX layer (undo/redo, standaardbrief, search, diff badges)
Status: done
Phase: 6 — Brief v2 (edit-on-the-letter, org templates, server-rendered preview)
## Why
PRD Brief v2 §7: the working-day features that make the composer pleasant daily.
Several are nearly free **because** state is one immutable value — that's the
teaching payload: undo/redo is a shell-side snapshot list, the rejection diff is a
pure function over two values. Say so in code comments and stories.
## Read first
- PRD Brief v2 §7 (and the plan-review trim recorded below)
- `src/app/brief/application/brief.store.ts` (autosave + `SaveState` already exist)
- `src/app/brief/domain/brief.machine.ts` — the `Seed` Msg (undo/redo's restore path)
## Decisions (pre-made, don't relitigate)
- **Trim agreed at plan review.** IN: undo/redo, autosave retry affordance,
standaardbrief, passage search, canvas zoom controls, Ctrl+Z/Ctrl+Shift+Z,
block-level rejection-diff badges. OUT (deferred, one line each in Out of scope):
soft lock/takeover, case-context panel, 401 autosave grace, per-user usage counts,
shortcut-overlay dialog, inline character-level text diff.
- **Undo/redo is shell state, not machine state**: a `past`/`future: Brief[]` stack in
`BriefStore` (cap 50; push on `edit()`; clear `future` on a new edit); restore
dispatches the **existing `Seed` Msg** — zero machine changes — then `scheduleSave()`.
(Superseded by WP-31: the hand-rolled stack was later extracted into the shared
`createHistory<T>(cap)` helper — `BriefStore` now holds `this.history =
createHistory<Brief>(50)` and calls `.record`/`.undo`/`.redo`/`.clear` on it; same
cap-50, push-on-edit, clear-on-new-edit semantics, just generalized for reuse.)
- **Standaardbrief**: backend seeds `IsDefault` on 23 kern passages
(`LibraryPassageDto` gains the flag); one button, visible only while the kern
section is empty, dispatches the existing `PassagesInserted` with the default set —
one Msg, one undo step.
- **Passage search is a client-side filter** in the picker (label + content match) —
the library is small; no server search, no usage tracking.
- **Rejection diff**: pure `diffBlocks(before, after): BlockDiff[]` in
`domain/brief-diff.ts` (added/removed/changed by `blockId`); the "before" snapshot
is captured shell-side when the `Rejected` dispatch happens (POC limit: lost on
reload — comment it). Rendered as "gewijzigd sinds afwijzing" badges on the canvas;
the approver gets a "Toon wijzigingen" toggle on resubmission.
- **Autosave retry**: `SaveState.Error` already exists; add the "Opnieuw proberen"
button that calls the existing flush path. No new state.
## Files
- `src/app/brief/application/brief.store.ts` (+spec: history bounds, clear-on-edit,
redo, rejection snapshot)
- `src/app/brief/domain/brief-diff.ts` (new, +spec)
- `backend/src/BigRegister.Api/Data/BriefStore.cs` (`IsDefault` seed) +
`Contracts/Dtos.cs` (`LibraryPassageDto`) + gen:api + adapter parse
- `src/app/brief/ui/passage-picker/*` (search input)
- `src/app/brief/ui/letter-canvas/*` (diff badges, standaardbrief button, zoom controls)
- `src/app/brief/ui/brief.page.ts` (undo/redo buttons + keydown listener, retry button)
## Steps
1. `diffBlocks` + spec (added/removed/changed/unchanged; changed = same blockId,
different content).
2. Store: history + undo/redo + rejection snapshot (+spec).
3. Backend `IsDefault` + gen:api + parse.
4. UI: standaardbrief button, search, zoom, badges, keyboard, retry.
5. Stories for the new states (axe).
## Acceptance criteria
- [x] Remove a block → Ctrl+Z restores it → Ctrl+Shift+Z re-removes; buttons mirror;
history capped at 50; a new edit clears redo; restore re-triggers autosave.
- [x] Empty kern + "Standaardbrief invoegen" → default passages inserted as one undo
step; button gone once kern is non-empty.
- [x] Search filters passages by label and content.
- [x] Reject → edit → resubmit: approver toggles "Toon wijzigingen", changed/added/
removed blocks are badged (block granularity).
- [x] Autosave failure shows "Niet opgeslagen — opnieuw proberen"; retry works;
content never lost locally.
- [x] Full GREEN.
## Verification
GREEN one-liner; store + diff specs; manual reject→edit→diff walk with two roles.
## Out of scope (deferred, per plan review)
Soft lock/heartbeat/takeover (real session infra, no FP teaching value here).
Case-context panel (no case data exists). 401 autosave grace (auth is faked).
Per-user passage usage counts (bookkeeping, demos nothing). Shortcut overlay dialog
(no modal component exists; not worth building one). Inline character-level diff
(block granularity carries the teaching point).
## Risks
Undo history holds `Brief` snapshots — deep-frozen immutable values, so sharing is
safe, but never push non-content dispatches (status transitions, `Seed` itself) into
history or undo will replay workflow state. The rejection snapshot lives in memory
only — document it where it's captured.