Skip to content

feat(v1.4.0.0): /make-pdf — markdown to publication-quality PDFs#1086

Merged
garrytan merged 10 commits intomainfrom
garrytan/perfect-pdf
Apr 20, 2026
Merged

feat(v1.4.0.0): /make-pdf — markdown to publication-quality PDFs#1086
garrytan merged 10 commits intomainfrom
garrytan/perfect-pdf

Conversation

@garrytan
Copy link
Copy Markdown
Owner

Summary

New /make-pdf skill + $P binary. Turn any markdown file into a PDF you wouldn't be embarrassed to send to a VC.

New capability:

  • $P generate letter.md → clean letter PDF with 1in margins, Helvetica, page numbers
  • $P generate --cover --toc --watermark DRAFT essay.md essay.pdf → full publication layout
  • $P preview essay.md → HTML preview in browser for fast iteration
  • $P setup → verify install + smoke test end-to-end

The copy-paste thing: Every other markdown-to-PDF tool produces output where "Sailing" copies as "S a i l i n g" (per-glyph Tj operators). We ship with system Helvetica, which Chromium emits as clean word-level Tj. A CI gate runs a combined-features fixture (smartypants + hyphens + ligatures + bold/italic + inline code + lists + blockquote + chapter breaks) through pdftotext and asserts whole-word extraction. If any feature breaks extraction, CI fails.

Architecture: make-pdf shells out to browse rather than duplicating Playwright. $B pdf grew from a 2-line A4 wrapper into a real PDF engine with flag contract: --format, --width/--height, --margins, --header-template, --footer-template, --page-numbers, --tagged, --outline, --toc, --tab-id, --from-file (Windows argv cap). $B load-html and $B js gained --tab-id for parallel render isolation. $B newtab --json for programmatic tab ID.

Commits (bottom-up):

  • feat(browse): full $B pdf flag contract + tab-scoped load-html/js/pdf
  • feat(resolvers): add MAKE_PDF_SETUP + makePdfDir host paths
  • feat(make-pdf): new /make-pdf skill + orchestrator binary
  • chore(build): wire make-pdf into build/test/setup/bin + add marked dep
  • ci(make-pdf): matrix copy-paste gate on Ubuntu + macOS
  • docs(skills): regenerate SKILL.md for make-pdf addition + browse pdf flags
  • fix(tests): repair stale test expectations + emit _EXPLAIN_LEVEL / _QUESTION_TUNING from preamble
  • chore: bump version and changelog (v1.4.0.0)

Plan reviews

Plan went through the full gauntlet before implementation:

Review Status Findings
CEO (EXPANSION) CLEAR 5 scope proposals, 5 accepted
Codex plan review (round 1) 25 findings, 22 applied (interface binary, per-feature gate risk, flag contract, CI specifics)
Codex plan review (round 2) 11 findings, 11 applied (argv limits, tab-id coverage, binary resolution, combined-features gate, sequencing rewrite)
Eng review CLEAR Architecture + test coverage diagram (47 paths)
Design review CLEAR score 6→9, .context/designs/make-pdf-*.html reference
DX review CLEAR score 5→10, CLI UX spec

