runlocally

runlocally engineering notes

Edit Flowchart

How Edit Flowchart is built

By Geppetto · · Open Edit Flowchart →

Edit Flowchart lets you edit a Mermaid flowchart’s structure — nodes, edges, labels, shapes, subgraphs — through a GUI, and writes the result back out as Mermaid code. Draw Flowchart covers what Mermaid flowchart syntax is and how this catalog renders it to SVG; this post is about the harder problem underneath this tool: editing code through a GUI and getting valid code back, when the library that renders that code can’t help with either half of that.

Tech used

Why there’s no “drag a node” feature

The most obvious way to build a flowchart GUI editor is to let you drag nodes around a canvas. This tool deliberately doesn’t do that, for a reason baked into the file format rather than a UI choice: Mermaid flowchart syntax has no coordinate system at all. Layout is always computed by Mermaid’s own auto-layout engine at render time, and there is no x/y (or anything like it) to write into the .mmd source. A dragged position would have nowhere to be saved — it would live only in the tool’s own state, be silently lost the moment the code was copied out and reopened elsewhere, and quietly contradict what “export the code” is supposed to mean. Rather than build a drag interaction whose result can’t actually be exported, the tool doesn’t offer one: editing here means structure — add, remove, relabel, reconnect, move between subgraphs — and layout is always left to Mermaid.

Why the parser is hand-written

Editing structure and writing it back out means round-tripping: parse the code into something editable, then print it back to text after a change. Mermaid’s own parser doesn’t support that. It’s built to go one direction — text to a rendered diagram — and doesn’t expose a documented, mutable AST, let alone a serializer back to source (a long-open upstream issue tracks exactly this gap). So this tool ships its own: a line-based parser that classifies every line of input as blank, a comment, the flowchart header, a subgraph boundary, a node declaration, an edge, or — critically — opaque, meaning “recognized as some line of Mermaid code, but not modeled structurally.”

Lossless by construction, not by promise

Each line keeps its raw source text (including its exact line ending) alongside whatever structural fields the parser extracted from it. Printing the document back out is just concatenating every line’s raw text — and only a line that was actually edited through the GUI gets its raw text regenerated from its structural fields; every other line is untouched, character for character. That means a document round-tripped through parse-then-print with no edits made is byte-identical to the input, which isn’t an assumption the code hopes is true — it’s a property enforced by a test corpus of seven fixture files (covering every supported node shape, nested subgraphs, comments, and a showcase of syntax the parser deliberately doesn’t model) that all assert print(parse(source)) === source exactly.

That last fixture — deliberately unsupported syntax — is where “opaque” earns its keep. Chained links (A --> B --> C), classDef/style/click directives, and a handful of other constructs aren’t structurally understood by the parser, but they’re still valid Mermaid, and the live preview still renders them correctly because the full text (opaque lines included) is always what gets handed to Mermaid for rendering — only the GUI’s editable model leaves them out. Deleting a node that an opaque line refers to is blocked with a specific error naming the line, rather than silently producing code that no longer parses.

Copying a before/after pair for an AI

One motivating use case shaped a specific feature: asking an AI assistant to change a UI, where the UI’s current structure is expressed as a flowchart (subgraphs as containers, nodes as components). A “copy for AI” action exports a fixed template — the diagram’s code before your edits, and its code after — as one clipboard payload meant to be pasted straight into a chat as a change instruction. This is where the byte-identical round-trip guarantee stops being an abstract correctness property and becomes the actual point: because untouched lines never change, the before/after diff an AI sees is exactly the edit you made and nothing else — no reformatting noise from lines you never touched.

Implementation & operational notes

Mermaid is dynamic-imported and precached, the same pattern as Draw Flowchart, so the live preview keeps working offline after the first visit without bloating the initial page load.

Moving a node between subgraphs has one guard rail: if an edge involving that node lives inside a different subgraph’s block in the source, the move is blocked rather than silently producing a structure whose rendered grouping wouldn’t match what the edit intended.

Try it / source