feat(v1.4.0.0): /make-pdf — markdown to publication-quality PDFs#1086
Merged
feat(v1.4.0.0): /make-pdf — markdown to publication-quality PDFs#1086
Conversation
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>
E2E Evals: ✅ PASS62/62 tests passed | $7.67 total cost | 12 parallel runners
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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
New
/make-pdfskill +$Pbinary. 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-endThe 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
pdftotextand asserts whole-word extraction. If any feature breaks extraction, CI fails.Architecture: make-pdf shells out to
browserather than duplicating Playwright.$B pdfgrew 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-htmland$B jsgained--tab-idfor parallel render isolation.$B newtab --jsonfor programmatic tab ID.Commits (bottom-up):
feat(browse): full $B pdf flag contract + tab-scoped load-html/js/pdffeat(resolvers): add MAKE_PDF_SETUP + makePdfDir host pathsfeat(make-pdf): new /make-pdf skill + orchestrator binarychore(build): wire make-pdf into build/test/setup/bin + add marked depci(make-pdf): matrix copy-paste gate on Ubuntu + macOSdocs(skills): regenerate SKILL.md for make-pdf addition + browse pdf flagsfix(tests): repair stale test expectations + emit _EXPLAIN_LEVEL / _QUESTION_TUNING from preamblechore: bump version and changelog (v1.4.0.0)Plan reviews
Plan went through the full gauntlet before implementation:
.context/designs/make-pdf-*.htmlreferenceTest Coverage
make-pdf/test/*.test.ts(browseClient, pdftotext comparator, smartypants, sanitizer, render, print-css)make-pdf/test/e2e/combined-gate.test.ts— combined-features copy-paste assertionPre-existing cleanup bundled
Six pre-existing test failures on
origin/mainwere repaired in this PR because they blocked /ship:skill-validationtests had stale string expectations from a pre-merge ship template compressionwriting-style-resolvertests expected_EXPLAIN_LEVELbash vars the preamble refactor droppedplan-tunetest expected_QUESTION_TUNINGsame causeDeferred (captured in
.context/ceo-plans/2026-04-19-perfect-pdf-generator.md)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 finalTest plan
🤖 Generated with Claude Code