TikZ (precise math figures) — shipped
Parent: rendering-engines
Fills the gap mermaid can't: precise mathematical figures — geometry constructions, commutative diagrams, coordinate/labeled boxes, function curves. A ```tikz fence renders to SVG. Live in the reader.
Engine symmetry is why TikZ won. node-tikzjax is the same engine as the Obsidian TikZJax plugin, so the owner draws TikZ in Obsidian and StandMeet renders the identical figure — the same argument as katex / mermaid. (Weighed and rejected: quiver, Mafs, function-plot, JSXGraph, Penrose — the need is static precise figures.)
It renders on the SERVER, not in the browser
The original sketch was browser-side WASM with the payload lazy-loaded or gated behind a setting. That is not what shipped, and the reversal is the design. The TeX engine runs server-side (/render-tikz, a Node-runtime route, usecase in lib/render/tikz); the client's TikZBlock POSTs the source and receives SVG. The heavy WASM never reaches a visitor.
The real cost turned out to be elsewhere: serverExternalPackages + outputFileTracingIncludes, because the TeX runtime assets (core.dump.gz, tex.wasm.gz, tex_files.tar.gz) are read from the filesystem, not imported, so Next's tracer does not carry them into the standalone build — and without them the route ENOENTs and hangs.
The engine is not reentrant — this is load-bearing
node-tikzjax drives a single in-process WASM TeX engine. Two renders that overlap trample each other and both throw TeX engine render failed. A reader page fires one POST per diagram at once, so a note with four figures lost a random subset of them — the reader got a slab of raw LaTeX where a picture should be, and which ones failed changed between loads.
Measured on prod: the same source returned 200 alone and 422 twice over when sent as two concurrent requests, answering in 0.6s — nowhere near the 25s timeout, so it was never a slowness problem.
Renders are therefore queued: one enters the engine at a time. The timeout starts when a render actually begins, not when it joins the queue, so a diagram waiting its turn is not judged slow. Per-process queue; multiple replicas each serialize their own, which is enough because the singleton is per-process anyway.
Fonts must be self-hosted
The SVG's text is <text> + font-family: cmr10 / cmsy10 / …, and the characters are TeX font slots, not Unicode: $\to$ sits at 0x21 in cmsy10 — that is !. Without the fonts the browser falls back, the arrow becomes an exclamation mark, and metrics shift enough to split words (stochastic → sto chastic).
The package ships embedFontCss: false with a default fontCssUrl pointing at jsDelivr. Both are wrong for a self-hosted product: an offline instance cannot fetch them, and every figure would make one third-party request on the owner's readers' behalf. The fonts are served by the instance (scripts/copy-tikz-fonts.mjs moves them into public/ at build).
Failure is not the reader's problem
A failed render used to print the LaTeX source into the page. mermaid had already settled this question — the owner sees the diagnostic, the visitor sees nothing, and the failure is logged either way — and TikZ simply never got that treatment. Loading now shows a placeholder rather than source, which matters more once renders are queued: on a multi-figure page the last one waits several seconds, and those seconds must not be a wall of LaTeX.