Initial Commit
This commit is contained in:
106
openspec/archive/2026-05-25-enterprise-pdf/proposal.md
Normal file
106
openspec/archive/2026-05-25-enterprise-pdf/proposal.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user