Skip to content

Hephaestus plot documents (.hep): render to SVG with brand colors, and live preview in q2 preview / hub-client (bd-3qych45b) - #688

Merged
cscheid merged 7 commits into
mainfrom
feature/bd-3qych45b-hephaestus-hep-svg
Sep 19, 2026
Merged

cscheid merged 7 commits into
mainfrom
feature/bd-3qych45b-hephaestus-hep-svg

Conversation

@cscheid

@cscheid cscheid commented Sep 17, 2026

Copy link
Copy Markdown
Member

Summary

Phases 1 and 2 of hephaestus support (bd-3qych45b, plan: claude-notes/plans/2026-09-17-hephaestus-hep-integration.md).

Phase 1 — q2 render (native)

  • ![](plot.hep) in html / revealjs output is rendered at render time with hephaestus's renderer-free SVG backend (no wgpu, no GPU), stored as a page-scoped artifact under <stem>_files/figure-html/, and the image is pointed at it. Size comes from width/height attrs, else the document's own hint, else 7in × 5in at 96 dpi; the plot re-flows at that size rather than being scaled.
  • Brand colors: with brand: declared, the light brand's background / foreground / primary become the plot's paper / ink / accent. Only hex values cross; anything else warns (Q-19-4).
  • Bundled Roboto faces (resources/hephaestus/fonts/, OFL) are registered once per process and mapped onto sans-serif, so the same .hep renders byte-identically on every machine and matches what hephaestus's browser client draws.
  • New image diagnostics subsystem, Q-19-1..Q-19-4, each with a docs page and sidebar entry. Failures are warnings and leave the image as written. (Numbered 18 until the merge of main, where the new engine subsystem had taken 18.)
  • transforms/image_walk.rs: the mutable body-image walker extracted from responsive_image so both transforms share one container set.
  • Runnable example: examples/plots/01-hephaestus-basic (captioned figure, explicit size, missing-file case, dark brand).

Phase 2 — q2 preview and hub-client (bd-sxiv2tio)

  • HepImage in @quarto/preview-renderer is the registry's Image entry: for a local .hep target it fetches the asset-walker bytes and mounts a live PlotView from the npm hephaestus-svg-wasm client (0.4.1, exact-pinned to the crate version; parity tests on both sides); anything else delegates to the plain <img>. Covers q2-preview documents and format: revealjs decks in both q2 preview and hub-client, since RevealDeck renders slide content through the same registry.
  • The client loads on demand (cached dynamic import); plot-free documents never fetch the 2.4 MB wasm. Its Roboto faces ship as hashed assets in every bundle (SPA, hub-client, sandboxed frame).
  • Sizing: width attr → document hint → 672 px, capped at the column, height via CSS aspect-ratio, so narrow columns and slides get a re-solved layout while wide columns match q2 render.
  • .hep added to the binary-extension allowlists (quarto-hub::resource and its quarto-automerge-schema mirror) — without it the bytes never reached the preview VFS.
  • Manifest miss → "file not found in the project" error box; load failures → error box with the message.
  • Not in this PR: the separate-origin sandboxed frame (bd-9t5nmq81) and brand colors in the browser, which need an upstream palette API on the wasm client (bd-qcltc0ll).

Why SVG and not PNG

hephaestus rasterizes through wgpu, which needs a GPU adapter a headless render machine may not have, and GPU output is not byte-stable across machines. The SVG backend needs no GPU, keeps text as real <text>, and is deterministic. PDF/Typst outputs plug into the same transform later (latex → the pdf backend, typst → SVG) — bd-br9bmysi.

Dependency notes

  • quarto-core gains hephaestus = 0.4.1 (document-read, svg, png) in the native-only table; both Cargo.locks pick up parley / fontique / skrifa / kurbo / peniko / clipper2-rust / png. All pure Rust. The main WASM bundle does not compile hephaestus.
  • On Linux, parley → fontique would link libfontconfig at build time by default. fontique's fontconfig-dlopen feature is enabled for Linux so building q2 does not require libfontconfig1-dev; a missing library at run time just yields an empty system font collection (fine — plot text uses the bundled faces).
  • preview-renderer gains hephaestus-svg-wasm = 0.4.1 (npm, MIT); optimizeDeps.exclude in hub-client and the SPA keeps dev pre-bundling from breaking its import.meta.url asset lookups.

