diff --git a/CHANGELOG.md b/CHANGELOG.md index 5682e33f..8fa10366 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,9 @@ ## [1.4.1.0] - 2026-04-20 -## **/make-pdf goes from "demo-quality" to "actually ship this to a VC partner."** +## **Three visible bugs in v1.4.0.0 /make-pdf, all fixed.** -Three visible bugs in the v1.4.0.0 release made the output look broken. Page footers showed "6 of 8" twice on every page because Chromium's native footer and our print CSS were both rendering numbers. A markdown title containing `&` rendered as `Herbert &amp; Garry` in `` and TOC entries, because the extractors stripped tags but forgot to decode entities. On Linux (Docker, CI, servers), body text fell through to DejaVu Sans because neither Helvetica nor Arial is installed by default, and nothing in the font stack caught that. This release fixes all three and extends the fix beyond the obvious symptom each time. +Page footers showed "6 of 8" twice on every page because Chromium's native footer and our print CSS were both rendering numbers. A markdown title containing `&` rendered as `Faber &amp; Faber` in `<title>` and TOC entries, because the extractors stripped tags but forgot to decode entities. On Linux (Docker, CI, servers), body text fell through to DejaVu Sans because neither Helvetica nor Arial is installed by default, and nothing in the font stack caught that. This release fixes all three and extends the fix beyond the obvious symptom each time. ### The numbers that matter @@ -13,7 +13,7 @@ All three bugs were caught and expanded in review before any code was written. T | Surface | Before (v1.4.0.0) | After (v1.4.1.0) | |---------|-------------------|-----------------| | Page footer | "6 of 8" stacked twice | "6 of 8" once | -| `# Herbert & Garry` in `<title>` | `Herbert &amp; Garry` | `Herbert & Garry` | +| `# Faber & Faber` in `<title>` | `Faber &amp; Faber` | `Faber & Faber` | | TOC entry with `&` | Double-escaped | Single-escaped | | `©` (copyright) in H1 | Broken | Decodes to `©` | | `--no-page-numbers` CLI flag | Silently did nothing | Actually suppresses page numbers | @@ -39,7 +39,7 @@ Page numbers are now controlled by one flag from CLI to CSS, with the custom-foo - **Page numbers no longer render twice on every page.** Chromium's native footer used to layer on top of our `@page @bottom-center` CSS. Now CSS is the single source of truth; Chromium native numbering is off unconditionally. - **`--no-page-numbers` works end-to-end.** The CLI flag now reaches the CSS layer via `RenderOptions.pageNumbers`. Previously it died at the orchestrator and the CSS kept rendering numbers regardless. - **`--footer-template` cleanly replaces the stock footer.** Passing a custom footer now also suppresses the CSS page numbers, preserving the original "custom footer wins" semantic that existed before Bug 1 collided with it. -- **HTML entities in titles, cover pages, and TOC entries render correctly.** A markdown heading like `# Herbert & Garry` renders as `Herbert & Garry` in `<title>` (single-escaped) instead of `Herbert &amp; Garry` (double-escaped). Covers both extractor call sites: `extractFirstHeading` (title + cover) and `extractHeadings` (TOC). +- **HTML entities in titles, cover pages, and TOC entries render correctly.** A markdown heading like `# Faber & Faber` renders as `Faber & Faber` in `<title>` (single-escaped) instead of `Faber &amp; Faber` (double-escaped). Covers both extractor call sites: `extractFirstHeading` (title + cover) and `extractHeadings` (TOC). - **Numeric HTML entities decode too.** `©` in an H1 now renders as `©` in the PDF title. Decimal and hex numeric entities both supported. - **Linux PDFs render in Liberation Sans instead of DejaVu Sans.** Font stacks in all four print-CSS slots (body, running header, page number, CONFIDENTIAL label) now include `"Liberation Sans"` between Helvetica and Arial. Metric-compatible, SIL OFL 1.1, installs via `fonts-liberation`. diff --git a/make-pdf/SKILL.md b/make-pdf/SKILL.md index 06e4d6a0..0c9353fa 100644 --- a/make-pdf/SKILL.md +++ b/make-pdf/SKILL.md @@ -5,11 +5,9 @@ version: 1.0.0 description: | Turn any markdown file into a publication-quality PDF. Proper 1in margins, intelligent page breaks, page numbers, cover pages, running headers, curly - quotes and em dashes, clickable TOC, diagonal DRAFT watermark. Output you'd - send to a VC partner, a book agent, a judge, or Rick Rubin's team. Not a - draft artifact — a finished artifact. Use when asked to "make a PDF", - "export to PDF", "turn this markdown into a PDF", or "generate a document". - (gstack) + quotes and em dashes, clickable TOC, diagonal DRAFT watermark. Not a draft + artifact — a finished artifact. Use when asked to "make a PDF", "export to + PDF", "turn this markdown into a PDF", or "generate a document". (gstack) Voice triggers (speech-to-text aliases): "make this a pdf", "make it a pdf", "export to pdf", "turn this into a pdf", "turn this markdown into a pdf", "generate a pdf", "make a pdf from", "pdf this markdown". triggers: - markdown to pdf diff --git a/make-pdf/SKILL.md.tmpl b/make-pdf/SKILL.md.tmpl index a3b0c362..0827492a 100644 --- a/make-pdf/SKILL.md.tmpl +++ b/make-pdf/SKILL.md.tmpl @@ -5,11 +5,9 @@ version: 1.0.0 description: | Turn any markdown file into a publication-quality PDF. Proper 1in margins, intelligent page breaks, page numbers, cover pages, running headers, curly - quotes and em dashes, clickable TOC, diagonal DRAFT watermark. Output you'd - send to a VC partner, a book agent, a judge, or Rick Rubin's team. Not a - draft artifact — a finished artifact. Use when asked to "make a PDF", - "export to PDF", "turn this markdown into a PDF", or "generate a document". - (gstack) + quotes and em dashes, clickable TOC, diagonal DRAFT watermark. Not a draft + artifact — a finished artifact. Use when asked to "make a PDF", "export to + PDF", "turn this markdown into a PDF", or "generate a document". (gstack) voice-triggers: - "make this a pdf" - "make it a pdf" diff --git a/make-pdf/test/render.test.ts b/make-pdf/test/render.test.ts index 4d643e7e..a61dea50 100644 --- a/make-pdf/test/render.test.ts +++ b/make-pdf/test/render.test.ts @@ -409,18 +409,18 @@ describe("render() — no double HTML entity escaping", () => { } test('ampersand in <title> renders as exactly one "&"', () => { - const result = render({ markdown: `# Herbert & Garry\n\nBody.` }); - expect(result.html).toContain("<title>Herbert & Garry"); + const result = render({ markdown: `# Faber & Faber\n\nBody.` }); + expect(result.html).toContain("Faber & Faber"); expect(result.html).not.toContain("&amp;"); }); test("TOC entries have no double-escape when a heading contains '&'", () => { const result = render({ - markdown: `# Doc\n\n## Herbert & Garry\n\nBody.\n\n## Other\n\nMore.`, + markdown: `# Doc\n\n## Faber & Faber\n\nBody.\n\n## Other\n\nMore.`, toc: true, }); // TOC renders the heading text through escapeHtml; must be single-escaped. - expect(result.html).toContain("Herbert & Garry"); + expect(result.html).toContain("Faber & Faber"); expect(result.html).not.toContain("&amp;"); });