---
phase: 43-documents-branding
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- server/src/services/renderers/types.ts
- server/src/services/renderers/pdf-renderer.ts
- server/src/services/content-job-runner.ts
- server/src/__tests__/pdf-renderer.test.ts
- server/package.json
autonomous: true
requirements: [DOC-01, DOC-02, DOC-03]
must_haves:
truths:
- "renderPdfDocument produces a pdf-document-bundle with non-empty pdfBase64 for report docType"
- "renderPdfDocument produces a pdf-document-bundle for invoice docType with line items"
- "renderPdfDocument produces a pdf-document-bundle for api-docs docType"
- "content-job-runner dispatches pdf-document jobType to pdf-renderer"
- "PdfDocumentBundle and BrandKitBundle types exported from types.ts"
artifacts:
- path: "server/src/services/renderers/pdf-renderer.ts"
provides: "PDF rendering via Playwright HTML-to-PDF"
exports: ["renderPdfDocument"]
- path: "server/src/services/renderers/types.ts"
provides: "PdfDocumentBundle + BrandKitBundle type definitions"
contains: "PdfDocumentBundle"
- path: "server/src/__tests__/pdf-renderer.test.ts"
provides: "Unit tests for PDF renderer"
min_lines: 40
key_links:
- from: "server/src/services/content-job-runner.ts"
to: "server/src/services/renderers/pdf-renderer.ts"
via: "dynamic import in renderContent switch"
pattern: 'case "pdf-document"'
- from: "server/src/services/renderers/pdf-renderer.ts"
to: "server/src/services/renderers/diagram-renderer.js"
via: "resolveBrowserPath import"
pattern: "resolveBrowserPath"
---
PDF renderer and shared types for Phase 43 document generation.
Purpose: Enable PDF report, invoice, one-pager, and API documentation generation via Playwright HTML-to-PDF. Also installs `archiver` and defines both bundle types needed by Plans 02-03.
Output: Working pdf-renderer.ts, updated types.ts, job-runner wiring, archiver installed.
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/43-documents-branding/43-RESEARCH.md
@server/src/services/renderers/types.ts
@server/src/services/content-job-runner.ts
@server/src/services/renderers/diagram-renderer.ts
@server/src/services/puter-inference.ts
From server/src/services/renderers/types.ts:
```typescript
export interface RenderResult {
filename: string;
contentType: string;
buffer: Buffer;
}
export type ContentBundle = DiagramBundle | IconSetBundle | ThemePaletteBundle | WallpaperBundle | AppIconBundle | SocialPostBundle | ConvertBundle;
```
From server/src/services/renderers/diagram-renderer.ts:
```typescript
export function resolveBrowserPath(): string; // resolves Playwright Chromium binary path
```
From server/src/services/puter-inference.ts:
```typescript
export interface ChatMessage { role: "system" | "user" | "assistant"; content: string; }
export async function puterChatComplete(messages: ChatMessage[], model?: string): Promise;
```
From server/src/services/content-job-runner.ts:
```typescript
// renderContent() switch — add new case for "pdf-document"
export async function renderContent(jobType: string, input: Record): Promise;
```
Task 1: Install archiver, add bundle types, create pdf-renderer with tests
server/package.json, server/src/services/renderers/types.ts, server/src/services/renderers/pdf-renderer.ts, server/src/__tests__/pdf-renderer.test.ts
- server/src/services/renderers/types.ts (current bundle types and ContentBundle union)
- server/src/services/renderers/diagram-renderer.ts (resolveBrowserPath, Playwright launch pattern, stripUnsafeDirectives)
- server/src/services/renderers/wallpaper-renderer.ts (stripMarkdownFences helper — lines 42-50)
- server/src/services/puter-inference.ts (puterChatComplete signature)
- server/src/__tests__/diagram-renderer.test.ts (test mock pattern for playwright-core and puter-inference)
- Test 1: renderPdfDocument with docType="report" returns { filename: "document-report.json", contentType: "application/json" } and buffer parses to PdfDocumentBundle with type "pdf-document-bundle" and non-empty pdfBase64
- Test 2: renderPdfDocument with docType="invoice" returns bundle with docType "invoice"
- Test 3: renderPdfDocument with docType="api-docs" returns bundle with docType "api-docs"
- Test 4: renderPdfDocument with docType="one-pager" returns bundle with docType "one-pager"
- Test 5: LLM system prompt varies by docType (report vs invoice vs api-docs vs one-pager)
1. Install archiver: `pnpm --filter @paperclipai/server add archiver && pnpm --filter @paperclipai/server add -D @types/archiver`
2. Add to server/src/services/renderers/types.ts — append BEFORE the ContentBundle union:
```typescript
export interface PdfDocumentBundle {
type: "pdf-document-bundle";
docType: string;
title: string;
pdfBase64: string;
}
export interface BrandKitBundle {
type: "brand-kit-bundle";
spec: {
name: string;
tagline: string;
primaryColor: string;
secondaryColor: string;
fontStyle: string;
industry: string;
};
logoSvgBase64: string;
avatarPngs: Record;
socialImages: Record;
signatureHtml: string;
letterheadHtml: string;
guidelinesPdfBase64: string;
zipBase64: string;
}
```
Update ContentBundle union to include `| PdfDocumentBundle | BrandKitBundle`.
3. Create server/src/services/renderers/pdf-renderer.ts:
- Import `chromium` from playwright-core, `resolveBrowserPath` from diagram-renderer, `puterChatComplete` from puter-inference, `RenderResult` and `PdfDocumentBundle` from types
- Create a local `stripMarkdownFences(raw: string): string` helper that removes ```html and ``` fences (same pattern as wallpaper-renderer line 42-50 — do NOT import from wallpaper-renderer as it is not exported)
- Create `buildPdfSystemPrompt(docType: string): string` with type-specific instructions for "report", "invoice", "one-pager", "api-docs" — each variant has different structural instructions. CRITICAL: system prompt must say "Use only inline CSS in a