Files
2026-05-27 23:29:53 +01:00

4.7 KiB

Tasks: enterprise-server-pdf

Change ID: enterprise-server-pdf Package: @suaveplan/pdf Target path: server/services/pdf Prerequisites on Verdaccio: @suaveplan/error, @suaveplan/types, @suaveplan/templates, @suaveplan/logger


0. Pre-flight (non-negotiable)

  • 0.1 Confirm clean working tree (git status reports nothing) and no in-flight worktrees with uncommitted work (git worktree list then git status in each)
  • 0.2 If any worktrees exist under .claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
  • 0.3 Confirm the package set in MANIFEST.md matches find packages -maxdepth 3 -name package.json count. Drift here means a prior change archived without regenerating the manifest.

1. Scaffold

  • Create directory tree per proposal.md.
  • package.json at 0.1.0, "sideEffects": false, exports "." + builder + renderers/puppeteer + renderers/pdfkit.
  • Deps: all workspace + catalog deps listed in proposal. Mark puppeteer-core and pdfkit as optionalDependencies.
  • DevDeps: vitest catalog:testing, @vitest/coverage-istanbul catalog:testing, @suaveplan/testing workspace:^.
  • tsconfig.json extending library-node.json; vitest.config.ts with pool: 'forks'.
  • Scripts: lint, typecheck, test, test:coverage, build, docs:check.

2. Types and errors

  • Define all interfaces and Zod schemas in src/types/pdf.types.ts.
  • Write pdf.types.md (≥200 words, both examples, API reference).
  • src/error-codes.ts: four codes; registerPdfErrorCodes(); eager call.

3. PDF builder

  • Implement PdfBuilder in src/builder/pdf-builder/pdf-builder.ts:
    • Fluent API using internal currentPage: PdfPage accumulator.
    • page(options?): push current page (if populated) and start a new one.
    • text, heading, table, image, spacer, footer, header: append elements to currentPage.elements.
    • build(): flush current page; return PdfDocument.
    • Validate non-empty content on build(); throw PDF_INVALID_CONTENT if no pages.
  • Write pdf-builder.types.ts with PdfPage, PdfElement discriminated union.
  • Write pdf-builder.test.ts (≤500 lines): multi-page build; all element types; empty build throws; metadata.
  • Write pdf-builder.md (≥200 words, simple + advanced, API reference).

4. PdfKit renderer

  • Implement PdfKitRenderer in src/renderers/pdfkit-renderer/pdfkit-renderer.ts:
    • Convert PdfDocument elements to PDFKit calls; collect output in PassThrough stream; return as Uint8Array.
    • Support: text, heading (larger font), table (basic grid), image (from URL or bytes), spacer, header, footer.
    • Guard import of pdfkit with a try/catch; throw PDF_RENDERER_UNAVAILABLE if not installed.
    • AbortSignal: check signal between pages.
    • OTel span pdf.render with renderer: 'pdfkit'; histogram pdf.render_duration_ms.
  • Write pdfkit-renderer.test.ts (≤500 lines): all element types produce non-empty PDF bytes; abort signal; unavailable pdfkit error.
  • Write pdfkit-renderer.md (≥200 words).

5. Puppeteer renderer

  • Implement PuppeteerRenderer in src/renderers/puppeteer-renderer/puppeteer-renderer.ts:
    • Convert PdfDocument to an HTML string using @suaveplan/templates.
    • Launch headless Chrome via puppeteer-core; navigate to data URL; call page.pdf().
    • dispose(): close browser.
    • Guard import of puppeteer-core; throw PDF_RENDERER_UNAVAILABLE if absent.
    • AbortSignal: abort browser navigation.
    • OTel span with renderer: 'puppeteer'.
  • Write puppeteer-renderer.test.ts (≤500 lines): skip if SKIP_PUPPETEER=1; basic render produces valid PDF bytes; dispose closes browser; { timeout: 30000 }.
  • Write puppeteer-renderer.md (≥200 words).

6. Integration tests

  • tests/integration.test.ts: build a multi-page invoice with table + text + image; render with PdfKit; verify output size > 1000 bytes; { timeout: 30000 }.

7. Docs + verify + publish

  • Root README.md: quick-start invoice example, renderer comparison table, template customisation, API table.
  • bunx turbo lint --filter=@suaveplan/pdf — zero errors.
  • bunx turbo typecheck --filter=@suaveplan/pdf — zero errors.
  • bunx turbo test --filter=@suaveplan/pdf — all pass.
  • bunx turbo test:coverage --filter=@suaveplan/pdf — 100%.
  • bun scripts/publish.ts --package @suaveplan/pdf.
  • Update CLAUDE.md; archive to openspec/archive/2026-04-24-enterprise-server-pdf/.