diagram-forge¶
The notebook's house diagram engine: write a small JSON spec, get an animated, self-contained architecture diagram in one locked style.
The house style¶
- Neo-brutal on graph paper — white cards, hard offset shadows, 2px black borders, Line icons, bold monospace labels on a faint engineering grid.
- One motion language. A pulse is a call in flight. The line says how it is called. Nothing else moves.
| Encoding | Meaning |
|---|---|
| Teal pulse | a call (request or data read/write) travelling a path |
| Solid line | synchronous call |
| Dashed line | asynchronous / stream (no pulse travels it) |
| Thick line | the hot (critical) path |
| Faint dashed | structural link, not on the request path |
Every diagram ships the same Key legend, and labels sit on a paper backing so wires never cross the text.
Usage¶
# build a self-contained animated HTML page from a spec
node build.mjs specs/cassandra-write.json # -> dist/cassandra-write.html
# export a static SVG for GitHub / MkDocs (JS does not run there)
node export-svg.mjs dist/cassandra-write.html dist/cassandra-write.svg --t=2.2
- In the MkDocs site / a blog: embed the HTML (animated) or the SVG (static).
- On GitHub / in Markdown: use the SVG.
- In a page directly: load
diagram-engine.jsand callDiagram.render(hostElement, spec).
The SVG export needs a browser once; it installs Playwright's Chromium into
~/.cache/diagram-forge (override with DIAGRAM_FORGE_CACHE). No browser? Open the
built HTML with ?static=1&t=2.2 and copy the <svg> out by hand.
The spec¶
{
"badge": "Deep dive · Apache Cassandra", // small eyebrow tag
"title": "A write, and where it lives",
"subtitle": "One or two sentences of context.",
"width": 1160, "height": 940, // viewBox
// tiers are horizontal bands, top to bottom; their y is the band centre
"tiers": [
{ "label": "Client", "y": 80 },
{ "label": "Coordinator", "y": 228 }
],
// nodes: x is explicit; y comes from the tier (override with "y" for a second
// row inside a tier). icon is a name from Diagram.ICONS. "wide": true for a bar.
"nodes": {
"client": { "x": 580, "tier": 0, "label": "Client", "sub": "app", "icon": "phone" },
"coord": { "x": 580, "tier": 1, "label": "Coordinator", "sub": "hashing", "icon": "hub" }
},
// flows draw SOLID wires AND send a pulse along the whole sequence.
// "hot": true makes the wire thick (the hot path).
"flows": [
{ "seq": ["client", "coord"] },
{ "seq": ["coord", "rB"], "hot": true }
],
// async: dashed wires, no pulse (streams, events, background flushes)
"async": [["mem", "ss3"]],
// weak: faint structural links (replication, indexes), no pulse
"weak": [["bloom", "ss3"]]
}
Icons¶
phone monitor gate branch hub box server surge hex route bell map card stream
cyl stack bars journal grid bloom merge cloud lock
Add more in diagram-engine.js under ICONS — each is line art drawn with
currentColor, centred on the origin, fitting a ~44px box.
Layout tips¶
- Put a tier's nodes at distinct
x. For two stacked rows inside one tier, give the lower node an explicity. - Keep vertical spines on a shared
xso wires run straight; the router is orthogonal (Manhattan) and trims to node edges automatically. - The hot path should be the single most important route — one thick line reads; three do not.
Genericity and limits (read this)¶
Be honest about what this is: a styled renderer with a fixed visual language, not an auto-layout tool. The style is fully generic — any spec renders identically. The layout is manual:
- You place every
x. There is no force-directed or hierarchical auto-layout.ycomes from the tier;xis yours. Two nodes at the samexin the same tier overlap. - The router is orthogonal but node-blind. Each wire routes independently (drop to a staggered mid-channel, across, down). It does not know about other nodes, so a wire can pass through a node you didn't intend — typically a long edge that skips a node in a shared row or column, or anything routed across a wide central node.
Three guardrails make this safe to work with:
- Spec validation. An unknown node id or bad tier renders a readable error box, not a blank page.
- Crossing warnings. The engine logs
console.warn("wire A->B crosses node X")for every wire that passes through an unrelated node's box. Check the console after every build and nudge anx(or re-tier) until it is silent. All three shipped specs are warning-free. - Channel stagger. Parallel fan-out edges are offset slightly so they separate instead of stacking into one ambiguous line.
What this means in practice: simple-to-moderate diagrams (a request path, a storage
engine, a service map of ~12–18 nodes) come out clean with a little x tuning. Very dense
graphs, or ones with a big hub that everything must route past, still need deliberate
placement — the warnings tell you where. Node-aware auto-routing is a deliberate non-goal
for now; the trade is a tiny, dependency-free engine you fully control.
Files¶
| File | Purpose |
|---|---|
diagram-engine.js |
the renderer + icon registry + house-style tokens |
build.mjs |
spec -> self-contained animated HTML |
export-svg.mjs |
built HTML -> static SVG |
template.html |
the page skeleton build.mjs fills |
specs/ |
example specs (Uber dispatch, Cassandra write path) |