Files
atomic-design-poc/docs/backlog/WP-24-letter-canvas.md
Edwin van den Houdt c07a33ee3e
Some checks failed
CI / frontend (push) Failing after 59s
CI / storybook-a11y (push) Successful in 4m22s
CI / backend (push) Successful in 1m18s
CI / codeql (csharp) (push) Failing after 1m47s
CI / codeql (javascript-typescript) (push) Failing after 1m20s
CI / api-client-drift (push) Successful in 1m42s
CI / e2e (push) Failing after 3h8m54s
feat(fp): WP-24 — letter canvas (edit on the letter)
One letter surface for every role: LetterCanvasComponent renders the
org template's letterhead/signature/footer around the case-type
sections, with editableRegions content|template|none. public/letter.css
is the FE⇄BE rendering contract (WP-25 inlines it verbatim).
letter-preview deleted — its read-only rendering absorbed into 'none'
mode. brief.machine.ts byte-identical; orgTemplate parses at the
adapter boundary and lives beside the machine in BriefStore.

Also fixes passage-picker multi-select (checkboxes all shared
id="undefined", so labels only toggled the first box) and keeps the
±page-break marks from drawing through canvas content.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 11:47:51 +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/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.