Test Coverage

  • 56 unit tests in make-pdf/test/*.test.ts (browseClient, pdftotext comparator, smartypants, sanitizer, render, print-css)
  • 1 E2E P0 gate in make-pdf/test/e2e/combined-gate.test.ts — combined-features copy-paste assertion
  • Existing browse tests still pass (292)
  • Full repo suite: 0 failures after merging main + fixing 6 pre-existing test-expectation drifts

Pre-existing cleanup bundled

Six pre-existing test failures on origin/main were repaired in this PR because they blocked /ship:

  • 3 skill-validation tests had stale string expectations from a pre-merge ship template compression
  • 2 writing-style-resolver tests expected _EXPLAIN_LEVEL bash vars the preamble refactor dropped
  • 1 plan-tune test expected _QUESTION_TUNING same cause
  • Golden-file fixtures refreshed to match the new preamble output

Deferred (captured in .context/ceo-plans/2026-04-19-perfect-pdf-generator.md)

  • Vendored Paged.js for accurate TOC page numbers
  • Vendored highlight.js for syntax highlighting
  • Drop caps, pull quotes
  • CMYK-safe color conversion
  • Two-column layout

Reference artifacts (not committed; .context/ is gitignored)

  • .context/ceo-plans/2026-04-19-perfect-pdf-generator.md — architectural + CLI UX spec
  • .context/designs/make-pdf-print-reference.html — DRAFT showcase
  • .context/designs/make-pdf-memo-reference.html — memo default
  • .context/designs/make-pdf-final-reference.html — shipping final

Test plan

  • bun test browse/test/ test/ make-pdf/test/ → 0 failures
  • make-pdf E2E combined-features copy-paste gate → PASS
  • $P generate on 99-word essay with --cover --watermark DRAFT → 3-page PDF, 62KB, 0.1s warm
  • pdftotext extracts paragraphs with "curly quotes", em dashes, ellipsis, no per-glyph fragmentation
  • CI matrix (Ubuntu + macOS) — runs on this PR

🤖 Generated with Claude Code

garrytan and others added 9 commits April 20, 2026 05:33
Grow $B pdf from a 2-line wrapper (hard-coded A4) into a real PDF engine
frontend so make-pdf can shell out to it without duplicating Playwright:

- pdf: --format, --width/--height, --margins, --margin-*, --header-template,
  --footer-template, --page-numbers, --tagged, --outline, --print-background,
  --prefer-css-page-size, --toc. Mutex rules enforced. --from-file <json>
  dodges Windows argv limits (8191 char CreateProcess cap).
- load-html: add --from-file <json> mode for large inline HTML. Size + magic
  byte checks still apply to the inline content, not the payload file path.
- newtab: add --json returning {"tabId":N,"url":...} for programmatic use.
- cli: extract --tab-id flag and route as body.tabId to the HTTP layer so
  parallel callers can target specific tabs without racing on the active
  tab (makes make-pdf's per-render tab isolation possible).
- --toc: non-fatal 3s wait for window.__pagedjsAfterFired. Paged.js ships
  later; v1 renders TOC statically via the markdown renderer.

Codex round 2 flagged these P0 issues during plan review. All resolved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Skill templates can now embed {{MAKE_PDF_SETUP}} to resolve $P to the
make-pdf binary via the same discovery order as $B / $D: env override
(MAKE_PDF_BIN), local skill root, global install, or PATH.

Mirrors the pattern established by generateBrowseSetup() and
generateDesignSetup() in scripts/resolvers/design.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Turn markdown into publication-quality PDFs. $P generate input.md out.pdf
produces a PDF with 1in margins, intelligent page breaks, page numbers,
running header, CONFIDENTIAL footer, and curly quotes/em dashes — all on
Helvetica so copy-paste extraction works ("S ai li ng" bug avoided).

Architecture (per Codex round 2):
  markdown → render.ts (marked + sanitize + smartypants) → orchestrator
    → $B newtab --json → $B load-html --tab-id → $B js (poll Paged.js)
    → $B pdf --tab-id → $B closetab

browseClient.ts shells out to the compiled browse CLI rather than
duplicating Playwright. --tab-id isolation per render means parallel
$P generate calls don't race on the active tab. try/finally tab cleanup
survives Paged.js timeouts, browser crashes, and output-path failures.

Features in v1:
  --cover              left-aligned cover page (eyebrow + title + hairline rule)
  --toc                clickable static TOC (Paged.js page numbers deferred)
  --watermark <text>   diagonal DRAFT/CONFIDENTIAL layer
  --no-chapter-breaks  opt out of H1-starts-new-page
  --page-numbers       "N of M" footer (default on)
  --tagged --outline   accessible PDF + bookmark outline (default on)
  --allow-network      opt in to external image loading (default off for privacy)
  --quiet --verbose    stderr control

Design decisions locked from the /plan-design-review pass:
  - Helvetica everywhere (Chromium emits single-word Tj operators for
    system fonts; bundled webfonts emit per-glyph and break extraction).
  - Left-aligned body, flush-left paragraphs, no text-indent, 12pt gap.
  - Cover shares 1in margins with body pages; no flexbox-center, no
    inset padding.
  - The reference HTMLs at .context/designs/*.html are the implementation
    source of truth for print-css.ts.

Tests (56 unit + 1 E2E combined-features gate):
  - smartypants: code/URL-safe, verified against 10 fixtures
  - sanitizer: strips <script>/<iframe>/on*/javascript: URLs
  - render: HTML assembly, CJK fallback, cover/TOC/chapter wrap
  - print-css: all @page rules, margin variants, watermark
  - pdftotext: normalize()+copyPasteGate() cross-OS tolerance
  - browseClient: binary resolution + typed error propagation
  - combined-features gate (P0): 2-chapter fixture with smartypants +
    hyphens + ligatures + bold/italic + inline code + lists + blockquote
    passes through PDF → pdftotext → expected.txt diff

Deferred to Phase 4 (future PR): Paged.js vendored for accurate TOC page
numbers, highlight.js for syntax highlighting, drop caps, pull quotes,
two-column, CMYK, watermark visual-diff acceptance.

Plan: .context/ceo-plans/2026-04-19-perfect-pdf-generator.md
References: .context/designs/make-pdf-*.html

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- package.json: compile make-pdf/dist/pdf as part of bun run build; add
  "make-pdf" to bin entry; include make-pdf/test/ in the free test pass;
  add marked@18.0.2 as a dep (markdown parser, ~40KB).
