Skip to content

feat(publish): replace publishing system with Quarto#33

Merged
vanandrew merged 6 commits into
mainfrom
feat/quarto-publishing
Jun 17, 2026
Merged

feat(publish): replace publishing system with Quarto#33
vanandrew merged 6 commits into
mainfrom
feat/quarto-publishing

Conversation

@vanandrew

Copy link
Copy Markdown
Owner

Summary

Replaces jamb's document publishing system with a single, Quarto-based pipeline. The previous design had two independently hand-maintained renderers — publish/formats/html.py (raw string concatenation) and publish/formats/docx.py (manual python-docx OXML) — with Markdown handled separately in the CLI. This is a ground-up replacement: the old publish/formats/ package is deleted and the new code stands on its own.

Requirement items now flow through one model → Quarto markdown (.qmd) → Quarto, which renders HTML, DOCX, and PDF. Markdown and raw .qmd are written directly without invoking the binary.

What's new

  • PDF output via Quarto's bundled Typst engine (no LaTeX) — the original motivation.
  • Table of contents and consistent, professional styling across all formats.
  • Working cross-references: item links resolve to HTML anchors, DOCX bookmarks, and PDF internal links automatically (replacing the hand-rolled bookmark/hyperlink OXML).
  • Data-driven styling: a bundled SCSS theme (HTML) and a Word reference document, both overridable with --template. jamb template now scaffolds these editable assets into ./jamb-assets/.

Architecture

src/jamb/publish/ is split into a pure layer and a render layer:

  • formats.pyOutputFormat enum + extension mapping
  • document.pybuild_publish_document() → ordered PublishDocument (pure)
  • qmd.pyPublishDocument.qmd source via Jinja2 (pure, unit-tested without the binary)
  • quarto.py — locate/run the Quarto binary; typed errors
  • render.py — generate .qmd in a temp dir and invoke Quarto
  • assets/ — bundled document.qmd.j2 and theme.scss

CLI

jamb publish PREFIX [PATH] gains --pdf; --template is now format-aware (SCSS / reference .docx / Typst template). .pdf and .qmd are auto-detected from the extension. jamb template scaffolds the styling assets instead of generating a .docx.

Dependencies

  • Removed python-docx.
  • Added quarto-cli as a core dependency (per design decision — publishing works out of the box).
  • jinja2 is now actually used (previously declared but unused).

⚠️ Note on footprint: the quarto-cli PyPI package downloads the ~150 MB Quarto binary at install time and hard-depends on jupyter/nbclient (jamb never executes notebooks, but the tree installs regardless). This meaningfully increases install size and uv sync time for every install, including pytest-plugin-only users. CI now enables uv caching so the build isn't repeated each run. Flagging in case we'd prefer an optional jamb[publish] extra instead — easy to switch.

Tests

  • Deleted the old HTML/DOCX byte-assertion unit tests.
  • Added tests/unit/test_publish_qmd.py — pure tests for the document model, .qmd generation, markdown escaping, format mapping, and binary resolution (no Quarto needed).
  • Added tests/integration/test_publish_render.py — real renders asserting magic bytes and surviving anchors, marked @pytest.mark.quarto.
  • Render-producing CLI/integration tests are marked quarto and auto-skip when the binary is absent (conftest hook).
  • Full suite: 1471 passed, 1 skipped; ruff, ruff-format, and ty all clean.

Docs

Updated the publishing guide, command reference, tutorial, installation, design, FAQ, troubleshooting, API reference, README, and CONTRIBUTING to describe the Quarto pipeline and PDF output.

