# Proposal — Migrate @suaveplan/pdf to genesis (multi-chunk) - **Design**: ./design.md - **Spec delta**: ./specs/pdf/spec.md - **Status**: PROPOSED (Chunk A ships in this change; Chunks B–E follow) ## Why Upstream `@suaveplan/pdf` (`/mnt/s/web/suaveplan/packages/server/pdf/`, ~2 100 LOC implementation across eleven feature modules, ~7 300 LOC of tests) ships a working server-tier PDF 1.4 generator built on the 14 standard PDF fonts and a hand-rolled binary writer. It exports a fluent `DocumentBuilder`, four element primitives (text, table, image, divider), a layout engine with cursor + flow-block tracking, a style resolver with theme merging, three high-level templates (invoice, receipt, report), unit + colour utilities, and a React preview component. The genesis `enterprise/pdf.md` audit calls out the cross-cutting gaps shared with every other migration target: 1. **No typed error registry.** Every failure path raises plain `Error` instances (`new Error("Invalid hex color: …")`, `Unknown unit`, etc.) so consumers cannot classify failures without parsing message strings. 2. **No OpenTelemetry surface.** PDF generation is a load-bearing primitive (invoices, receipts, financial reports) yet emits zero counters, histograms, or spans, leaving operators blind to throughput, latency, and failure rates. 3. **No Zod-validated public boundary.** Hex strings, unit strings, element props, document definitions, and template inputs are accepted as plain TS types — invalid input surfaces as cryptic runtime errors deep in the render pipeline. 4. **No AbortSignal support.** Rendering a large report cannot be cancelled. 5. **No injectable seams.** The PDF writer hard-codes `TextEncoder`/`Date` so tests must rely on real wall-clock metadata. ## What Land `@suaveplan/pdf@0.1.0` at `packages/devtools/docs/pdf/` (server-tier; allocation confirmed in `scripts/folder-reorg/assignment.yml` line 261 under the `docs` plugin) across five chunks. Each chunk is an independently shippable subset with its own 100 / 100 / 100 / 100 coverage gate. The chunks are sized so a single agent session can land each one with full enterprise additions (error codes, Zod, OTel, AbortSignal, `.md` per source file, real round-trip tests). ### Chunk A — Foundation (this change) Pure utilities + the cross-cutting wiring. Zero external runtime deps beyond `@suaveplan/error`, `@suaveplan/opentelemetry`, `@suaveplan/types`, `tslib`, and `zod`. No PDF I/O. Easy to verify to 100 % in one pass. - `types/` — `PageSize`, `PAGE_DIMENSIONS`, `PageOrientation`, `Margins`, `DEFAULT_MARGINS`, `DocumentMetadata`, `PageConfig`, `DEFAULT_PAGE_CONFIG`, `DocumentDefinition`, `PageDefinition`, `ElementNode`, `RgbColor`, `PdfColor`, `Unit`. - `error-codes/` — `PDF_CODES` via `defineCodes("PDF", [...])` (`INVALID_UNIT`, `INVALID_HEX_COLOR`, `INVALID_RGB`, `INVALID_PAGE_CONFIG`, `INVALID_DOCUMENT`, `INVALID_ELEMENT`, `INVALID_TEMPLATE_INPUT`, `RENDER_FAILED`, `LAYOUT_OVERFLOW`, `IMAGE_DECODE_FAILED`, `FONT_UNKNOWN`, `WRITER_DISPOSED`, `ABORTED`) + `PdfError` class. - `schemas/` — Zod schemas (`UnitSchema`, `HexColorSchema`, `RgbColorSchema`, `MarginsSchema`, `PageConfigSchema`, `DocumentMetadataSchema`). - `telemetry/` — OTel counters (`pdf_documents_rendered_total`, `pdf_pages_emitted_total`, `pdf_render_errors_total`, `pdf_validation_failures_total`) + histograms (`pdf_render_duration_ms`, `pdf_document_size_bytes`) on the `@suaveplan/pdf` meter, plus recording helpers. - `units/` — `toPoints`, `fromPoints`, `convert`, `parseUnit`, `formatUnit` raising `PdfError(PDF_CODES.INVALID_UNIT)` on bad input. - `colors/` — `hexToRgb`, `rgbToPdfColor`, `hexToPdfColor`, `rgbToHex`, `lighten`, `darken`, `COLORS`, `isTransparent` raising `PdfError(PDF_CODES.INVALID_HEX_COLOR)` on bad input. ### Chunk B — Low-level writer + content streams (next change) - `pdf-writer/` — Real PDF 1.4 binary writer with deterministic byte output (injectable `Clock` for metadata), font registry for the 14 standard fonts, indirect-object + xref + trailer assembly, `escapePdfString`, `fmt`, AbortSignal-aware `generate`. - `content-stream/` — Encoder for text-positioning, line-drawing, and state-save / state-restore operators. ### Chunk C — Elements - `text-element/`, `table-element/`, `image-element/`, `divider-element/` — each with Zod input schemas, OTel counters tagged by element kind, and round-trip verification via `pdf-parse`. ### Chunk D — Layout, styles, document builder, renderer - `layout-engine/`, `style-resolver/`, `document-builder/`, `renderer/` — cursor tracking, theme merging, fluent builder, top-level `renderDocument(definition, opts)`. ### Chunk E — Templates + React preview - `invoice-template/`, `receipt-template/`, `report-template/`, `pdf-preview/` (React 19 component with peer-only React dep). ## Out of scope - PDF/UA and PDF/A profiles — tracked in a follow-up enterprise plan. - Embedded font subsetting — handled by `@suaveplan/font-subset` / `@suaveplan/font-compress` and wired in via a future plugin slot. - Digital signatures — gated on `@suaveplan/jws` reaching enterprise parity for PDF (`/Sig` dictionaries). - HTTP plugin (`@suaveplan/api-plugin-pdf`) — separate change once Chunks A–E land.