Emanote bundles lua-filters/diagram.lua, a render-time Pandoc Lua filter that turns fenced code blocks into inline SVG. The filter is vendored from pandoc-ext/diagram and post-processed so SVG diagrams land directly in the page — no --extract-media pass, no JavaScript renderer, no network at view time.
Enabling the filter
Diagrams render only on notes that opt in. Add the bundled filter to a note’s render-time HTML phase in frontmatter:
The bundled filter recognises seven engine classes from upstream pandoc-ext/diagram; Emanote’s Nix closure pins the binaries for two of them, and leaves the rest as bring-your-own:
A note that fences a class without a corresponding binary renders a Pandoc Lua filter error banner pointing at the failed pandoc.pipe call, so the missing-binary case is loud, not silent.
d2 demo
D2’s declarative syntax is the shortest path from prose to picture. The thought-meandering exercise Richard describes in “Silly or Sensible” — “you will go off into a side branch … and that will branch off into another side branch … and into another and another … and so on” — drops out as a four-step meander followed by the snap-back:
cetz demo
CeTZ shines on figures whose meaning is geometric, not flow-shaped. The temporal contrast Richard draws in “This moment has no duration” — time had a periodicity versus the cutting edge where this moment has no duration — is itself a geometric distinction (discrete tick marks vs. an infinitesimally thin line) that cetz can render directly:
canvas and draw are pre-imported by Emanote’s wrapper, so the author writes the figure directly without typing or remembering the cetz version:
Caching
The upstream filter supports a content-addressed disk cache that skips re-running an engine for unchanged source. Enable it once per document via metadata:
diagram:cache:true
Cache files land under $XDG_CACHE_HOME/pandoc-diagram-filter/ (typically ~/.cache/pandoc-diagram-filter/), keyed by sha1(fence-body). The cache is shared across notebooks and naturally git-clean. Set diagram.cache-dir: <path> to override the location.
Note
The cache key is the fence body text only — it does not include the engine binary version or the fence’s code-block attributes. Bumping d2 / typst (or changing a {.d2 layout=elk} attribute) does not invalidate previously-cached renders. Bust the cache manually with rm -rf $XDG_CACHE_HOME/pandoc-diagram-filter/ when an engine upgrade should reflow output.
Without caching, every render of a page re-invokes the engine. For static sites this happens once per build; for the live server it happens on each save of a note that contains diagrams.
Limitations
Filter declaration must live on the note itself — site-wide cascade from index.yaml is tracked in #263.
Other engines (mermaid, dot, plantuml, tikz, asymptote) work if you install the matching binary, but Emanote’s Nix closure does not pin them. Set the engine’s _BIN environment variable (MERMAID_BIN, DOT_BIN, …) to override the executable path explicitly.
Diagrams render only when FORMAT == "html", so they have no effect on parse-time concerns like backlinks, tags, search index, or the note model. The fenced source remains in the document for export targets that don’t run the filter.