- setup: add make-pdf/dist/pdf to the Apple Silicon codesign loop.
- .gitignore: add make-pdf/dist/ (matches browse/dist/ and design/dist/).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Runs the combined-features P0 gate on pull requests that touch make-pdf/
or browse's PDF surface. Installs poppler (macOS) / poppler-utils (Ubuntu)
per OS. Windows deferred to tolerant mode (Xpdf / Poppler-Windows
extraction variance not yet calibrated against the normalized comparator —
Codex round 2 #18).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…flags

bun run gen:skill-docs picks up:
  - the new /make-pdf skill (make-pdf/SKILL.md)
  - updated browse command descriptions for 'pdf', 'load-html', 'newtab'
    reflecting the new flag contract and --from-file mode

Source of truth stays the .tmpl files + COMMAND_DESCRIPTIONS;
these are regenerated artifacts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…UESTION_TUNING from preamble

Three pre-existing test failures on main were blocking /ship:

- test/skill-validation.test.ts "Step 3.4 test coverage audit" expected the
  literal strings "CODE PATH COVERAGE" and "USER FLOW COVERAGE" which were
  removed when the Step 7 coverage diagram was compressed. Updated assertions
  to check the stable `Code paths:` / `User flows:` labels that still ship.

- test/skill-validation.test.ts "ship step numbering" allowed-substeps list
  didn't include 15.0 (WIP squash) and 15.1 (bisectable commits) which were
  added for continuous checkpoint mode. Extended the allowlist.

- test/writing-style-resolver.test.ts and test/plan-tune.test.ts expected
  `_EXPLAIN_LEVEL` and `_QUESTION_TUNING` bash variables in the preamble but
  generate-preamble-bash.ts had been refactored and those lines were dropped.
  Without them, downstream skills can't read `explain_level` or
  `question_tuning` config at runtime — terse mode and /plan-tune features
  were silently broken.

Added the two bash echo blocks back to generatePreambleBash and refreshed
the golden-file fixtures to match. All three preamble-related golden
baselines (claude/codex/factory) are synchronized with the new output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New /make-pdf skill + $P binary.

Turn any markdown file into a publication-quality PDF. Default output is
a 1in-margin Helvetica letter with page numbers in the footer. `--cover`
adds a left-aligned cover page, `--toc` generates a clickable table of
contents, `--watermark DRAFT` overlays a diagonal watermark. Copy-paste
extraction from the PDF produces clean words, not "S a i l i n g"
spaced out letter by letter. CI gate (macOS + Ubuntu) runs a combined-
features fixture through pdftotext on every PR.

make-pdf shells out to browse rather than duplicating Playwright.
$B pdf grew into a real PDF engine with full flag contract (--format,
--margins, --header-template, --footer-template, --page-numbers,
--tagged, --outline, --toc, --tab-id, --from-file). $B load-html and
$B js gained --tab-id. $B newtab --json returns structured output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 19, 2026

E2E Evals: ✅ PASS

62/62 tests passed | $7.67 total cost | 12 parallel runners

Suite Result Status Cost
e2e-browse 6/6 $0.29
e2e-deploy 6/6 $1.1
e2e-design 3/3 $0.44
e2e-plan 8/8 $1.71
e2e-qa-workflow 3/3 $0.99
e2e-review 6/6 $1.31
e2e-workflow 3/3 $0.36
llm-judge 24/24 $0.48
e2e-qa-workflow 3/3 $0.99

12x ubicloud-standard-2 (Docker: pre-baked toolchain + deps) | wall clock ≈ slowest suite

…aming

The original headline led with "a PDF you wouldn't be embarrassed to send
to a VC": double-negative voice and audience-too-narrow. /make-pdf works
for essays, letters, memos, reports, proposals, and briefs. Framing the
whole release around founders-to-investors misses the wider audience.

New headline: "Turn any markdown file into a PDF that looks finished."
New tagline: "This one reads like a real essay or a real letter."

Positive voice. Broader aperture. Same energy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@garrytan garrytan merged commit d0782c4 into main Apr 20, 2026
20 checks passed
garrytan added a commit that referenced this pull request Apr 20, 2026
Main landed v1.4.0.0 with /make-pdf (PR #1086), so this branch bumps
to v1.5.0.0 and keeps main's entry intact below.

Conflicts resolved:
- CHANGELOG.md: both branches used v1.4.0.0 — renumbered this branch
  to v1.5.0.0, kept main's v1.4.0.0 entry directly below.
- test/skill-validation.test.ts: both branches fixed the same set of
  failing tests. Took main's more conservative assertions (check for
  "Code paths:" / "User flows:" summary labels instead of the older
  "CODE PATHS" / "USER FLOWS" header strings). ALLOWED_SUBSTEPS stays
  the same on both sides.
- bun.lock: kept both new deps (matcher from this branch, marked
  from main's /make-pdf). Verified via bun install.
- scripts/resolvers/preamble/generate-preamble-bash.ts: both branches
  added _EXPLAIN_LEVEL + _QUESTION_TUNING echoes. Kept main's version
  (which has value validation) and removed the duplicate block my
  branch added. Regenerated all SKILL.md files.
- Golden fixtures refreshed after regen.

VERSION: 1.4.0.0 → 1.5.0.0. package.json synced.

All tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant