Mermaid Editor
Turn Mermaid text into SVG, PNG, or PDF entirely on your own machine: a CLI, a live web editor, and a local-LLM assist.
Problem
Mermaid turns text into diagrams, but the easy ways to render it all reach for the cloud: a web playground, a hosted API, an editor that quietly phones home. For a diagram that describes your own architecture or an unreleased feature, that’s exactly what you don’t want: your system’s shape, pasted into someone else’s server. And the command-line renderers that do stay local tend to stop at one file at a time, with no preview and no help writing the syntax.
Solution
Mermaid Editor keeps the whole loop on your machine. A CLI converts .mmd files to SVG, PNG, or PDF (one file or a whole folder), and a local web editor gives you Monaco with live preview, pan-and-zoom, ten starter templates, and one-click export. Both share a single renderer: a bundled Mermaid running inside headless Chromium, so nothing is fetched at render time. When Ollama is installed, an AI pane generates and edits diagrams from a local model: no account, no API key, no data leaving the laptop.
Journey
I built it as one tool with two front doors (a Commander/Clack CLI and an Express-served single-page editor) over a shared rendering module, and coded it with Claude Code. The guiding rule was zero-cloud: every capability had to work with the network unplugged. That’s why Mermaid is injected into Puppeteer from node_modules instead of a CDN, why the LLM runs through a local Ollama, and why the system prompt lives in an editable rules/ file rather than the code, so the one thing you’d most want to tune is a text file, not a rebuild.
Challenges
- /01
Mermaid renders clean SVG at any size, but a PNG or PDF needs fixed pixel dimensions, and a diagram’s real size isn’t known until it’s drawn. Guess too small and it clips; guess too big and it floats in a sea of background.
Fix → The renderer draws to SVG first, measures the diagram’s actual bounding box, then resizes the headless-Chromium viewport to those dimensions plus a small margin before it screenshots or prints. The raster gets its theme background; the SVG stays transparent.
- /02
A local model streams its reply as a run of JSON lines, and at speed a single line arrives split across two network reads: parse it eagerly and you choke on half a token.
Fix → A small buffering decoder holds the incoming text, splits only on real newlines, parses the complete lines, and keeps the trailing fragment for the next read, flushing whatever’s left at the end. The browser’s server-sent-events reader uses the same trick.
- /03
Close the editor mid-generation and the model keeps talking to a browser that’s gone: wasted cycles on a stream nobody’s reading. But the obvious way to detect the disconnect fires false alarms in some setups and kills healthy streams.
Fix → The server ties an AbortController to the response stream closing, guarded by a “finished” flag so a normal completion never reads as an abort. Watching the response rather than the request is what makes the signal trustworthy.