Verification

  • uv sync && uv run quarto --version → resolves
  • uv build --wheel → bundles publish/assets/*
  • Manual: jamb publish all out.{html,docx,pdf,md,qmd} all render; jamb template scaffolds theme + reference doc; --template overrides apply.

🤖 Generated with Claude Code

@codecov

codecov Bot commented Jun 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.26646% with 47 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.92%. Comparing base (9d6b03d) to head (2691f68).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/jamb/cli/commands.py 85.00% 11 Missing and 4 partials ⚠️
src/jamb/publish/quarto.py 68.18% 12 Missing and 2 partials ⚠️
src/jamb/publish/docx_reference.py 80.95% 6 Missing and 2 partials ⚠️
src/jamb/publish/render.py 88.37% 2 Missing and 3 partials ⚠️
src/jamb/publish/document.py 85.18% 2 Missing and 2 partials ⚠️
src/jamb/publish/qmd.py 98.07% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #33      +/-   ##
==========================================
- Coverage   91.84%   90.92%   -0.93%     
==========================================
  Files          27       31       +4     
  Lines        3852     3770      -82     
  Branches      867      857      -10     
==========================================
- Hits         3538     3428     -110     
- Misses        198      221      +23     
- Partials      116      121       +5     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

vanandrew added a commit that referenced this pull request Jun 16, 2026
…34)

## Summary

Aligns jamb's ruff configuration with the **median** project's, which
uses a curated lint rule set instead of jamb's defaults-only config.

### Config changes (`pyproject.toml`)

- **`[tool.ruff.lint]`** — select `N` (pep8-naming), `UP` (pyupgrade),
`B` (bugbear), `SIM` (simplify), and `RUF` on top of the existing
`E`/`W`/`F`/`I`; ignore `E501` (the formatter handles line length).
- **`[tool.ruff.format]`** — explicit `quote-style = "double"`,
`indent-style = "space"`, `docstring-code-format = true`.

### Adapted, not copied verbatim

These median values are project-specific, so I kept jamb's:

- **`target-version`** stays `py310` (median is `py311`). jamb's
`requires-python` is `>=3.10`; copying `py311` would let `pyupgrade`
rewrite code with 3.11-only syntax and break 3.10 support.
- **`line-length`** stays `120` (median is `88`) — keeping it avoids
reformatting the entire codebase in this change.
- median's `craniotrace`/`tissunet`/`clinseg` per-file-ignores are not
relevant to jamb and were not copied.

### Code fixes

The new rules surfaced **223 violations**, all resolved:

- **Auto-fixed** (safe + unsafe): import sorting, `pyupgrade`
modernizations, `contextlib.suppress`, ternaries, `next(iter(...))`,
sorted `__all__`, etc.
- **143 `RUF002`** — EN DASH (`–`) in docstrings (almost all in
`test_comprehensive.py` titles) replaced with ASCII hyphens.
- **Manual** — combined nested `with`/`if` statements, lowercased
in-function constants (e.g. DFS `WHITE/GRAY/BLACK` →
`white/gray/black`), and escaped/raw-stringed regex metacharacters in
`pytest.raises(match=...)`.

Behavior is unchanged: ruff check, ruff format, `ty`, and the full test
suite (**1478 passed, 1 skipped**) are all green.

> **Merge note:** this branch is off `main` and lightly edits
`src/jamb/publish/formats/docx.py` and `html.py`, which PR #33 (Quarto
publishing) deletes. Whichever PR merges second will hit a trivial
modify/delete conflict on those two files — resolve by taking the
deletion.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the publishing system with a single Quarto-based pipeline. Items are
assembled into a document model, rendered to Quarto markdown (.qmd), then
rendered to HTML, DOCX, and PDF by the bundled Quarto binary; Markdown and raw
.qmd are written directly.

- Output formats: HTML, DOCX, PDF (new), Markdown, and raw .qmd
- Cross-references between items resolve to HTML anchors, DOCX bookmarks, and
  PDF links; every format gets a table of contents
- Styling is data-driven: a bundled SCSS theme for HTML and a Word reference
  document, both overridable via --template; `jamb template` scaffolds them
- quarto-cli is a core dependency so publishing works out of the box
- The publish package splits a pure layer (document model + .qmd generation,
  unit-tested without the binary) from a render layer that invokes Quarto

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vanandrew vanandrew force-pushed the feat/quarto-publishing branch from 0371983 to b98d6e3 Compare June 16, 2026 05:33
…items

- heading items map their level field to heading depth (h1-h6, clamped)
- info items render as an anchored heading plus a Quarto note callout, giving
  them a distinct boxed style; the anchor lives on the heading so cross-
  references into an info item resolve in PDF/Typst (a callout id alone breaks
  the Typst render)
- requirement items remain a level-2 heading plus body text

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vanandrew vanandrew force-pushed the feat/quarto-publishing branch from b98d6e3 to a3cc413 Compare June 16, 2026 05:36
vanandrew and others added 4 commits June 16, 2026 00:48
- Rework the default HTML theme: refined typography, prominent document-section
  banners, UID cross-references styled as monospace chips, tuned note callouts,
  and table/print styles.
- Add [tool.jamb] settings to apply styling on every publish without repeating
  --template: publish_html_theme, publish_docx_reference, publish_pdf_template.
  An explicit --template still overrides. Missing configured files error clearly.
- Make `jamb template` scaffold only the HTML theme by default; the Word
  reference document (which styles DOCX — SCSS does not) is now opt-in via
  --docx. The command also prints the pyproject.toml snippet to wire it up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make PDF and DOCX match the HTML theme as closely as each format allows, so a
published document looks the same regardless of format: near-black text, a
single blue accent, and a clean sans-serif typeface.

- Rework the default HTML theme (theme.scss): clean modern look — SF/system
  type, hairline dividers, pill-shaped UID cross-references, soft note callouts.
- PDF: bundle a Typst preamble (typst-theme.typ) included in the header, mapping
  the same palette/font/margins; applied by default, overridable via --template.
- DOCX: generate the Word reference document at runtime by restyling Pandoc's
  default to the same palette and typeface (docx_reference.py) — applied by
  default, overridable via --template. No binary asset is committed.
- `jamb template` scaffolds the HTML and PDF text themes by default; --docx adds
  the Word reference. Refresh docs/_static/publish-example.png to the new look.

Format-specific HTML touches (pill chips, rounded cards) do not carry to PDF/DOCX.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Render published documents as a formal specification, consistently across HTML,
PDF, and DOCX:

- Title block with a metadata line (document id, version, date, status). Version
  comes from project/config, date from the render, status from a new
  [tool.jamb].publish_status setting.
- Numbered sections (1, 1.1, 1.1.1) and a "Contents" table of contents; HTML
  renders the TOC inline as a contents section rather than a sidebar.
- Serif body with sans-serif headings and a conservative blue accent: theme.scss
  (HTML), typst-theme.typ with a "Page X of Y" footer (PDF), and the runtime
  Word reference document (DOCX, Georgia body / Helvetica Neue headings).

Threads an optional subtitle through PublishDocument and build_publish_document;
the CLI composes it from config plus the render date. Refresh
docs/_static/publish-example.png and the publishing/configuration docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When publishing every document, the subtitle now starts with the version/date
rather than a redundant "All documents" label. Single-document output keeps its
"Document <PREFIX>" label.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vanandrew vanandrew merged commit ae0b41a into main Jun 17, 2026
9 checks passed
@vanandrew vanandrew deleted the feat/quarto-publishing branch June 17, 2026 01:12
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