From e39af91cb659664e7cb89288a5e528899ee53652 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 23 Jul 2026 11:11:11 +0200 Subject: [PATCH 01/13] Resync Pandoc-derived templates for Pandoc 3.10.1 Pandoc 3.10.1 changed common.latex (caption package + spacing for tables), default.typst (native terms hanging-indent and divider() polyfill, quoted keywords), and styles.html (dropped hardcoded print font-size) relative to 3.10.0. Ported the same changes into Quarto's copies, verified byte-identical against the upstream 3.10.1 source. Not gated on a bundled-binary version bump: tables.tex is Quarto's own always-loaded LaTeX/Beamer partial, so this takes effect immediately regardless of which Pandoc binary renders. typst.template and html.styles are pandoc-quarto template resync only; the QUARTO_VERSION bump itself is a separate, gated step. --- src/resources/formats/beamer/pandoc/tables.tex | 2 ++ src/resources/formats/html/pandoc/html.styles | 1 - src/resources/formats/pdf/pandoc/tables.tex | 2 ++ src/resources/formats/typst/pandoc/typst.template | 13 ++++++------- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/resources/formats/beamer/pandoc/tables.tex b/src/resources/formats/beamer/pandoc/tables.tex index 443a14ab2f3..f6f20ff1d7c 100644 --- a/src/resources/formats/beamer/pandoc/tables.tex +++ b/src/resources/formats/beamer/pandoc/tables.tex @@ -3,6 +3,8 @@ $-- $if(tables)$ \usepackage{longtable,booktabs,array} +\usepackage{caption} +\captionsetup[table]{skip=6pt} \newcounter{none} % for unnumbered tables $if(multirow)$ \usepackage{multirow} diff --git a/src/resources/formats/html/pandoc/html.styles b/src/resources/formats/html/pandoc/html.styles index cac6172c5ed..be69d8a4af0 100644 --- a/src/resources/formats/html/pandoc/html.styles +++ b/src/resources/formats/html/pandoc/html.styles @@ -43,7 +43,6 @@ body { body { background-color: transparent; color: black; - font-size: 12pt; } p, h2, h3 { orphans: 3; diff --git a/src/resources/formats/pdf/pandoc/tables.tex b/src/resources/formats/pdf/pandoc/tables.tex index 443a14ab2f3..f6f20ff1d7c 100644 --- a/src/resources/formats/pdf/pandoc/tables.tex +++ b/src/resources/formats/pdf/pandoc/tables.tex @@ -3,6 +3,8 @@ $-- $if(tables)$ \usepackage{longtable,booktabs,array} +\usepackage{caption} +\captionsetup[table]{skip=6pt} \newcounter{none} % for unnumbered tables $if(multirow)$ \usepackage{multirow} diff --git a/src/resources/formats/typst/pandoc/typst.template b/src/resources/formats/typst/pandoc/typst.template index 9b161a4c873..368feda35d9 100644 --- a/src/resources/formats/typst/pandoc/typst.template +++ b/src/resources/formats/typst/pandoc/typst.template @@ -1,15 +1,14 @@ -#let horizontalrule = line(start: (25%,0%), end: (75%,0%)) - -#show terms.item: it => block(breakable: false)[ - #text(weight: "bold")[#it.term] - #block(inset: (left: 1.5em, top: -0.4em))[#it.description] -] +#set terms(hanging-indent: 1.5em) #set table( inset: 6pt, stroke: none ) +#let horizontalrule = line(start: (25%,0%), end: (75%,0%)) +// Polyfill divider to allow compiling with typst < 0.15: +#let divider = if "divider" in std { divider } else { horizontalRule } + #show figure.where( kind: table ): set figure.caption(position: $if(table-caption-position)$$table-caption-position$$else$top$endif$) @@ -61,7 +60,7 @@ $endfor$ ), $endif$ $if(keywords)$ - keywords: ($for(keywords)$$keywords$$sep$,$endfor$), + keywords: ($for(keywords)$"$keywords/nowrap$"$sep$,$endfor$), $endif$ $if(date)$ date: [$date$], From aa51dff182a5c0433c067d6ddda0b5cc0def40a4 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 23 Jul 2026 11:11:32 +0200 Subject: [PATCH 02/13] Fix stale pandoc-template llm-docs and add frontmatter typst.template/template.typst were documented as a custom-template fallback path, but they're never referenced by any TypeScript code - they only exist so a maintainer can diff them against a fresh Pandoc checkout during the update-pandoc process. Corrected that, and added a note that Pandoc's typst writer can change body-level output (e.g. horizontalrule -> divider()) independently of any template file, so a clean template diff alone isn't proof a Pandoc bump has no effect. The LaTeX doc was missing document-metadata.latex (added in Quarto 1.7 for Pandoc 3.6.3's PDF/A-UA-X support): absent from the file table, the partialNamesPandoc sample, and the invocation order (it's actually called first). Also noted update-pandoc.ts's own templateDirFiles mapping doesn't copy this file, so the script itself undercounts relative to what format-pdf.ts wires up. Both files gain main_commit/analyzed_date/key_files frontmatter per the llm-docs staleness-check convention. --- llm-docs/pandoc-quarto-latex-templates.md | 72 +++++++++++++++-------- llm-docs/pandoc-quarto-typst-templates.md | 26 +++++++- 2 files changed, 70 insertions(+), 28 deletions(-) diff --git a/llm-docs/pandoc-quarto-latex-templates.md b/llm-docs/pandoc-quarto-latex-templates.md index c75e11f5d9b..7a1510434b6 100644 --- a/llm-docs/pandoc-quarto-latex-templates.md +++ b/llm-docs/pandoc-quarto-latex-templates.md @@ -1,3 +1,16 @@ +--- +main_commit: d30cdbb9e +analyzed_date: 2026-07-23 +key_files: + - package/src/common/update-pandoc.ts + - src/format/pdf/format-pdf.ts + - src/resources/formats/pdf/pandoc/template.tex + - src/resources/formats/pdf/pandoc/common.latex + - src/resources/formats/pdf/pandoc/pandoc.tex + - src/resources/formats/pdf/pandoc/document-metadata.latex + - src/resources/formats/beamer/pandoc/template.tex +--- + # Pandoc and Quarto LaTeX Templates This document describes how Quarto integrates Pandoc's LaTeX templates and transforms them into a modular structure suitable for Quarto's extended functionality. @@ -13,7 +26,7 @@ The `writePandocTemplates` function in `package/src/common/update-pandoc.ts` han ### Files Copied -Seven files are copied from Pandoc for LaTeX support: +**Stale-script warning**: `update-pandoc.ts`'s `templateDirFiles` mapping only lists `default.latex`, `common.latex`, `after-header-includes.latex`, `hypersetup.latex`, `font-settings.latex`, `fonts.latex`, and `passoptions.latex`. It does **not** include `document-metadata.latex` (added later, see below), so the script under-copies relative to what `format-pdf.ts` actually wires up. Don't trust the script's file list as the source of truth — cross-check against `partialNamesPandoc` in `format-pdf.ts` instead. | Pandoc Source | Quarto Destination | Notes | | ----------------------------- | ------------------ | -------------------------------------------------- | @@ -24,6 +37,7 @@ Seven files are copied from Pandoc for LaTeX support: | `font-settings.latex` | (unchanged) | User font configuration | | `fonts.latex` | (unchanged) | Font engine detection | | `passoptions.latex` | (unchanged) | Package options passthrough | +| `document-metadata.latex` | (unchanged) | PDF standard metadata (PDF/A, PDF/UA, PDF/X); added in Quarto 1.7 following Pandoc 3.6.3 support; **not copied by `update-pandoc.ts`'s current mapping** | ### Purpose of Each Pandoc File @@ -41,6 +55,8 @@ Seven files are copied from Pandoc for LaTeX support: **passoptions.latex**: Package options passthrough mechanism for hyperref, xcolor, and xeCJK. +**document-metadata.latex**: Emits a `\DocumentMetadata{...}` block (LuaLaTeX, 2023+ LaTeX) for PDF/A, PDF/UA, and PDF/X standards support, driven by the `pdfstandard` variable. Called first, before `passoptions.latex()`, in the pdf `template.tex`. **Beamer does not use this partial** — its `template.tex` has no `document-metadata.latex()` call, and `format-pdf.ts` filters it out of `beamerPartialNamesPandoc`. + ## 2. Quarto's Modular Template Structure Quarto breaks the Pandoc templates into a modular structure in `src/resources/formats/pdf/pandoc/`. This allows for: @@ -120,37 +136,38 @@ The `template.tex` orchestrates the full document: ### Preamble (before `\begin{document}`) -1. `$passoptions.latex()$` - Package options passthrough -2. `$doc-class.tex()$` - `\documentclass[...]{...}` declaration -3. (inline) - beamerarticle, xcolor, geometry, amsmath -4. (inline) - Section numbering configuration -5. `$fonts.latex()$` - Font engine detection (ifPDFTeX, ifXeTeX, ifLuaTeX) -6. `$font-settings.latex()$` - User font configuration -7. `$common.latex()$` - Common preamble setup, which internally calls: +1. `$document-metadata.latex()$` - `\DocumentMetadata{...}` for PDF/A, PDF/UA, PDF/X (pdf only — beamer's `template.tex` omits this call) +2. `$passoptions.latex()$` - Package options passthrough +3. `$doc-class.tex()$` - `\documentclass[...]{...}` declaration +4. (inline) - beamerarticle, xcolor, geometry, amsmath +5. (inline) - Section numbering configuration +6. `$fonts.latex()$` - Font engine detection (ifPDFTeX, ifXeTeX, ifLuaTeX) +7. `$font-settings.latex()$` - User font configuration +8. `$common.latex()$` - Common preamble setup, which internally calls: - `$pandoc.tex()$` → `$tables.tex()$`, `$graphics.tex()$`, `$citations.tex()$`, `$babel-lang.tex()$`, `$tightlist.tex()$`, `$biblio-config.tex()$` -8. `$after-header-includes.latex()$` - Bookmark, URL packages -9. `$hypersetup.latex()$` - Hyperref configuration -10. `$before-title.tex()$` - Pre-title content -11. `$title.tex()$` - `\title{}`, `\author{}`, `\date{}` +9. `$after-header-includes.latex()$` - Bookmark, URL packages +10. `$hypersetup.latex()$` - Hyperref configuration +11. `$before-title.tex()$` - Pre-title content +12. `$title.tex()$` - `\title{}`, `\author{}`, `\date{}` ### Document Body -12. `\begin{document}` -13. `$before-body.tex()$` - Frontmatter, `\maketitle`, abstract -14. (loop) - include-before content -15. `$toc.tex()$` - Table of contents -16. (inline) - linestretch, mainmatter -17. `$body$` - Document content -18. `$before-bib.tex()$` - Pre-bibliography content -19. (inline) - backmatter, nocite -20. `$biblio.tex()$` - Bibliography rendering -21. (loop) - include-after content -22. `$after-body.tex()$` - Post-body content -23. `\end{document}` +13. `\begin{document}` +14. `$before-body.tex()$` - Frontmatter, `\maketitle`, abstract +15. (loop) - include-before content +16. `$toc.tex()$` - Table of contents +17. (inline) - linestretch, mainmatter +18. `$body$` - Document content +19. `$before-bib.tex()$` - Pre-bibliography content +20. (inline) - backmatter, nocite +21. `$biblio.tex()$` - Bibliography rendering +22. (loop) - include-after content +23. `$after-body.tex()$` - Post-body content +24. `\end{document}` ## 4. TypeScript Configuration -The `pdfFormat` function in `src/format/pdf/format-pdf.ts` (lines 236-284) defines the template configuration: +The template configuration is defined inline inside `src/format/pdf/format-pdf.ts` (around lines 241-267, in the function that builds the PDF format): ```typescript // Quarto-maintained partials (.tex) @@ -172,10 +189,11 @@ const partialNamesQuarto: string[] = [ "toc", ]; -// Pandoc-derived partials (.latex) +// Pandoc-derived partials (.latex) — since Pandoc 3.6.3 const partialNamesPandoc: string[] = [ "after-header-includes", "common", + "document-metadata", "font-settings", "fonts", "hypersetup", @@ -188,6 +206,8 @@ The template context maps these to files: - Quarto partials: `pandoc/${name}.tex` - Pandoc partials: `pandoc/${name}.latex` +Beamer builds its own `beamerPartialNamesPandoc` by filtering `document-metadata` out of `partialNamesPandoc` (`.filter((name) => name !== "document-metadata")`), since beamer's `template.tex` never calls it. + ## 5. Parameter Summary Table ### Font Parameters (XeTeX/LuaTeX) diff --git a/llm-docs/pandoc-quarto-typst-templates.md b/llm-docs/pandoc-quarto-typst-templates.md index f3358c9697d..2a305837754 100644 --- a/llm-docs/pandoc-quarto-typst-templates.md +++ b/llm-docs/pandoc-quarto-typst-templates.md @@ -1,3 +1,15 @@ +--- +main_commit: d30cdbb9e +analyzed_date: 2026-07-23 +key_files: + - package/src/common/update-pandoc.ts + - src/format/typst/format-typst.ts + - src/resources/formats/typst/pandoc/quarto/definitions.typ + - src/resources/formats/typst/pandoc/quarto/template.typ + - src/resources/formats/typst/pandoc/typst.template + - src/resources/formats/typst/pandoc/template.typst +--- + # Pandoc and Quarto Typst Templates This document describes how Quarto integrates Pandoc's typst templates and transforms them into a more modular structure suitable for Quarto's extended functionality. @@ -170,9 +182,19 @@ Handles bibliography rendering: - Sets bibliography style from CSL file or bibliography style option - Renders bibliography from specified files -### typst.template - Reference Copy +### typst.template and template.typst - Dev-Reference Copies Only + +`typst.template` (Pandoc's `default.typst`) and `template.typst` (Pandoc's own `template.typst`, kept under the same name) are **not referenced by any runtime TypeScript code path** — a repo-wide grep for either filename across `src/**/*.ts` returns zero matches. `format-typst.ts` wires up only the `quarto/` partials (see above) as the template context. + +These two files exist solely so a maintainer running the Pandoc update process (`writePandocTemplates` in `package/src/common/update-pandoc.ts`) can `git diff` them against a fresh Pandoc checkout to see what changed upstream, then manually port the relevant parts into `quarto/definitions.typ` and friends. They are not a "no custom template" fallback path — don't rely on them actually executing for any user-facing render. + +### Writer-Level Changes vs. Template-Level Changes + +Not every user-visible difference between Pandoc versions shows up as a diff in these template files. Some behavior lives in Pandoc's own Haskell **typst writer** — the code that converts the Pandoc AST into typst body markup — and is emitted directly into the rendered body regardless of which template (if any) is used. + +Example (verified 2026-07-23, Pandoc 3.10.0 → 3.10.1): the typst writer changed how it emits a `HorizontalRule` AST node, from `#horizontalrule` (calling a symbol the template defines) to `#divider()` (typst's own native function, added in typst 0.15). It also changed term-list (definition list) output from wrapping each description in `#block[...]` to emitting it bare, relying on an external `#set terms(hanging-indent: ...)`. Neither change touched `default.typst`/`template.typst` content in a way that mattered for Quarto: Quarto's `quarto/definitions.typ` keeps its own `#let horizontalrule = ...` (still used internally by `notes.typ`) and its own `#show terms.item` rule (which reformats the description regardless of whether Pandoc pre-wrapped it in `#block[...]`), and since Quarto always bundles a recent typst (0.15.1+) where `divider` is a native `std` builtin, `#divider()` in the body just resolves with no local definition needed. -This is the copy of Pandoc's `default.typst`, kept for reference. It is used when rendering without Quarto's template context (e.g., when a user provides a custom template). +**Implication for future Pandoc bumps**: a clean `data/templates/` diff between two Pandoc versions is necessary but not sufficient evidence that "nothing changed" for typst output. Confirm by actually rendering a document with both Pandoc versions (e.g. via a `QUARTO_PANDOC` env override against the bundled binary) and inspecting the generated `.typ` body content, not just diffing the template files. ## 3. Parameter Summary Table From 158d27bd9c312972b62a5eb388aa13ba1e07ea07 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 23 Jul 2026 11:58:56 +0200 Subject: [PATCH 03/13] Resync latex.common reference copies for Pandoc 3.10.1 Missed these in the earlier tables.tex resync: latex.common (both pdf and beamer) is Pandoc's own common.latex kept verbatim for future-diff purposes, same as typst.template - not wired into any partial list, so it was still byte-for-byte Pandoc 3.10.0. A glob pattern filtered on *.latex silently excluded it since it ends in .common, which is what let this slip past the earlier pass. Both files verified byte-identical to Pandoc 3.10.1's common.latex. --- src/resources/formats/beamer/pandoc/latex.common | 2 ++ src/resources/formats/pdf/pandoc/latex.common | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/resources/formats/beamer/pandoc/latex.common b/src/resources/formats/beamer/pandoc/latex.common index 1f26b339fa1..f7dee421799 100644 --- a/src/resources/formats/beamer/pandoc/latex.common +++ b/src/resources/formats/beamer/pandoc/latex.common @@ -69,6 +69,8 @@ $-- tables $-- $if(tables)$ \usepackage{longtable,booktabs,array} +\usepackage{caption} +\captionsetup[table]{skip=6pt} \newcounter{none} % for unnumbered tables $if(multirow)$ \usepackage{multirow} diff --git a/src/resources/formats/pdf/pandoc/latex.common b/src/resources/formats/pdf/pandoc/latex.common index 1f26b339fa1..f7dee421799 100644 --- a/src/resources/formats/pdf/pandoc/latex.common +++ b/src/resources/formats/pdf/pandoc/latex.common @@ -69,6 +69,8 @@ $-- tables $-- $if(tables)$ \usepackage{longtable,booktabs,array} +\usepackage{caption} +\captionsetup[table]{skip=6pt} \newcounter{none} % for unnumbered tables $if(multirow)$ \usepackage{multirow} From d4e5f27fdde3fcc8be22e98e13f3fc2a7bae6446 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 23 Jul 2026 11:59:43 +0200 Subject: [PATCH 04/13] Add opt-in Pandoc version override to smoke test workflow Bumping a bundled binary can't be tested in CI until it's archived to S3, since configure only ever downloads what's already archived there. This adds a pandoc-override-version workflow_dispatch input that installs the given release via r-lib/actions/setup-pandoc and points quarto at it through QUARTO_PANDOC for that run only - the archived/configured version, and every other trigger of this workflow, is unaffected. Lets template/rendering changes be smoke-tested against an unarchived Pandoc release before anyone with S3 access has to get involved. --- .github/workflows/test-smokes.yml | 31 +++++++++++++++++++++++++++++ dev-docs/update-pandoc-checklist.md | 2 ++ 2 files changed, 33 insertions(+) diff --git a/.github/workflows/test-smokes.yml b/.github/workflows/test-smokes.yml index 652807eddd7..7cdd8596a78 100644 --- a/.github/workflows/test-smokes.yml +++ b/.github/workflows/test-smokes.yml @@ -35,6 +35,11 @@ on: required: false type: string default: "" + pandoc-override-version: + description: "Pandoc release tag to download from GitHub and use via QUARTO_PANDOC instead of the archived/configured version (e.g. 3.10.1) - for smoke-testing a version before it has been archived to S3. Leave empty for normal behavior." + required: false + type: string + default: "" schedule: - cron: "0 6 * * *" @@ -229,6 +234,32 @@ jobs: - uses: ./.github/workflows/actions/quarto-dev + # Optional override: installs an unarchived Pandoc release and points + # quarto at it via QUARTO_PANDOC, for smoke-testing a version before it + # has been archived to S3. No-op unless pandoc-override-version is set - + # the archived/configured binary is used as normal otherwise. + - name: Install Pandoc override + if: inputs.pandoc-override-version != '' + uses: r-lib/actions/setup-pandoc@v2 + with: + pandoc-version: ${{ inputs.pandoc-override-version }} + + - name: Point QUARTO_PANDOC at override (Linux) + if: runner.os == 'Linux' && inputs.pandoc-override-version != '' + shell: bash + run: echo "QUARTO_PANDOC=$(command -v pandoc)" >> "$GITHUB_ENV" + + - name: Point QUARTO_PANDOC at override (Windows) + if: runner.os == 'Windows' && inputs.pandoc-override-version != '' + shell: pwsh + run: | + "QUARTO_PANDOC=$((Get-Command pandoc).Source)" >> $env:GITHUB_ENV + + - name: Confirm Pandoc override is live + if: inputs.pandoc-override-version != '' + shell: bash + run: quarto pandoc --version + - name: Install Tinytex env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/dev-docs/update-pandoc-checklist.md b/dev-docs/update-pandoc-checklist.md index 230241e65bb..20eee6ee908 100644 --- a/dev-docs/update-pandoc-checklist.md +++ b/dev-docs/update-pandoc-checklist.md @@ -1,5 +1,7 @@ ## Updating the bundled version of Pandoc +Template and rendering changes can be smoke-tested before archival: dispatch `.github/workflows/test-smokes.yml` with the `pandoc-override-version` input set to the new release tag (e.g. `3.10.1`). It downloads that Pandoc release directly from GitHub, points `quarto` at it via `QUARTO_PANDOC` for that run only, and leaves the archived/configured version untouched - no S3 access needed. Scope `buckets` to the relevant smoke-all dirs (e.g. `typst`, `latex`, `table`) for a faster signal. + Carlos needs to run this: - [ ] Ensure archives are upgraded From a28f470dc705070eac4b95565a565635eca15270 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 23 Jul 2026 14:20:34 +0200 Subject: [PATCH 05/13] Fix stale print CSS font-size in the real styles.html The earlier html.styles fix patched Pandoc's dev-reference-only copy (html.template's sibling), not styles.html - the actual file wired up in format-html.ts and rendered whenever theme:pandoc is set. Same mistake as the latex.common miss: an extension-based assumption about which file is "ours" vs Pandoc's reference copy. styles.html has its own local divergences from Pandoc's stock CSS (existing height/auto typo, a simplified table-caption block, custom checkbox margin per #4556), so this only removes the stale font-size: 12pt rule rather than re-syncing the whole file. Verified via a real render with theme: pandoc (the only path that sets document-css: true and actually includes this file) that the rule is now gone from rendered output. --- src/resources/formats/html/pandoc/styles.html | 1 - 1 file changed, 1 deletion(-) diff --git a/src/resources/formats/html/pandoc/styles.html b/src/resources/formats/html/pandoc/styles.html index 795fd311307..9437be7c0c8 100644 --- a/src/resources/formats/html/pandoc/styles.html +++ b/src/resources/formats/html/pandoc/styles.html @@ -43,7 +43,6 @@ body { background-color: transparent; color: black; - font-size: 12pt; } p, h2, h3 { orphans: 3; From 6356a5f98e724f4bbec2bf37351ed58a02367bea Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 23 Jul 2026 14:21:14 +0200 Subject: [PATCH 06/13] Full audit of pandoc-template naming docs across all formats The dev-reference-only pattern (Pandoc's own template copied in verbatim, unreferenced by any TS code, kept only for future-diff purposes) turned out to apply to every format with a pandoc/ resource dir, not just typst and latex: beamer, html (both html.template and html.styles), revealjs, and asciidoc all have the same split between a reference copy and Quarto's actual wired-up template. jats is the exception - template.xml is entirely Quarto-authored, no Pandoc reference copy exists. update-pandoc-checklist.md now lists the per-format file pairs and states the naming gotcha explicitly (extension-filtered globs like *.latex silently exclude latex.common). The rule pointer's paths now cover every affected format directory so it actually fires. localization-architecture.md had four citations pointing at dead reference-copy files (html.template, latex.template, beamer.template) to describe behavior that's actually in the live template.html, template.tex, and toc.tex - corrected each and added a naming-note caveat so the same mix-up doesn't recur here. --- .claude/rules/formats/pandoc-templates.md | 9 +++++++- dev-docs/update-pandoc-checklist.md | 26 +++++++++++++++++++++++ llm-docs/localization-architecture.md | 23 ++++++++++---------- llm-docs/pandoc-quarto-latex-templates.md | 4 +++- 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/.claude/rules/formats/pandoc-templates.md b/.claude/rules/formats/pandoc-templates.md index 68c8d56e80c..e25f230324e 100644 --- a/.claude/rules/formats/pandoc-templates.md +++ b/.claude/rules/formats/pandoc-templates.md @@ -1,12 +1,19 @@ --- paths: - "src/resources/formats/pdf/pandoc/**" + - "src/resources/formats/beamer/pandoc/**" - "src/resources/formats/typst/pandoc/**" + - "src/resources/formats/html/pandoc/**" + - "src/resources/formats/revealjs/pandoc/**" + - "src/resources/formats/asciidoc/pandoc/**" - "package/src/common/update-pandoc.ts" + - "dev-docs/update-pandoc-checklist.md" --- -# Pandoc Templates (LaTeX & Typst) +# Pandoc Templates (LaTeX, Typst, HTML, Reveal.js, AsciiDoc) For how Pandoc's LaTeX templates are copied into Quarto and restructured into a modular form (`latex.template`, `latex.common`, …), see `llm-docs/pandoc-quarto-latex-templates.md`. For the equivalent Typst template integration (`typst.template`, `template.typst`), see `llm-docs/pandoc-quarto-typst-templates.md`. + +Every format's `pandoc/` directory has the same split: a dev-reference-only copy of Pandoc's own template (unreferenced by any TypeScript code — kept only so a maintainer can diff it against a fresh Pandoc checkout) alongside Quarto's actual, wired-up template. `dev-docs/update-pandoc-checklist.md` has the full per-format file list (pdf, beamer, html, revealjs, asciidoc, typst) and the naming gotcha that makes the reference-copy file easy to miss when checking what's in a directory. diff --git a/dev-docs/update-pandoc-checklist.md b/dev-docs/update-pandoc-checklist.md index 20eee6ee908..cd5a4077338 100644 --- a/dev-docs/update-pandoc-checklist.md +++ b/dev-docs/update-pandoc-checklist.md @@ -27,6 +27,32 @@ Partials: - Ours: - src/resources/formats/beamer/pandoc/common.latex +#### pdf / latex + +- Pandoc's: src/resources/formats/pdf/pandoc/latex.template +- Ours: src/resources/formats/pdf/pandoc/template.tex (see `llm-docs/pandoc-quarto-latex-templates.md` for the full partial breakdown, including `latex.common`/`common.latex`) + +#### html + +- Pandoc's: src/resources/formats/html/pandoc/html.template, src/resources/formats/html/pandoc/html.styles +- Ours: src/resources/formats/html/pandoc/template.html, src/resources/formats/html/pandoc/styles.html + +#### revealjs + +- Pandoc's: src/resources/formats/revealjs/pandoc/revealjs.template +- Ours: src/resources/formats/revealjs/pandoc/template.html + +#### asciidoc + +- Pandoc's: src/resources/formats/asciidoc/pandoc/asciidoc.template +- Ours: src/resources/formats/asciidoc/pandoc/template.asciidoc + +#### typst + +See `llm-docs/pandoc-quarto-typst-templates.md` - typst has an extra wrinkle where Pandoc's own `template.typst` partial is also kept verbatim (same filename) alongside the renamed `typst.template`. + +**All of the "Pandoc's" files above are dev-reference-only**: none of them are read by any TypeScript code path (confirmed via `grep -rn "" src/ --include="*.ts"` returning zero matches for each, across every format listed here). They exist purely so `git diff` against a fresh Pandoc checkout shows what changed upstream - patch the corresponding "Ours" file (or its own sub-partials, e.g. `tables.tex`, `toc.tex`) to actually change rendered behavior, and update the "Pandoc's" copy too so the next resync's diff stays meaningful. It's easy to update one and miss the other (or vice versa) since the filenames differ only by which segment comes first - use an unfiltered `ls`/`Glob **/*` on the directory when checking what's there, not an extension-filtered glob (e.g. `*.latex` silently excludes `latex.common`, which ends in `.common`). + ## Manual steps - [ ] Update schemas by inspecting [their changelog](https://github.com/jgm/pandoc/blob/main/changelog.md) for new commands, deprecation removals, etc diff --git a/llm-docs/localization-architecture.md b/llm-docs/localization-architecture.md index 768b79068bd..8728c444ed8 100644 --- a/llm-docs/localization-architecture.md +++ b/llm-docs/localization-architecture.md @@ -1,6 +1,6 @@ --- -main_commit: eca40cdab -analyzed_date: 2026-05-21 +main_commit: d30cdbb9e +analyzed_date: 2026-07-23 key_files: - src/resources/language/_language.yml - src/resources/language/_language-fr.yml @@ -16,14 +16,13 @@ key_files: - src/resources/filters/modules/authors.lua - src/resources/filters/modules/callouts.lua - src/resources/filters/layout/meta.lua - - src/resources/formats/html/pandoc/html.template + - src/resources/formats/html/pandoc/template.html + - src/resources/formats/html/pandoc/toc.html - src/resources/formats/html/pandoc/title-block.html - src/resources/formats/html/templates/title-metadata.html - - src/resources/formats/pdf/pandoc/latex.template - src/resources/formats/pdf/pandoc/babel-lang.tex - src/resources/formats/pdf/pandoc/toc.tex - - src/resources/formats/beamer/pandoc/beamer.template - - src/resources/formats/typst/pandoc/typst.template + - src/resources/formats/beamer/pandoc/toc.tex - src/resources/formats/typst/pandoc/quarto/typst-template.typ - src/resources/formats/typst/pandoc/quarto/typst-show.typ - src/resources/extension-subtrees/orange-book/_extensions/orange-book/typst-show.typ @@ -151,7 +150,9 @@ These bypass both Pandoc metadata and Lua filters. The string lands directly in `quarto.*` is an internal namespace. The user-facing override path for localized strings is the top-level `language:` YAML key (resolved by `formatLanguage`, already merged into `options.format.language` before the builder runs). The schema does not advertise `variables.quarto.*` as a user option, but the merge in `generateDefaults` honors a user-set value on collision: it deep-merges via `mergeConfigs` (`src/core/config.ts`) so user-supplied keys win at any nesting depth while all other localized values remain available. For example, a user setting `variables.quarto.language.crossref-ch-prefix: Bouquin` overrides only that one leaf — `$quarto.language.toc-title-document$` still resolves to the localized fallback. (Same helper is used in `src/core/language.ts:formatLanguage` to merge user-supplied `language:` onto `_language.yml` defaults.) A non-object `variables.quarto` (string, number, array) is ignored defensively. New contributors to this file should design around the `language:` path, not around the escape hatch. -From any Pandoc template — built-in (`html.template`, `latex.template`, `typst-template.typ`), extension partial, or custom user template — every localized key is then accessible as: +From any Pandoc template — built-in (`template.html`, `template.tex`, `typst-template.typ`), extension partial, or custom user template — every localized key is then accessible as: + +> **Naming note**: Quarto's actual, rendered templates are `template.html` (HTML), `template.tex` (LaTeX/PDF and Beamer), and `quarto/typst-template.typ` (Typst). The similarly-named `html.template`, `latex.template`, `beamer.template`, `typst.template`, `revealjs.template`, and `asciidoc.template` files elsewhere in these `pandoc/` directories are dev-reference-only copies of Pandoc's own default templates (zero references in `src/**/*.ts` — confirmed via grep), kept solely so a maintainer can diff them against a fresh Pandoc checkout during a version bump. Citations below point at the real, rendered files. ```pandoc $quarto.language.$ @@ -173,9 +174,9 @@ It does **not** replace 2a — Lua filters still need `param("key")` because Pan Localization paths used: -- **Document `lang` attribute**: `_language.yml` lookup not involved. `lang:` flows as Pandoc-native metadata, template renders `` (`html.template:2`). -- **TOC title**: `$toc-title$` resolved via channel 2b. Used in `html.template:60`, `toc.html:3`. -- **Abstract title**: `$abstract-title$` resolved via 2b. Used in `html.template:51`, `title-block.html:15`. +- **Document `lang` attribute**: `_language.yml` lookup not involved. `lang:` flows as Pandoc-native metadata, template renders `` (`template.html:2`). +- **TOC title**: `$toc-title$` resolved via channel 2b. Used in `toc.html:3`. +- **Abstract title**: `$abstract-title$` resolved via 2b. Used in `title-block.html:15`. - **Title block labels** (`Authors`, `Affiliations`, `Published`, `Modified`, `Doi`, `Abstract`, `Keywords`): `$labels.*$` written into meta by `modules/authors.lua:854-913` (`computeLabels`). Templates: `title-metadata.html:3,4,32,43,52,61,74,83`, `manuscript/title-metadata.html:6,7,37,48,57,66,83,92`. - **Crossref text** (`Figure 1.1`, `Table 2.1`): assembled in Lua by `crossref/format.lua` using `title()` / `refPrefix()` which call `param("crossref--title"/"-prefix")`. Written as inline text directly into the AST. By the time Pandoc renders HTML, the localized prefix is already document content. - **Callout titles** (`Tip`, `Note`, etc.): `modules/callouts.lua:15,185` reads `param("callout--title", default)`, writes into the callout node. @@ -191,7 +192,7 @@ Three localization paths layered: Other Lua filters use `metaInjectLatex` for non-language LaTeX customization (`crossref/custom.lua:78`, `layout/meta.lua`, `quarto-post/landscape.lua`, etc. — they inject packages or styling, not localized strings). -3. **Pandoc template `$var$`** — `pdf/pandoc/toc.tex:3` and `pdf/pandoc/latex.template:93-94` use `$toc-title$` to set `\contentsname`. Beamer templates (`beamer/pandoc/beamer.template:146-151`, `beamer/pandoc/toc.tex:3`) likewise. +3. **Pandoc template `$var$`** — `pdf/pandoc/toc.tex:2-3` uses `$toc-title$` to set `\contentsname`. Beamer's `beamer/pandoc/toc.tex:2-3` does the same. PDF-side TS code does not read `format.language` directly except `src/format/pdf/format-pdf.ts:242` which registers `"babel-lang"` as a Quarto partial. diff --git a/llm-docs/pandoc-quarto-latex-templates.md b/llm-docs/pandoc-quarto-latex-templates.md index 7a1510434b6..784bfb11bb3 100644 --- a/llm-docs/pandoc-quarto-latex-templates.md +++ b/llm-docs/pandoc-quarto-latex-templates.md @@ -84,7 +84,7 @@ Assembles the document by including all partials in order. This is Quarto's main Quarto maintains two versions of the common preamble: -**latex.common** (264 lines): Pandoc's original, kept as reference. Contains the full common preamble: paragraph formatting, verbatim, highlighting, tables, graphics, strikeout, CSL citations, Babel, page style, tight lists, subfigure support, text direction, natbib/biblatex configuration. +**latex.common** (265 lines): Pandoc's original, kept as reference. Contains the full common preamble: paragraph formatting, verbatim, highlighting, tables, graphics, strikeout, CSL citations, Babel, page style, tight lists, subfigure support, text direction, natbib/biblatex configuration. **common.latex** (66 lines): Quarto's shortened version that: @@ -93,6 +93,8 @@ Quarto maintains two versions of the common preamble: This allows Quarto to selectively override specific parts of the common preamble while maintaining compatibility with Pandoc updates. +**Naming gotcha**: `latex.common` and `common.latex` are two *different* files that happen to share the same two words reversed - `latex.common` is Pandoc's own file (named `common.latex` in Pandoc's own repo, renamed on copy), `common.latex` is Quarto's own, unrelated in content. An extension-filtered glob like `*.latex` will silently miss `latex.common` (it ends in `.common`). When checking whether either file exists or is up to date, list the directory unfiltered rather than filtering by extension. + ### Quarto Partials (\*.tex) **Document Structure:** From 429e1d13652d52ab437797ee6f81c81fea8d9c72 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 23 Jul 2026 14:21:37 +0200 Subject: [PATCH 07/13] Add path-scoped rule pointing configuration bumps at the dependency docs Editing configuration or check.ts (the first files touched in any bundled-binary bump) had no rule surfacing dev-docs/upgrade- dependencies.md, unlike editing a pandoc/ template dir which already routes to the Pandoc-specific checklist. A bump starting at configuration - before touching any template file - got no automatic pointer at all. Also cross-referenced update-pandoc-checklist.md from upgrade- dependencies.md's general Pandoc mention, and noted that check.ts's version constraint tracks the bundled version exactly rather than a looser floor (confirmed via the 3.8.3 -> 3.10.0 history). --- .claude/rules/dev-tools/dependency-updates.md | 10 ++++++++++ dev-docs/upgrade-dependencies.md | 4 +++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 .claude/rules/dev-tools/dependency-updates.md diff --git a/.claude/rules/dev-tools/dependency-updates.md b/.claude/rules/dev-tools/dependency-updates.md new file mode 100644 index 00000000000..eb95c2d4528 --- /dev/null +++ b/.claude/rules/dev-tools/dependency-updates.md @@ -0,0 +1,10 @@ +--- +description: "Bundled dependency version bumps (Deno, Pandoc, Dart Sass, Typst, esbuild, veraPDF)" +paths: + - "configuration" + - "src/command/check/check.ts" +--- + +# Bundled Dependency Updates + +Bumping any bundled binary version starts here: see `dev-docs/upgrade-dependencies.md` for the general procedure (version numbers, `check.ts` constraints, the installer signing/notarization dry-run gate before merging). For Pandoc specifically, `dev-docs/upgrade-dependencies.md` links to `dev-docs/update-pandoc-checklist.md` (template resync, schema/lua-types checks, CI smoke-test path). diff --git a/dev-docs/upgrade-dependencies.md b/dev-docs/upgrade-dependencies.md index 555817f9bfc..d44999480e8 100644 --- a/dev-docs/upgrade-dependencies.md +++ b/dev-docs/upgrade-dependencies.md @@ -1,6 +1,8 @@ Change version numbers in `./configuration` to correspond to new versions. -Update hardcoded version strings in `src/command/check/check.ts` (`versionConstraints` array, ~line 249) so that they match the new versions in `configuration`. The `configuration` file warns about this in a comment. +Update hardcoded version strings in `src/command/check/check.ts` (`versionConstraints` array, ~line 249) so that they match the new versions in `configuration`. The `configuration` file warns about this in a comment. This constraint tracks the bundled version exactly (verified via history: `3.8.3` → `3.10.0` for Pandoc alongside the same PR's Dart Sass/Typst bumps), not a looser minimum floor. + +**Pandoc specifically** has its own checklist: [update-pandoc-checklist.md](update-pandoc-checklist.md) - template resync (with a naming gotcha that's easy to miss), schema/lua-types checks, and a `QUARTO_PANDOC`-based CI path for smoke-testing a version before it's archived to S3. ## Verify installer signing & notarization before merging (bundled binaries) From ecaf5d83599fe0c8e4b9bcb4b4933cd20d2c3b07 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 23 Jul 2026 15:14:11 +0200 Subject: [PATCH 08/13] Add --skip-archive to quarto-bld update-pandoc Splits the Pandoc update into an S3-free prep phase. --skip-archive regenerates templates and format-extension.ts from a QUARTO_PANDOC binary without archiving, configuring, or rewriting configuration, so contributors without S3 credentials can prepare and verify a bump. QUARTO_PANDOC is required so writeVariants cannot silently regenerate format-extension.ts from a stale configured Pandoc. Result is left as an uncommitted diff. --- package/src/common/update-pandoc.ts | 159 ++++++++++++++++++++++------ 1 file changed, 126 insertions(+), 33 deletions(-) diff --git a/package/src/common/update-pandoc.ts b/package/src/common/update-pandoc.ts index 9b99c17ca6a..709e5a6ab63 100644 --- a/package/src/common/update-pandoc.ts +++ b/package/src/common/update-pandoc.ts @@ -7,7 +7,7 @@ import { Command } from "cliffy/command/mod.ts"; import { join } from "../../../src/deno_ral/path.ts"; import { ensureDirSync } from "../../../src/deno_ral/fs.ts"; -import { info } from "../../../src/deno_ral/log.ts"; +import { error, info, warning } from "../../../src/deno_ral/log.ts"; import { Configuration, @@ -33,61 +33,154 @@ import { import * as ld from "../../../src/core/lodash.ts"; +import { execProcess } from "../../../src/core/process.ts"; +import { pandocBinaryPath } from "../../../src/core/resources.ts"; + export function updatePandoc() { return new Command() .name("update-pandoc") .arguments("") .description("Updates Pandoc to the specified version") - .action(async (_args, version: string) => { + .option( + "--skip-archive", + "Phase 1 prep only: regenerate templates and format-extension.ts from a QUARTO_PANDOC-provided binary without archiving to S3, configuring the binary, or bumping the configuration file. Requires QUARTO_PANDOC to point at a Pandoc binary.", + ) + // deno-lint-ignore no-explicit-any + .action(async (options: any, version: string) => { info(`Updating Pandoc to ${version}`); const configuration = readConfiguration(); + const skipArchive = !!options.skipArchive; - // Update the configuration file - info(" updating configuration file."); - const configFilePath = join( - configuration.directoryInfo.root, - "configuration", - ); - const configText = Deno.readTextFileSync(configFilePath); - const configLines = lines(configText); - const outputLines: string[] = []; - for (const line of configLines) { - if (line.startsWith("export PANDOC=")) { - outputLines.push(`export PANDOC=${version}`); - } else { - outputLines.push(line); + if (skipArchive) { + // Phase 1 (prep): no S3, no configuration rewrite. Regenerate the + // template files and format-extension.ts against a locally supplied + // binary so nothing is hand-patched by mistake. This requires a real + // Pandoc binary resolvable via QUARTO_PANDOC, because + // writeVariants() shells out to it to enumerate formats/extensions. + const overridePath = Deno.env.get("QUARTO_PANDOC"); + if (!overridePath) { + error( + "--skip-archive requires the QUARTO_PANDOC environment variable to " + + `point at a Pandoc ${version} binary. Without it, writeVariants() ` + + "would regenerate src/core/pandoc/format-extension.ts from whatever " + + "Pandoc is currently configured (likely the old version), silently " + + "corrupting it. Download the target release and set QUARTO_PANDOC, e.g.\n" + + ` QUARTO_PANDOC=/path/to/pandoc-${version}/bin/pandoc \\\n` + + ` ./package/src/quarto-bld update-pandoc ${version} --skip-archive`, + ); + throw new Error("QUARTO_PANDOC is not set"); } - } - Deno.writeTextFileSync(configFilePath, outputLines.join("\n")); - const pandocDependency = pandoc(version); + // Best-effort sanity check: warn (do not fail) if the override binary + // does not report the requested version. Version-string formatting can + // differ (e.g. a trailing .0), so a mismatch is a warning, not an error. + try { + const reported = lines( + (await execProcess({ + cmd: pandocBinaryPath(), + args: ["--version"], + stdout: "piped", + })).stdout!, + )[0]?.split(" ")[1]; + if ( + normalizePandocVersion(reported) !== normalizePandocVersion(version) + ) { + warning( + `QUARTO_PANDOC reports Pandoc ${reported}, but --skip-archive was ` + + `asked to update to ${version}. Continuing, but double-check that ` + + "QUARTO_PANDOC points at the intended binary.", + ); + } + } catch (_e) { + warning( + "Could not read the QUARTO_PANDOC binary version for the pre-flight " + + "check; continuing. writeVariants() will fail below if the binary is " + + "not runnable.", + ); + } + } else { + // Update the configuration file + info(" updating configuration file."); + const configFilePath = join( + configuration.directoryInfo.root, + "configuration", + ); + const configText = Deno.readTextFileSync(configFilePath); + const configLines = lines(configText); + const outputLines: string[] = []; + for (const line of configLines) { + if (line.startsWith("export PANDOC=")) { + outputLines.push(`export PANDOC=${version}`); + } else { + outputLines.push(line); + } + } + Deno.writeTextFileSync(configFilePath, outputLines.join("\n")); + } - // Call archive-bin-deps for this file await withWorkingDir(async (workingDir) => { - await archiveBinaryDependency(pandocDependency, workingDir); + if (!skipArchive) { + const pandocDependency = pandoc(version); - // Configure this version of pandoc - await configureDependency( - pandocDependency, - join(configuration.directoryInfo.bin, "tools"), - configuration, - ); + // Archive to S3 + await archiveBinaryDependency(pandocDependency, workingDir); - // Generate templates + // Configure this version of pandoc + await configureDependency( + pandocDependency, + join(configuration.directoryInfo.bin, "tools"), + configuration, + ); + } + + // Generate templates (always: downloads Pandoc's source zip from + // GitHub, never touches S3) await writePandocTemplates(configuration, version, workingDir); - // Generate variants + // Generate variants (always: uses the resolved Pandoc binary, which + // honors QUARTO_PANDOC) await writeVariants(configuration); }); - // print the warning to complete the checklist - console.log(bgBlack(brightWhite(bold( - "\n** Remember to complete the checklist in /dev-docs/update-pandoc-checklist.md! **", - )))); + // print the completion message + if (skipArchive) { + console.log(bgBlack(brightWhite(bold( + "\n** Phase 1 (prep) complete. **" + + "\n** Templates and src/core/pandoc/format-extension.ts were regenerated " + + "as an UNCOMMITTED working-tree diff - review with `git diff` and commit " + + "yourself. **" + + "\n** A diff in a hand-patched template or dev-reference file means " + + "Phase 1 missed that file; a diff limited to format-extension.ts is a " + + "normal generated change to review. **" + + "\n** The `configuration` file is still pinned. Phase 2 (bump " + + "`configuration` + src/command/check/check.ts, archive the binary to S3, " + + "run the create-release.yml dry-run) must be done by an S3 credential " + + "holder before merge. See dev-docs/update-pandoc-checklist.md. **", + )))); + } else { + console.log(bgBlack(brightWhite(bold( + "\n** Remember to complete the checklist in /dev-docs/update-pandoc-checklist.md! **", + )))); + } }); } +// Normalize a Pandoc version string to a 3-part "x.y.z" form for comparison, +// mirroring the semver-ish normalization in src/command/check/check.ts. +function normalizePandocVersion(version: string | undefined): string { + if (!version) { + return ""; + } + const parts = version.split("."); + if (parts.length > 3) { + return parts.slice(0, 3).join("."); + } else if (parts.length < 3) { + return parts.concat(Array(3 - parts.length).fill("0")).join("."); + } + return parts.join("."); +} + // Starting in Pandoc 3, we saw a number of variants that appear to be supported // disappear from the --list-extensions command, so for the time being we're just // hard adding them here From 3edb1c5798c9c7d19269dc4997b6b8fe6da62d3b Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 23 Jul 2026 15:15:37 +0200 Subject: [PATCH 09/13] Document two-phase Pandoc update workflow Names Phase 1 (--skip-archive prep, no S3, anyone) and Phase 2 (archival by an S3 holder before merge), records the QUARTO_PANDOC requirement, and states the reference-copy invariant: those copies are regenerated wholesale, so upstream bugs must never be hand-fixed there. --- dev-docs/update-pandoc-checklist.md | 36 ++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/dev-docs/update-pandoc-checklist.md b/dev-docs/update-pandoc-checklist.md index cd5a4077338..b1062cf43bd 100644 --- a/dev-docs/update-pandoc-checklist.md +++ b/dev-docs/update-pandoc-checklist.md @@ -1,15 +1,35 @@ ## Updating the bundled version of Pandoc -Template and rendering changes can be smoke-tested before archival: dispatch `.github/workflows/test-smokes.yml` with the `pandoc-override-version` input set to the new release tag (e.g. `3.10.1`). It downloads that Pandoc release directly from GitHub, points `quarto` at it via `QUARTO_PANDOC` for that run only, and leaves the archived/configured version untouched - no S3 access needed. Scope `buckets` to the relevant smoke-all dirs (e.g. `typst`, `latex`, `table`) for a faster signal. +Updating Pandoc happens in two phases. Phase 1 (prep) needs no S3 access and can be done by anyone; Phase 2 (archival) is done by whoever holds S3 write credentials, right before merge. -Carlos needs to run this: +### Phase 1 - prep (no S3, anyone) -- [ ] Ensure archives are upgraded -- [ ] Run `AWS_PROFILE=... ./package/src/quarto-bld update-pandoc PANDOC_VERSION` -- [ ] look at `git diff`, specifically for changes in Pandoc templates, and adjust as needed. +Patch Quarto's wired-up template files and regenerate the dev-reference copies + `src/core/pandoc/format-extension.ts`, then verify rendering - all while `configuration`'s `export PANDOC=` line stays pinned at the currently-archived version, so plain `./configure.sh`/`.cmd` keeps working for everyone else. -As a reminder, our templates are kept in the same directories as Pandoc's templates, but with different names. `git diff` will show the diff in Pandoc's template; we have to manually patch -ours. (We can't just use `patch` because the templates have diverged too much) +Run the template/variant regeneration with `--skip-archive`, pointing `QUARTO_PANDOC` at a real binary of the target version: + +```bash +QUARTO_PANDOC=/path/to/pandoc-3.10.1/bin/pandoc \ + ./package/src/quarto-bld update-pandoc 3.10.1 --skip-archive +``` + +`--skip-archive` skips the S3 archival, the `configureDependency` download, and the `configuration` rewrite. It still runs `writePandocTemplates` (regenerates every mapped template + dev-reference copy) and `writeVariants` (regenerates `format-extension.ts`). `QUARTO_PANDOC` is **required** with `--skip-archive`: `writeVariants` shells out to that binary to enumerate formats/extensions, and without the override it would silently regenerate `format-extension.ts` from the old configured Pandoc. The command leaves everything as an uncommitted working-tree diff for you to review and commit yourself; it never commits. + +Running `update-pandoc ... --skip-archive` is itself a safety-net checkpoint: if it produces a diff on a template or dev-reference file that Phase 1 was supposed to have already hand-patched, Phase 1 missed that file - exactly the naming trap described below (this bit twice in one session, on `latex.common` and on the real `styles.html`). A diff limited to `format-extension.ts` is a normal generated change to review, not a miss. The same checkpoint applies to the full Phase 2 run. + +Rendering changes can also be smoke-tested before archival: dispatch `.github/workflows/test-smokes.yml` with the `pandoc-override-version` input set to the new release tag (e.g. `3.10.1`). It downloads that Pandoc release directly from GitHub, points `quarto` at it via `QUARTO_PANDOC` for that run only, and leaves the archived/configured version untouched - no S3 access needed. Scope `buckets` to the relevant smoke-all dirs (e.g. `typst`, `latex`, `table`) for a faster signal. + +### Phase 2 - archival (S3 credential holder, before merge) + +Whoever holds S3 write credentials does this last, right before merging: + +- [ ] Bump `configuration`'s `export PANDOC=` line and the `Pandoc` entry in `src/command/check/check.ts`'s `versionConstraints` array to the new version (this constraint tracks the bundled version exactly). +- [ ] Ensure archives are upgraded. +- [ ] Run `AWS_PROFILE=... ./package/src/quarto-bld update-pandoc PANDOC_VERSION` (without `--skip-archive`) to archive the binary to S3, configure it locally, and regenerate templates/variants. +- [ ] Look at `git diff`, specifically for changes in Pandoc templates, and adjust as needed. As in Phase 1, a diff on an already-patched file means something was missed. +- [ ] Run the `create-release.yml` dry-run signing/notarization gate (see `dev-docs/upgrade-dependencies.md`), then merge. + +As a reminder, our templates are kept in the same directories as Pandoc's templates, but with different names. `git diff` will show the diff in Pandoc's template; we have to manually patch ours. (We can't just use `patch` because the templates have diverged too much) ### Pandoc templates @@ -51,7 +71,7 @@ Partials: See `llm-docs/pandoc-quarto-typst-templates.md` - typst has an extra wrinkle where Pandoc's own `template.typst` partial is also kept verbatim (same filename) alongside the renamed `typst.template`. -**All of the "Pandoc's" files above are dev-reference-only**: none of them are read by any TypeScript code path (confirmed via `grep -rn "" src/ --include="*.ts"` returning zero matches for each, across every format listed here). They exist purely so `git diff` against a fresh Pandoc checkout shows what changed upstream - patch the corresponding "Ours" file (or its own sub-partials, e.g. `tables.tex`, `toc.tex`) to actually change rendered behavior, and update the "Pandoc's" copy too so the next resync's diff stays meaningful. It's easy to update one and miss the other (or vice versa) since the filenames differ only by which segment comes first - use an unfiltered `ls`/`Glob **/*` on the directory when checking what's there, not an extension-filtered glob (e.g. `*.latex` silently excludes `latex.common`, which ends in `.common`). +**All of the "Pandoc's" files above are dev-reference-only**: none of them are read by any TypeScript code path (confirmed via `grep -rn "" src/ --include="*.ts"` returning zero matches for each, across every format listed here). They exist purely so `git diff` against a fresh Pandoc checkout shows what changed upstream - patch the corresponding "Ours" file (or its own sub-partials, e.g. `tables.tex`, `toc.tex`) to actually change rendered behavior, and update the "Pandoc's" copy too so the next resync's diff stays meaningful. It's easy to update one and miss the other (or vice versa) since the filenames differ only by which segment comes first - use an unfiltered `ls`/`Glob **/*` on the directory when checking what's there, not an extension-filtered glob (e.g. `*.latex` silently excludes `latex.common`, which ends in `.common`). Because `writePandocTemplates` (in `package/src/common/update-pandoc.ts`) overwrites every "Pandoc's" copy wholesale on each run, never hand-fix an upstream bug in one of those reference copies - the change would silently vanish on the next resync, and the reference copy must stay byte-identical to upstream so the diff stays meaningful. (This session deliberately preserved a real upstream typo in `typst.template`'s divider-fallback polyfill for exactly this reason.) Behavioral fixes belong only in the corresponding "Ours" file (or its sub-partials). ## Manual steps From f4f2ec2658732b6794c4f221e8cab6b62f5f69cb Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 23 Jul 2026 15:31:00 +0200 Subject: [PATCH 10/13] Extract Pandoc override steps into a composite action Moves the 4 inline QUARTO_PANDOC-override steps out of test-smokes.yml into .github/workflows/actions/pandoc-override, matching the existing composite-action pattern (quarto-dev, sign-files). All 4 steps gate on the same input consistently instead of being scattered inline in the workflow. --- .../actions/pandoc-override/action.yml | 33 +++++++++++++++++++ .github/workflows/test-smokes.yml | 24 +------------- 2 files changed, 34 insertions(+), 23 deletions(-) create mode 100644 .github/workflows/actions/pandoc-override/action.yml diff --git a/.github/workflows/actions/pandoc-override/action.yml b/.github/workflows/actions/pandoc-override/action.yml new file mode 100644 index 00000000000..f19ba97792c --- /dev/null +++ b/.github/workflows/actions/pandoc-override/action.yml @@ -0,0 +1,33 @@ +name: "Pandoc Override" +description: "Installs an unarchived Pandoc release and points quarto at it via QUARTO_PANDOC, for smoke-testing a version before it has been archived to S3. No-op unless pandoc-version is set - the archived/configured binary is used as normal otherwise." + +inputs: + pandoc-version: + description: "Pandoc release tag to install and use instead of the archived/configured version (e.g. 3.10.1). Leave empty to skip." + required: false + default: "" + +runs: + using: "composite" + steps: + - name: Install Pandoc override + if: inputs.pandoc-version != '' + uses: r-lib/actions/setup-pandoc@v2 + with: + pandoc-version: ${{ inputs.pandoc-version }} + + - name: Point QUARTO_PANDOC at override (Linux) + if: runner.os == 'Linux' && inputs.pandoc-version != '' + shell: bash + run: echo "QUARTO_PANDOC=$(command -v pandoc)" >> "$GITHUB_ENV" + + - name: Point QUARTO_PANDOC at override (Windows) + if: runner.os == 'Windows' && inputs.pandoc-version != '' + shell: pwsh + run: | + "QUARTO_PANDOC=$((Get-Command pandoc).Source)" >> $env:GITHUB_ENV + + - name: Confirm Pandoc override is live + if: inputs.pandoc-version != '' + shell: bash + run: quarto pandoc --version diff --git a/.github/workflows/test-smokes.yml b/.github/workflows/test-smokes.yml index 7cdd8596a78..9e0e3f78fb1 100644 --- a/.github/workflows/test-smokes.yml +++ b/.github/workflows/test-smokes.yml @@ -234,32 +234,10 @@ jobs: - uses: ./.github/workflows/actions/quarto-dev - # Optional override: installs an unarchived Pandoc release and points - # quarto at it via QUARTO_PANDOC, for smoke-testing a version before it - # has been archived to S3. No-op unless pandoc-override-version is set - - # the archived/configured binary is used as normal otherwise. - - name: Install Pandoc override - if: inputs.pandoc-override-version != '' - uses: r-lib/actions/setup-pandoc@v2 + - uses: ./.github/workflows/actions/pandoc-override with: pandoc-version: ${{ inputs.pandoc-override-version }} - - name: Point QUARTO_PANDOC at override (Linux) - if: runner.os == 'Linux' && inputs.pandoc-override-version != '' - shell: bash - run: echo "QUARTO_PANDOC=$(command -v pandoc)" >> "$GITHUB_ENV" - - - name: Point QUARTO_PANDOC at override (Windows) - if: runner.os == 'Windows' && inputs.pandoc-override-version != '' - shell: pwsh - run: | - "QUARTO_PANDOC=$((Get-Command pandoc).Source)" >> $env:GITHUB_ENV - - - name: Confirm Pandoc override is live - if: inputs.pandoc-override-version != '' - shell: bash - run: quarto pandoc --version - - name: Install Tinytex env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 18e168476d87f699bf86bf870868ad527acb6ca4 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 23 Jul 2026 15:47:31 +0200 Subject: [PATCH 11/13] Fix Windows quarto resolution in pandoc-override action The confirm step forced shell: bash (Git Bash/MSYS), which doesn't apply Windows PATHEXT and can't resolve a bare quarto when only quarto.cmd is on PATH - failed with exit 127 command not found on the first CI dispatch. Splits the step per OS like the existing QUARTO_PANDOC-setting steps: bash on Linux, pwsh on Windows (which does apply PATHEXT). --- .github/workflows/actions/pandoc-override/action.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/actions/pandoc-override/action.yml b/.github/workflows/actions/pandoc-override/action.yml index f19ba97792c..f94dd2dd901 100644 --- a/.github/workflows/actions/pandoc-override/action.yml +++ b/.github/workflows/actions/pandoc-override/action.yml @@ -27,7 +27,12 @@ runs: run: | "QUARTO_PANDOC=$((Get-Command pandoc).Source)" >> $env:GITHUB_ENV - - name: Confirm Pandoc override is live - if: inputs.pandoc-version != '' + - name: Confirm Pandoc override is live (Linux) + if: runner.os == 'Linux' && inputs.pandoc-version != '' shell: bash run: quarto pandoc --version + + - name: Confirm Pandoc override is live (Windows) + if: runner.os == 'Windows' && inputs.pandoc-version != '' + shell: pwsh + run: quarto pandoc --version From b86843f25c924aa4d6b99b3fa41762d6e4a987b5 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 23 Jul 2026 15:52:13 +0200 Subject: [PATCH 12/13] Add README index for composite actions Neither .github/actions/ nor .github/workflows/actions/ had a reference listing what each composite action does. Adds one README per directory; doesn't merge or reorganize the two locations. --- .github/actions/README.md | 9 +++++++++ .github/workflows/actions/README.md | 13 +++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 .github/actions/README.md create mode 100644 .github/workflows/actions/README.md diff --git a/.github/actions/README.md b/.github/actions/README.md new file mode 100644 index 00000000000..0e41f453e9a --- /dev/null +++ b/.github/actions/README.md @@ -0,0 +1,9 @@ +# Composite Actions + +Reusable steps shared across workflows in this repo. See also `.github/workflows/actions/` for another set of composite actions (historical split, no functional difference). + +| Action | Purpose | +|--------|---------| +| [`cache-typst`](cache-typst/action.yml) | Configures caching for Typst packages. | +| [`docker`](docker/action.yml) | Builds and pushes a Quarto Docker container to the GitHub Container Registry. | +| [`merge-extension-tests`](merge-extension-tests/action.yml) | Copies test files from extension subtrees into the main test directories. | diff --git a/.github/workflows/actions/README.md b/.github/workflows/actions/README.md new file mode 100644 index 00000000000..b0665d2cec1 --- /dev/null +++ b/.github/workflows/actions/README.md @@ -0,0 +1,13 @@ +# Composite Actions + +Reusable steps shared across workflows in this repo. See also `.github/actions/` for another set of composite actions (historical split, no functional difference). + +| Action | Purpose | +|--------|---------| +| [`amplitude`](amplitude/action.yml) | Sends an event to Amplitude. | +| [`archive`](archive/action.yml) | Archives a dependency binary to S3. | +| [`keychain`](keychain/action.yml) | Configures a P12 certificate into the keychain for signing. | +| [`pandoc-override`](pandoc-override/action.yml) | Installs an unarchived Pandoc release and points quarto at it via `QUARTO_PANDOC`, for smoke-testing a version before it has been archived to S3. | +| [`prevent-rerun`](prevent-rerun/action.yml) | Fails if the workflow is re-run (`github.run_attempt > 1`). | +| [`quarto-dev`](quarto-dev/action.yml) | Configures the image for Quarto development (runs `configure.sh`/`.cmd`). | +| [`sign-files`](sign-files/action.yml) | Installs and configures the environment to sign files. | From 1ac5a40ab36ee727c0abd770c9471ecb8db79281 Mon Sep 17 00:00:00 2001 From: Christophe Dervieux Date: Thu, 23 Jul 2026 17:21:58 +0200 Subject: [PATCH 13/13] Make QUARTO_PANDOC version mismatch fatal in --skip-archive toolsPath() (src/core/resources.ts) silently falls back to the bundled/configured Pandoc if QUARTO_PANDOC is unset, nonexistent, or otherwise fails to resolve - only a warnOnce, no error. The prior preflight only warned on a version mismatch and continued into writeVariants(), which would then silently regenerate format-extension.ts from the wrong (old, bundled) Pandoc - the exact corruption --skip-archive exists to prevent. Both the unreadable-version and mismatched-version cases now abort instead of warning. --- package/src/common/update-pandoc.ts | 46 ++++++++++++++++++----------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/package/src/common/update-pandoc.ts b/package/src/common/update-pandoc.ts index 709e5a6ab63..fba0bfcbc25 100644 --- a/package/src/common/update-pandoc.ts +++ b/package/src/common/update-pandoc.ts @@ -7,7 +7,7 @@ import { Command } from "cliffy/command/mod.ts"; import { join } from "../../../src/deno_ral/path.ts"; import { ensureDirSync } from "../../../src/deno_ral/fs.ts"; -import { error, info, warning } from "../../../src/deno_ral/log.ts"; +import { error, info } from "../../../src/deno_ral/log.ts"; import { Configuration, @@ -72,32 +72,42 @@ export function updatePandoc() { throw new Error("QUARTO_PANDOC is not set"); } - // Best-effort sanity check: warn (do not fail) if the override binary - // does not report the requested version. Version-string formatting can - // differ (e.g. a trailing .0), so a mismatch is a warning, not an error. + // Fatal check: pandocBinaryPath() silently falls back to the bundled/ + // configured Pandoc (with only a warnOnce) if QUARTO_PANDOC is unset, + // nonexistent, or otherwise fails to resolve (src/core/resources.ts + // toolsPath()). If the resolved binary doesn't report the requested + // version, continuing would regenerate format-extension.ts from the + // wrong (likely old, bundled) Pandoc - the exact silent corruption + // this flag exists to prevent - so this must fail, not warn. + let reported: string | undefined; try { - const reported = lines( + reported = lines( (await execProcess({ cmd: pandocBinaryPath(), args: ["--version"], stdout: "piped", })).stdout!, )[0]?.split(" ")[1]; - if ( - normalizePandocVersion(reported) !== normalizePandocVersion(version) - ) { - warning( - `QUARTO_PANDOC reports Pandoc ${reported}, but --skip-archive was ` + - `asked to update to ${version}. Continuing, but double-check that ` + - "QUARTO_PANDOC points at the intended binary.", - ); - } } catch (_e) { - warning( - "Could not read the QUARTO_PANDOC binary version for the pre-flight " + - "check; continuing. writeVariants() will fail below if the binary is " + - "not runnable.", + error( + "Could not run the QUARTO_PANDOC binary to verify its version. " + + `QUARTO_PANDOC is set to "${overridePath}" - confirm it points at a ` + + "runnable Pandoc executable.", + ); + throw new Error("QUARTO_PANDOC binary is not runnable"); + } + if ( + normalizePandocVersion(reported) !== normalizePandocVersion(version) + ) { + error( + `The Pandoc binary resolved via QUARTO_PANDOC reports version ` + + `${reported}, not the requested ${version}. QUARTO_PANDOC is set to ` + + `"${overridePath}" - if that path doesn't exist or isn't a file, ` + + "pandocBinaryPath() silently fell back to the bundled/configured " + + "Pandoc, which would corrupt format-extension.ts if this continued. " + + "Fix QUARTO_PANDOC and retry.", ); + throw new Error("QUARTO_PANDOC does not resolve to the requested version"); } } else { // Update the configuration file