Files
suaveplan/openspec/archive/2026-05-25-enterprise-pdf/proposal.md
2026-05-27 23:29:53 +01:00

107 lines
5.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.