Files
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

5.2 KiB

WP-24 — Letter canvas (edit on the letter)

Status: done Phase: 6 — Brief v2 (edit-on-the-letter, org templates, server-rendered preview)

Why

The drafter should compose on the letter — letterhead above, footer/signature below, content blocks edited in place — instead of in an abstract form next to a separate preview. PRD Brief v2 §4. This is a presentation rebuild only: the domain model, brief.machine.ts, and every BriefMsg stay byte-identical.

Read first

  • PRD Brief v2 §2b (fidelity note), §4, §10; the sample voorbeeldbrief-inschrijving.pdf
  • src/app/brief/ui/letter-composer/letter-composer.component.ts (the canEdit pivot)
  • src/app/brief/ui/letter-preview/letter-preview.component.ts (rendering that migrates in)
  • docs/project/backlog/WP-23-org-template-backend.md (the orgTemplate on BriefViewDto)

Decisions (pre-made, don't relitigate)

  • One stylesheet is the FE⇄BE rendering contract: public/letter.css — class vocabulary .letter, .letter__letterhead, .letter__body, .letter__signature, .letter__footer, .letter__page-break; margins as --letter-margin-* custom props; @page/print rules. Loaded via <link> in index.html (app + Storybook). WP-25's backend renderer inlines the same file; its parity test is the fence.
  • LetterCanvasComponent (new organism, Domein/Brief/Letter Canvas) with editableRegions: 'content' | 'template' | 'none' — one component serves drafter composing, approver review (and in WP-26, the admin editor). Letter typography on the canvas is the letter's, not the portal UI's — but still token-bridged.
  • 'content' mode hosts the existing letter-section/letter-block components unchanged; letterhead/footer/signature render read-only with a subtle tint and a first-use caption ("komt uit de huisstijl van de organisatie").
  • 'none' mode absorbs letter-preview's rendering (paragraph grouping, placeholder chips, sample-values toggle); ui/letter-preview/ is then deleted — the PRD explicitly supersedes it; no third rendering is maintained.
  • Page-break indicator is approximate by design: a dashed line per A4-content interval with the caption "±pagina-einde — afdrukvoorbeeld is leidend" (PRD §2b honesty requirement).
  • brief.machine.ts and BriefMsg are untouched — git diff must prove it.

Files

  • public/letter.css (new), src/index.html (link)
  • src/app/brief/domain/org-template.ts (new, pure) — OrgTemplate type
  • src/app/brief/infrastructure/brief.adapter.ts (+spec) — parse orgTemplate
  • src/app/brief/ui/letter-canvas/* (new: component + stories)
  • src/app/brief/ui/letter-composer/letter-composer.component.ts (pivot swap) + stories
  • src/app/brief/ui/brief.page.ts (pass orgTemplate through)
  • delete src/app/brief/ui/letter-preview/*

Steps

  1. letter.css from the sample PDF's geometry (A4 proportions, letterhead, address window + reference block, footer rule, signature).
  2. OrgTemplate domain type + parseOrgTemplate boundary in the adapter (+spec).
  3. Canvas organism: three modes, zoom input, page-break indicator.
  4. Migrate letter-preview rendering into 'none' mode; delete the component, fold its stories into the canvas stories.
  5. Swap the composer's @if (canEdit()) pivot for <app-letter-canvas [editableRegions]="canEdit() ? 'content' : 'none'">.
  6. Stories: three modes + zoom + page-break, all axe-gated.

Acceptance criteria

  • Drafter edits blocks in place on the letter surface; passage picker and diagnostics click-to-locate still work on the canvas.
  • Approver sees the identical surface read-only with the action bar.
  • Letterhead/footer/signature come from orgTemplate and are visibly non-editable.
  • git diff shows zero changes in brief.machine.ts / brief.ts Msg surface.
  • letter-preview is gone; no story regression (test-storybook:ci green).
  • Full GREEN + e2e smoke.

Field notes (landed alongside): the CIBG huisstijl styles bare <header>/<footer> elements (robijn background), so the canvas regions are <div>s — the letter surface must stay letter.css-only. The passage picker's checkboxes now get unique checkboxIds (all previously resolved to id="undefined", so labels toggled only the first box — multi-select was broken) and an opaque background; .letter__body stacks above the page-break marks so the dashed line never draws through content, while the "±pagina-einde" caption floats above everything for legibility.

Verification

GREEN one-liner; npm run e2e (brief flow); manual: ?role=drafter compose on canvas, ?role=approver review; ?scenario=slow|error still degrade gracefully through <app-async>.

Out of scope

Server-rendered preview + [NOG IN TE VULLEN] resolution (WP-25); admin 'template' mode wiring beyond the input existing (WP-26 gives it a consumer); zoom controls polish + standaardbrief + diff badges (WP-27).

Risks

The canvas duplicates the letter markup that WP-25's backend renderer will emit — acceptable only because letter.css is shared and WP-25 adds the class-parity test; until WP-25 lands, the canvas is the sole consumer, so no drift is possible. Deleting letter-preview breaks any deep import of it — repo-wide grep before delete.