Test plan

  • crates/quarto-core/tests/integration/hephaestus_render.rs: end-to-end tests through render_to_file (artifact path, determinism, bundled font, sizing, id prefixes, revealjs, untouched non-hep images, missing / invalid file, brand applied / non-hex slot / no brand) + unit tests, incl. the npm-version parity test.
  • HepImage.test.tsx (20 cases: mount, delegation, external/data targets, load-once, errors, free() on unmount, remount on new bytes, sizing rules, a11y attrs, registry layering, version pin vs package.json and Cargo.lock); resource.rs + fileType.test.ts for the .hep extension.
  • Full cargo xtask verify green (Rust + WASM + hub-client build/tests + SPA), before and after merging main.
  • cargo run --bin q2 -- render examples/plots/01-hephaestus-basic, output inspected.
  • Browser checks (recorded in the plan): q2 preview html document + revealjs deck; hub-client (q2 preview --ui editor) format: q2-preview document + revealjs deck. Plots mount as reflowed SVG; missing file shows the error box; .png stays an <img>.
  • CI green on Linux, macOS and Windows on the phase-1 and phase-2 pushes.

🤖 Generated with Claude Code

cscheid and others added 3 commits September 17, 2026 11:26
…ych45b)

Phase 1 of the hephaestus integration plan
(claude-notes/plans/2026-09-17-hephaestus-hep-integration.md): an
`![](plot.hep)` image in `html` / `revealjs` output is rendered at
render time with hephaestus's renderer-free SVG backend, stored as a
page-scoped artifact under `<stem>_files/figure-html/`, and the image
is pointed at it. Preview (`q2-preview` / `q2-slides`) is deliberately
left to a React component over the npm `hephaestus-svg-wasm` client
(bd-sxiv2tio); the transform is native-only and excluded from the
preview pipeline like `mermaid-render`.

- `transforms/hephaestus.rs`: `HephaestusRenderTransform`
  (Finalization, after `resource-collector`, before
  `responsive-image`). Size from `width`/`height` attrs, else the
  document's hint, else 7in x 5in at 96 dpi. Content+size-addressed
  artifact names; per-image SVG id prefixes. Fail-soft warnings
  `Q-18-1` (not found), `Q-18-2` (unreadable / wrong format version),
  `Q-18-3` (backend degradation) — a new `image` diagnostics
  subsystem with docs pages and sidebar section.
- `transforms/image_walk.rs`: the mutable body-image walker extracted
  from `responsive_image` so both transforms share one container set.
- `resources/hephaestus/fonts/`: the four Roboto faces hephaestus's
  browser clients ship, registered once per process and mapped onto
  `sans-serif`, so the same `.hep` renders byte-identically on every
  machine and matches what the wasm client draws. OFL licence included.
- quarto-core: `hephaestus = 0.4.1` (`document-read`, `svg`, `png`; no
  wgpu) in the native-only dependency table. On Linux, fontique's
  `fontconfig-dlopen` is enabled so building q2 does not require
  `libfontconfig1-dev`; a missing library at run time just yields an
  empty system font collection.
- Tests: `tests/integration/hephaestus_render.rs` (11 end-to-end
  through `render_to_file`: artifact path, determinism, bundled font,
  sizing, id prefixes, revealjs, untouched non-hep images, both failure
  modes) plus 4 unit tests. `cargo xtask verify` green.
- Docs: "Plot Documents" section in the figures guide.

No snapshot files changed. Both `Cargo.lock`s gain hephaestus's text
stack (parley / fontique / skrifa / kurbo / peniko / clipper2-rust /
png); the WASM crate's lock records them but the crate never compiles
them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…example (bd-3qych45b)

