flowchart-studio is a mermaid-flowchart-compatible diagram editor with no dependency on
mermaid, dagre, or elkjs. It accepts mermaid flowchart
syntax 1:1, but parses, lays
out, and renders every diagram with its own hand-written engine end to end.
On top of that engine sits a mermaid.live-style live editor.
This project uses pnpm (never npm — no package-lock.json in the repo).
pnpm install
pnpm dev # start the editor at http://localhost:3000
pnpm test # run the vitest suite
pnpm build # production build (next build)Other useful scripts: pnpm exec tsc --noEmit (typecheck), pnpm start (serve
a production build).
A diagram flows through three independent, framework-free stages —
parse → layout → render — glued together by the public
renderMermaid(source) entry point in src/lib/flowchart/index.ts. Parsing
turns source text into a FlowchartModel (nodes, edges, subgraphs, classes,
styles, click bindings); layout runs a custom Sugiyama-style layered algorithm
over that model to produce absolute coordinates and routed edge paths; render
turns the positioned graph into an SVG string (or DOM, for the live preview).
Each stage is pure and independently testable, with no dependency on the
others' internals beyond the shared FlowchartModel / PositionedGraph
types. The Next.js app (src/app/, src/components/) is a thin UI layer over
this library — it never touches parsing/layout/rendering logic directly.
src/lib/flowchart/
types.ts shared model types (FlowNode, FlowEdge, Subgraph, ...)
index.ts public API: renderMermaid(source) -> { ok, svg } | { ok, error }
parse/ text -> FlowchartModel
parser.ts top-level recursive-descent parser (statements, config, directives)
node-parser.ts node id/label/shape/@{...} metadata parsing
edge-parser.ts edge stroke/head/length/label parsing
shapes.ts bracket-shape table + @{shape:...} alias table
preprocess.ts frontmatter, comments, entity-code decoding
fixtures.ts flowchart-docs example corpus, used as parser+render smoke tests
layout/ FlowchartModel -> PositionedGraph
rank.ts cycle removal + longest-path layer assignment
order.ts in-layer ordering (barycenter heuristic)
position.ts coordinate assignment, clustering, edge routing
render/ PositionedGraph -> SVG
shapes-svg.ts per-shape SVG element generators
measure.ts text measurement (canvas in-browser, approximate in tests/SSR)
curves.ts edge path interpolation (basis/linear/step*/monotone*/...)
svg.ts top-level SVG document assembly (styles, markers, click/animation)
src/app/ Next.js App Router pages
src/components/ editor UI (CodeMirror pane, SVG preview, toolbar, error panel)
src/lib/editor/ editor-only concerns: sample diagrams, share-link encode/decode, SVG/PNG export
| Feature group | Status | Notes |
|---|---|---|
Directions (TB/TD/BT/LR/RL) |
Full | All four rendered directions, implemented as coordinate transforms over a canonical TB layout. |
Bracket node shapes ([ ], ( ), ([ ]), [[ ]], [( )], (( )), >], { }, {{ }}, [/ /], [\ \], [/ \], [\ /], ((( )))) |
Full | All 15 bracket forms. |
Expanded @{ shape: ... } named shapes |
Full | ~50 canonical names + all documented aliases (rect/proc/process, diamond/decision, etc.). |
Icon shape (@{ shape: icon, ... } and the @{ icon: "..." } shorthand) |
Partial | Parses and renders (with or without an explicit shape: key); the icon glyph itself is a text placeholder, not a real FontAwesome/iconify glyph (no icon-font/kit dependency is bundled). |
Image shape (@{ img: "...", ... }) |
Full | Renders an <image> element with the given pos/w/h. |
| Links: solid/dotted/thick/invisible, arrow/circle/cross/none heads (incl. bidirectional), length modifiers, labels (inline and piped) | Full | |
Link chaining (A --> B --> C) and fan-out/fan-in (A & B --> C & D) |
Full | |
Edge IDs and animation (e1@--> B, e1@{ animate: true }, animation: fast|slow) |
Full | |
Subgraphs, incl. nested subgraphs, per-subgraph direction, and titled subgraphs |
Full | Laid out recursively as clusters, then treated as super-nodes in the parent layout. |
classDef (incl. default, comma-separated multi-def), class, ::: shorthand, style, linkStyle (incl. multi-index) |
Full | |
Click interactions (click id callback/call/href ..., tooltips, _self/_blank/_parent/_top) |
Full | |
Comments (%%), entity codes (#35;, #quot;, ...) |
Full | |
Markdown-string labels (["`**bold** text`"]) |
Partial | Bold/italic runs render, including _/__ markers and nested combinations (**_bold italic_**); multi-line markdown labels and markdownAutoWrap auto-wrapping are unsupported. |
Basic FontAwesome tokens (fa:/fab:/fas:/far:/fal:/fad:/fak:) inside plain labels |
Partial | Parsed and preserved in the label text; rendered as plain text rather than an actual icon glyph. |
Frontmatter ---config--- block |
Full (parse) | All keys parse and are attached to the model; only flowchart.curve currently affects layout/rendering. |
defaultRenderer: elk |
Deferred | Parses and is stored on the model's config but is always ignored — layout is always flowchart-studio's own Sugiyama engine (see Known limitations). |
| Diagram types other than flowchart (sequence, class, ER, ...) | Deferred | Out of scope for v1 — architecture keeps parser/layout/render behind one interface so more types could be added later. |
- Live preview: source is parsed/laid out/rendered on every edit (~300ms debounce); on a parse error the last valid diagram stays on screen and an error panel shows the message plus line/column.
- CodeMirror 6 editor pane with mermaid-flowchart syntax highlighting.
- Pan (drag) and zoom (wheel) on the preview, plus a "fit to view" button.
- Sample diagram picker covering shapes, subgraphs, styling, edge labels/arrow types, and edge animation.
- Export the current diagram as SVG or PNG (rasterized client-side via
<canvas>). - Share links: the diagram source is deflate-compressed (
pako) and base64url-encoded into a#pako:...URL hash, mermaid.live-style — no server-side storage. - Light/dark theme toggle; the diagram theme and the app chrome derive from
one token system (
src/lib/flowchart/theme.ts).
The preview is a live editor: every gesture writes a targeted edit into the Mermaid source (never a regeneration), so comments and formatting survive and Ctrl+Z undoes gestures like any other edit. Before committing, each edit is re-parsed — an edit that would break the document is discarded and the error panel explains why.
| Gesture | Effect on the code |
|---|---|
| Drag a node's body | moves the node — writes a position pin A@{ x: ..., y: ... } |
| Drag from a connection port (dots on hover) | appends A --> B (inside the shared subgraph when both nodes live in one) |
| Double-click empty canvas | inserts n1["New node"] pinned at that spot and opens inline label editing |
| Double-click a node | inline label edit → rewrites just the label token |
| Click a node/edge, then Delete | removes the statement; chains and A & B --> C fan-outs are rebuilt, neighbors survive |
| Right-click a node | context menu: change shape (@{ shape: ... }) or assign a class |
| Drag empty canvas / wheel | pan / zoom |
The canvas toolbar (bottom left) offers add-node, delete-selection, undo/redo, and zoom controls.
Position pins are a flowchart-studio extension of the @{ ... } metadata
syntax: pinned nodes override the automatic layout (their edges reroute as
straight border-to-border segments); unpinned nodes keep the layered
auto-layout. Delete the @{ x, y } line to return a node to automatic
placement.
| Syntax | Mermaid-compatible? |
|---|---|
| shapes, labels, subgraphs, edge labels/strokes | yes — standard Mermaid |
e1@{ animate: true } |
yes — Mermaid v11.3+ |
A@{ x: 10, y: 20 } position pins |
studio-only — other renderers ignore them |
e1@{ anchorStart: ..., anchorEnd: ... } |
studio-only |
A pinned design pasted into a Notion/GitHub mermaid block will be re-laid-out
by that renderer and look different. For a 1:1 look in documents, embed the
exported SVG/PNG (or a share link); the validate_diagram MCP tool flags
studio-only syntax via the allowStudioExtensions rule so docs-bound
diagrams stay portable.
mcp/ contains a stdio MCP server, flowchart-rules, that lets any MCP
client (Claude Code, documentation pipelines, ...) generate diagrams that
conform to this repo's rules:
| Tool | Behavior |
|---|---|
get_rules |
Returns rules/diagram-rules.md + rules/constraints.json — load before generating |
validate_diagram |
Parses with the studio parser; returns parse errors (line/column) and rule violations |
render_svg |
Self-contained SVG string for embedding in docs |
render_png |
PNG image (rasterized via @resvg/resvg-js) |
create_share_link |
#pako: URL that opens the diagram in the live editor |
Setup: pnpm --dir mcp install once, then the repo-root .mcp.json
registers the server automatically for Claude Code sessions in this repo.
From another project, register it with the absolute path:
{
"mcpServers": {
"flowchart-rules": {
"command": "pnpm",
"args": ["--dir", "/path/to/flowchart-studio/mcp", "exec", "tsx", "src/server.ts"]
}
}
}Smoke test: pnpm --dir mcp smoke. The rules files are read from disk on
every call, so editing rules/ needs no restart. Edit rules via the
diagram-rules skill (.claude/skills/diagram-rules/).
- Only flowcharts. Other mermaid diagram types (sequence, class, state, ER, ...) are not implemented; this is a deliberate v1 non-goal.
- No ELK renderer.
defaultRenderer: elkin frontmatter is accepted but has no effect — layout is always flowchart-studio's own Sugiyama-style layered algorithm. - FontAwesome icons are placeholders. Both the icon shape
(
@{ shape: icon, icon: "..." }) and inlinefa:-style tokens parse and render, but as text placeholders rather than real icon glyphs — no FontAwesome/iconify asset is bundled. - No multi-line markdown labels. Single-line markdown-string labels
(bold/italic) render; a label spanning multiple lines inside
"`...`"is not supported, nor ismarkdownAutoWrap. - No server-side persistence. Sharing a diagram works only via the URL
hash (
#pako:...); there is no save/load against a backend. linkStyle defaultis explicitly rejected by the parser (only numeric indices are supported).
pnpm test runs the vitest suite (parser, layout invariants, renderer
snapshots, and end-to-end renderMermaid checks). src/lib/flowchart/parse/fixtures.ts
holds a corpus of examples lifted directly from the mermaid flowchart
docs; every fixture is asserted
to both parse successfully and render to well-formed SVG, so the corpus
doubles as a docs-parity regression test.