# WP-25 — Server-rendered letter preview (HTML; PDF seam deferred)

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

## Why

"What you compose is what is sent" needs a server-side rendering of the letter —
placeholders resolved, org template applied — from the same CSS contract the canvas
uses (PRD §2b: one rendering, used twice). The preview is the artifact: at send, the
same composition is archived with the brief, making sent letters immutable.

## Read first

- PRD Brief v2 §2b, §8; `docs/backlog/WP-24-letter-canvas.md` (the `letter.css` contract)
- `backend/src/BigRegister.Api/Program.cs` — upload `content` endpoint (binary house
  pattern: `.ExcludeFromDescription()` + hand-written FE fetch)
- `src/app/shared/upload/upload.adapter.ts` (hand-written transport precedent)

## Decisions (pre-made, don't relitigate)

- **HTML, not PDF** (user decision at plan review): no Microsoft.Playwright/Chromium
  dependency in the POC. `GET /api/v1/brief/preview` returns `text/html` — the fully
  composed, print-ready letter (`@page` CSS; browser print-to-PDF is the manual
  affordance). The endpoint is the seam where a headless-Chromium PDF render slots
  in later; mark it `// ponytail: HTML today, Chromium PDF behind this same route if
  the POC ever needs real PDF bytes`.
- **`LetterHtml.Render(brief, orgTemplate)`** is a pure static composer: mirrors the
  canvas class vocabulary exactly, inlines `public/letter.css` from disk, inlines the
  logo bytes as a data-URI. Placeholders: auto-resolvable keys resolve from
  seed/case data; unresolved manual keys render as `[NOG IN TE VULLEN: label]`
  (PRD §8) — preview is allowed with errors, only send blocks on them.
- **Parity is tested, not hoped for**: a golden-file test snapshots the composed
  HTML; a second test asserts every `letter`-prefixed class in the golden HTML
  exists in `letter.css`. `dotnet test` never launches a browser.
- **Archive at send**: `Send` stores the composed HTML in `BriefEntity.ArchivedHtml`
  (SQLite text column) alongside the WP-23 version pin; the preview endpoint serves
  the archive when status is `sent`, so a republish never changes a sent letter.
- **Two endpoints, both excluded from OpenAPI** (JSON-only generated client stays
  clean): `GET /brief/preview` and `GET /admin/org-template/{subOrgId}/preview`
  (proefbrief: draft template + a fixture brief). FE consumes them via a small
  hand-written fetch (needs the `X-Role` header) → blob → object URL in a new tab.
- Watermark: previews of unsent letters carry a `VOORBEELD` watermark (CSS), the
  archived/sent rendering never does — the PRD's open question resolved the simple way.

## Files

- `backend/src/BigRegister.Api/Domain/Letters/LetterHtml.cs` (new)
- `backend/src/BigRegister.Api/Data/BriefStore.cs` — `ArchivedHtml` + archive at send (+migration)
- `backend/src/BigRegister.Api/Program.cs` — 2 preview endpoints
- `backend/tests/BigRegister.Tests/LetterHtmlTests.cs` (new) + `LetterHtml.golden.html`
- `src/app/brief/infrastructure/letter-preview.adapter.ts` (new, fetch → `Result<string, Blob>`)
- `src/app/brief/application/brief.store.ts` — `previewLetter()` command
- `src/app/brief/ui/letter-composer/*` — "Voorbeeld" button

## Steps

1. `LetterHtml.Render` + placeholder resolution + data-URI logo + watermark flag.
2. Golden-file + class-parity tests.
3. Endpoints (serve archive when sent; proefbrief renders the draft template).
4. Archive-at-send in `BriefStore.Send` (+ migration for `ArchivedHtml`).
5. FE adapter + store command + button (explicit action — no live re-render; PRD §8).

## Acceptance criteria

- [x] Preview opens the composed print-ready letter in a new tab; browser print
      shows correct margins via `@page`.
- [x] Unresolved manual placeholders render `[NOG IN TE VULLEN: …]`; preview works
      despite lint errors (only send blocks).
- [x] A sent brief serves its archived HTML unchanged after an org-template republish.
- [x] Golden + parity tests green without any browser installed.
- [x] `swagger.json` unchanged by the two endpoints (drift check green).

## Verification

`cd backend && dotnet test`; GREEN one-liner; manual: compose → preview → print
dialog; send → republish template → preview still the archived rendering.

## Out of scope

Real PDF bytes / headless Chromium (the deliberate deferral — the endpoint is the
seam). Pixel-parity testing (the shared CSS + class-parity test is the fence).
Pagination fidelity beyond the browser's own print engine.

## Risks

`LetterHtml` reads `public/letter.css` from disk — path must resolve for `dotnet run`,
tests, and docker (bind mount/copy); fail loudly with a clear error if missing.
The golden file will churn whenever the letter structure changes — that is its job;
update it deliberately, never blindly.