A runnable project for checking the hephaestus integration by hand:
one plot document referenced as a captioned figure, at an explicit
size, and via a missing path (the fail-soft Q-18-1 case). Render with
`cargo run --bin q2 -- render examples/plots/01-hephaestus-basic`.
Render output is gitignored like the diagrams examples.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
When a document declares a brand, the light brand's `background`,
`foreground` and `primary` colors become each rendered `.hep` plot's
`paper`, `ink` and `accent`. hephaestus's built-in themes derive every
chrome color from those anchors, so a dark-paper / light-ink brand
inverts the whole plot in one step; data series keep their own
color-scale colors. Only hex values cross into the plot — a named CSS
color or `rgb()` function warns once per document (`Q-18-4`, new page
+ sidebar entry) and leaves that anchor as the document defines it.
The palette is part of the artifact hash, so a brand change is a new
artifact name.

Pulled into phase 1 at the user's request while testing the example;
phase 3 (bd-l6e3sd45) stays typography-only. Tests: three end-to-end
(brand applied, non-hex slot warns and the rest still apply, no brand
leaves the document's palette) plus a hex-parser unit test.
`cargo xtask verify --skip-hub-build` green; the change is inside the
native-only module.

examples/plots/01-hephaestus-basic gains `brand.yml` (night paper,
chalk ink, coral accent) and `brand: brand.yml` in the document, with
the README and the figures guide describing the mapping.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@posit-snyk-bot

posit-snyk-bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

cscheid and others added 4 commits September 17, 2026 13:31
…iv2tio)

Phase 2 of the hephaestus integration: `![](plot.hep)` now draws in the
browser for q2-preview documents and `format: revealjs` decks, in both
`q2 preview` and hub-client, through the npm `hephaestus-svg-wasm`
client (0.4.1, pinned to the `hephaestus` crate version; parity tests
on both sides).

- preview-renderer: `HepImage` wraps the built-in `Image` as the
  registry's `Image` entry. For a local `.hep` target it fetches the
  asset-walker bytes and mounts `PlotView` (light, auto-resize, no
  picking); everything else delegates to `<img>`. The client loads on
  demand with a cached dynamic import. Sizing: `width` attr -> document
  hint -> 672x480, `max-width: 100%`, height via CSS `aspect-ratio`, so
  narrow columns and slides get a re-solved layout. Manifest miss ->
  "file not found" error box (no fetch); load failures -> error box.
- `.hep` added to the binary-extension allowlists in
  `quarto-hub::resource` and `quarto-automerge-schema` (MIME
  `application/vnd.hephaestus.plot`), so the bytes sync into the
  preview VFS at all.
- `optimizeDeps.exclude` for the client in hub-client and the SPA (dev
  pre-bundling breaks the glue's `import.meta.url` asset lookups).
- Docs: preview note in the figures guide. Plan records decisions,
  findings, and the browser evidence for both hosts.

Follow-ups filed: bd-9t5nmq81 (sandboxed frame), bd-qcltc0ll (brand
colors need an upstream palette API).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Conflicts in the error catalog and the errors sidebar: main added an
`engine` subsystem numbered 18 while this branch had numbered its
`image` subsystem 18. The image codes move to `Q-19-1`..`Q-19-4`
(catalog, docs pages renamed, sidebar, transform, tests, examples,
preview component comments, plan). Both subsystems keep their pages.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@cscheid cscheid changed the title Render hephaestus plot documents (.hep) to SVG, with brand colors (bd-3qych45b) Hephaestus plot documents (.hep): render to SVG with brand colors, and live preview in q2 preview / hub-client (bd-3qych45b) Sep 19, 2026
@cscheid
cscheid merged commit e0406a7 into main Sep 19, 2026
10 checks passed
@cscheid
cscheid deleted the feature/bd-3qych45b-hephaestus-hep-svg branch September 19, 2026 22:39
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.

2 participants