Second template axis (org identity: letterhead, footer, signature, margins) server-side: OrgTemplateStore with JSON version history, publish/rollback, sent-brief version pinning, admin role + capability, 5 admin endpoints, org-logo upload category. FE seam widened only (Role/Capability unions, interceptor); WP-24/26 consume it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4.6 KiB
WP-27 — Brief UX layer (undo/redo, standaardbrief, search, diff badges)
Status: todo 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 +SaveStatealready exist)src/app/brief/domain/brief.machine.ts— theSeedMsg (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:
past/future: Brief[]inBriefStore(cap 50; push onedit(); clearfutureon a new edit); restore dispatches the existingSeedMsg — zero machine changes — thenscheduleSave(). - Standaardbrief: backend seeds
IsDefaulton 2–3 kern passages (LibraryPassageDtogains the flag); one button, visible only while the kern section is empty, dispatches the existingPassagesInsertedwith 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[]indomain/brief-diff.ts(added/removed/changed byblockId); the "before" snapshot is captured shell-side when theRejecteddispatch 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.Erroralready 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(IsDefaultseed) +Contracts/Dtos.cs(LibraryPassageDto) + gen:api + adapter parsesrc/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
diffBlocks+ spec (added/removed/changed/unchanged; changed = same blockId, different content).- Store: history + undo/redo + rejection snapshot (+spec).
- Backend
IsDefault+ gen:api + parse. - UI: standaardbrief button, search, zoom, badges, keyboard, retry.
- Stories for the new states (axe).
Acceptance criteria
- 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.
- Empty kern + "Standaardbrief invoegen" → default passages inserted as one undo step; button gone once kern is non-empty.
- Search filters passages by label and content.
- Reject → edit → resubmit: approver toggles "Toon wijzigingen", changed/added/ removed blocks are badged (block granularity).
- Autosave failure shows "Niet opgeslagen — opnieuw proberen"; retry works; content never lost locally.
- 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.