fix(typst): emit compilable mermaid embeds and add body-only mermaid API#70
Conversation
The Typst renderer emitted #image.decode(bytes.fromBase64("…")), but
bytes.fromBase64 does not exist in Typst and image.decode is deprecated —
every document with a rendered mermaid block failed typst compile. The
tests only asserted the emitted string, so this never met a real typst.
- Embed diagrams as image(bytes("<svg…>"), format: "svg") with the SVG
source in a string literal (quotes/backslashes escaped, raw newlines
legal; verified against typst 0.14.2).
- Render diagrams at natural size, scaling down only when wider than the
line width (layout/measure wrapper) — matches how LaTeX pipelines size
images instead of blowing small flowcharts up to full text width.
- Add renderTypstWithMermaid / renderToWriterWithMermaid: body-only Typst
rendering with the mermaid hook, for callers that supply their own
preamble (e.g. PolicyPress).
- Bump version to 0.7.2.
Signed-off-by: TsunamiNoAi <spam@falseblue.com>
- nix run .#bump -- <version|patch|minor|major> bumps build.zig.zon and rolls the CHANGELOG [Unreleased] section into a dated release section (ported from PolicyPress). - .githooks/pre-push rejects release-tag pushes without a CHANGELOG change since the previous tag; installed by the devshell shellHook. - The release workflow enforces the same rule as an un-bypassable backstop before building artifacts. Signed-off-by: TsunamiNoAi <spam@falseblue.com>
|
Also added here per request: the |
There was a problem hiding this comment.
Pull request overview
This PR fixes Typst mermaid rendering so the emitted Typst compiles (by embedding SVG source directly instead of using nonexistent/deprecated Typst APIs), adds a new body-only Typst rendering API with the mermaid hook for downstream consumers that provide their own preamble, and tightens the release process around changelog/versioning.
Changes:
- Replace Typst mermaid output from
#image.decode(bytes.fromBase64(...))to a compilablebytes("...")+image(..., format: "svg")embed with intrinsic sizing capped to line width. - Add public body-only Typst rendering with mermaid hook (
renderTypstWithMermaid/typst.renderToWriterWithMermaid) and expand tests to cover string-literal escaping and the new API. - Bump version to 0.7.2 and add release tooling/guards (bump helper, pre-push hook, CI changelog enforcement).
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/root.zig | Exposes a new body-only Typst API with mermaid hook and updates public mermaid docs. |
| src/markdown/renderers/typst.zig | Implements the new Typst mermaid embedding strategy, adds body-only mermaid writer API, and expands tests. |
| README.md | Updates architecture/API documentation for Typst mermaid support and new entry point. |
| flake.nix | Installs a version-controlled pre-push hook and adds a nix run .#bump helper app. |
| CHANGELOG.md | Adds 0.7.2 release notes documenting the Typst mermaid fix and new API. |
| build.zig.zon | Bumps package version to 0.7.2. |
| .github/workflows/release.yml | Ensures full git history for tags and enforces changelog updates on release tags. |
| .githooks/pre-push | Adds a local guard preventing release tags without changelog updates. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ownership Copilot review on #70 flagged that the public docs describe the Typst mermaid output as "inline #image calls" while the renderer actually emits a #{ … } block that binds the SVG via bytes("…") and calls image(…, format: "svg") inside a layout/measure wrapper. Align README + all four Typst mermaid docstrings with the real output. Also document MermaidRendererFn's ownership contract: the returned slice must be allocated with the allocator passed to the callback, since both the HTML and Typst renderers free it with that allocator. Returning static memory would trigger an invalid free. Docs-only; no behavior change. zig build test passes.
…llback ownership Second round of Copilot review on #70: - CHANGELOG.md: add the `## [Unreleased]` header the `nix run .#bump` helper rolls into a dated release. Without it, bump would update build.zig.zon but leave the changelog untouched, then trip the pre-push/CI release guard. - flake.nix bump app: fail fast (with a clear message, before touching any files) when `## [Unreleased]` is missing, instead of silently succeeding and committing only build.zig.zon. Rolled entries now use the em-dash separator to match existing changelog headers. - html/typst renderers: document that the mermaid callback must return memory allocated with the passed allocator (it is freed after emit) on the raw fn-pointer signatures, matching the note already on `MermaidRendererFn`. Docs + release-tooling only; no library behavior change. zig build test passes.
…shed sha Copilot review on #70: for annotated tags the pushed local_sha is the tag object rather than a commit. Resolve `prev` and the CHANGELOG diff through the tag ref (`$tag`) instead so the guard peels to the tagged commit for both lightweight and annotated tags. Verified against both tag types; shellcheck clean.
#113) * build: add pozeiden dependency and bump zigmark to 0.7.2 pre-release pin zigmark is pinned to the fix/typst-mermaid-image-emit commit (sc2in/zigmark#70) until v0.7.2 is tagged; swap to ?ref=v0.7.2 before merge. pozeiden is pinned to the 23-update-to-zig-0160 branch head (zig 0.16 support is not on its main yet); the lock resolves the same content via the v0.1.3 tag tarball. * feat(utils): add underscoresToBlocks and executableInPath helpers underscoresToBlocks converts redaction underscores to solid █ bars for the typst engine (CommonMark parses 3+ underscores as thematic breaks, which render as thin gray rules instead of redaction marks). executableInPath moves from pandoc.zig so it survives the pandoc teardown. * feat!: replace pandoc/xelatex PDF engine with typst New src/typst.zig renders policies in-process: markdown → utils preprocessing (unchanged) → zigmark AST → Typst markup with an eisvogel-matching preamble (title page + colored rule + 6cm logo, 3-column header/footer, zebra tables, TOC depth 3, 5-column version history incl. Revised By) → 'typst compile'. Mermaid diagrams render via pozeiden to inline SVG — no mermaid-filter/node/Chromium, and they now work inside the nix sandbox and on macOS. Parity preserved: CLI flags, output filename patterns (issue #97), draft.png watermark resolution (site root → theme fallback), redaction bars (underscores → █ via utils.underscoresToBlocks). pozeiden is not thread-safe (module-global arena and lazily initialised grammar caches) while policies compile concurrently, so the mermaid wrapper serialises calls behind a spinlock (zigmark's hook signature has no std.Io for an Io.Mutex). Removed: src/pandoc.zig, templates/eisvogel.latex (embed machinery in build.zig), .puppeteer.json, Config.data_dir. The 'pdf' build step now runs the typst engine standalone; e2e no longer copies the LaTeX template into starter/. Tests: pdf rendering gates on typst instead of xelatex and now exercises the mermaid fixture; new typst-source tests for inline SVG, redaction bars, and the draft background; draft.png resolution tests target Typst.resolveDraftPng. * refactor: drop mermaid spinlock; pozeiden is thread-safe upstream pozeiden now guarantees concurrent render safety itself (threadlocal theme state, locked grammar-cache init — sc2in/pozeiden#35), so the consumer-side serialisation is unnecessary. Repin to the fix commit; swap to a tagged release once #35 merges and a tag is cut. * build(nix)+ci+docs: drop the LaTeX toolchain from infra flake.nix: remove eisvogel-tex input, pandoc, mermaid-filter, imagemagick (unused), the mmdc/Chromium sandbox wrapper, and all fontconfig plumbing. typst joins runtimeDeps; fonts (Source Sans 3, Source Code Pro) reach typst via TYPST_FONT_PATHS in the devshells, apps, and the test check (with TYPST_IGNORE_SYSTEM_FONTS for determinism). zig build no longer depends on templates/, so Zola template edits stop busting the build cache. action.yml: drop the eisvogel.latex copy step; pin the build shell to the action's own checkout (path: flake ref) so consumers on released binaries can't be broken by flake changes on main. CI: macOS upgrades from 'zig build check' to the full test suite (the mermaid-filter/Chromium blocker is gone); remove the apparmor userns sysctls that existed only for Chromium. Docs: README, building-pdfs, writing-policies, installation (ADO sample → v1.4.0, closure-size note), live-editing; CHANGELOG [Unreleased] entry. * fix: redact_web no longer drives PDF redaction redact_web is documented as web-only (the Zola templates read it for the site's redaction bars), but Config.load seeded the PDF pipeline's redact default from it. An org hiding content (e.g. phone numbers) on the public website could not keep it in the PDFs without knowing to pass --no-redact. PDF redaction is now controlled solely by --redact/--no-redact and the action's redact_mode input, which always passes an explicit flag, so action consumers are unaffected. Closes #115 * fix(ci): restore userns sysctl - bubblewrap needs it, not just Chromium zig2nix's zig-hook runs the compiler inside bwrap, which requires unprivileged user namespaces on Ubuntu 24.04 runners. The step's old comment attributed it solely to Chromium/mermaid-filter, but removing it broke any from-source build of zig2nix tooling with 'bwrap: setting up uid map: Permission denied'. Restored with an accurate comment. * fix(nix): TYPST_IGNORE_SYSTEM_FONTS accepts only true/false typst's clap rejects '1' ('invalid value for --ignore-system-fonts'), which failed the pdf-rendering test inside checks.test. Verified with an unpiped 'nix flake check' this time (the earlier pass was tail masking the exit code). * fix(action): build the binary from the flake for in-repo runs The PR preview workflow (uses: ./) installed the latest RELEASE binary — v1.3.x still speaks pandoc, which no longer exists in this branch's ci shell, so previews failed with 'pandoc not found'. When action_ref is empty (local action), build policypress from the action's own flake so previews exercise the code under review; remote consumers keep the release download. The theme checkout likewise falls back to the workflow's commit instead of the default branch. * build: pin zigmark v0.7.3 and pozeiden v0.2.0 release tags Replaces the interim PR-commit pins now that both upstream releases are cut. zigmark v0.7.3 carries the typst mermaid emit fix and the body-only renderTypstWithMermaid API; pozeiden v0.2.0 carries zig 0.16 support and thread-safe rendering. * build: bump zigmark pin to v0.7.4 Immutable releases on the zigmark repo prevented re-pointing v0.7.3, so the release was re-cut as v0.7.4 (same fixes, plus the pozeiden v0.2.0 lazy-dep bump from zigmark#71). Pinned to the exact tag commit since zig fetch resolved the bare ?ref one commit behind the tag. * fix: address Copilot review findings - Never delete a pre-existing .typ path on name collision; retry with a fresh random name instead (error.TypstWorkFileConflict after 16 tries). - Build the output PDF path with std.fs.path.join instead of string concatenation. - underscoresToBlocks: the closing frontmatter delimiter only counts as a whole line (line start + newline/EOF). Previously a '---' inside a frontmatter value ended the protected region early and let redaction corrupt underscores in later keys like last_reviewed. Adds a regression test. --------- Co-authored-by: TsunamiNoAi <spam@falseblue.com>
Problem
The Typst renderer emits
#image.decode(bytes.fromBase64("…"))for mermaid blocks.bytes.fromBase64does not exist in Typst (andimage.decodeis deprecated), so every document containing a rendered mermaid diagram failstypst compilewith a hard error:The mermaid tests only assert the emitted string — nothing ever ran the output through a real
typst, which is how this slipped through.Fix
image(bytes("<svg…>"), format: "svg"), using the existingwriteStringLiteralescaping ("and\; raw newlines are legal in Typst strings). Verified against typst 0.14.2, including multi-line pozeiden output.width: 100%: alayout/measurewrapper renders at intrinsic size and scales down only when wider than the line width. Previously a 4-node flowchart blew up to full text width and sprawled across 3 pages; it now sits on one page at natural size (matches LaTeX\includegraphicsmax-width conventions).renderTypstWithMermaid(root) /renderToWriterWithMermaid(typst module) — body-only rendering with the mermaid hook, for callers that supply their own preamble. The internals already supported this (seeokMermaidtest helper); it just wasn't exposed. Needed by PolicyPress (try typst as a latex replacement PolicyPress#58), which owns its own eisvogel-style preamble.e590c0a; note its zon still said 0.7.0 — this bump fixes that too).Testing
zig build test— all pass, incl. new tests: string-literal escaping with quotes/backslashes/newlines, and the new public API.zig build spec652/652,zig build gfm24/24.zigmark -f typst(pozeiden) →typst compile(0.14.2) → PDF renders the flowchart as a vector diagram at natural size.Downstream: PolicyPress issue sc2in/PolicyPress#58 (LaTeX→Typst migration) depends on this; please tag
v0.7.2after merge so it can pin the release.