Specifies the diagram payload for documents that carry a single, text-defined diagram (flowchart, sequence, state, ER, gantt, gitGraph, …) rendered to SVG. The renderer is Mermaid (mermaid, MIT) — a text-DSL → SVG library. The diagram source is an opaque string in Mermaid syntax; the renderer parses it at display time, the codec does not.
See also: doc-kind-chart | doc-kind-graph | doc-kind-mindmap | web-ui
1. Purpose
Use cases: Flowcharts in strategy documents, sequence diagrams for API sketches, state machines, ER models, Gantt timelines, Git branches, C4 architecture diagrams. Primarily intended as LLM output — a Worker describes a process or architecture and stores it as a visual artifact in the Project document pool. Secondarily, manually maintained by the user in the Raw tab.
Distinctions:
chart: numerical data series with axes (Line/Bar/Pie/Heatmap/…). For visualizing data, not processes.
graph: nodes + edges as a structured JSON model with drag-editing. diagram is text-DSL → SVG without edit interactivity.
mindmap: hierarchical outline with radial rendering. While Mermaid itself has a mindmap diagram type, it is just one of many possible source variants here — semantically clean Mindmap documents remain kind: mindmap.
slides: slide deck. A single Mermaid diagram can be referenced or embedded in a slide (see slides §6.3), but it is not a slide document.
Design Principle — Text as Truth. The source is the origin. No structured representation of nodes/edges in Vancetope — Mermaid parses it itself at render time. Rationale:
LLM Fluency: Mermaid syntax is in practically every LLM training set (GitHub Markdown, documentation sites, Stack Overflow). Models write it compactly and usually syntactically correct. A custom JSON schema for it would first need to be explained in the prompt.
Token Efficiency: 5-10 lines of Mermaid vs. dozens of lines of JSON with x/y coordinates. Mermaid (dagre internally) handles layout itself.
Consistent with existing pattern: markmap = Markdown→Mindmap, Marpit = Markdown→Slides, Mermaid = DSL-Text→Diagram. Same persistence form (source: string).
Library-change resilient: if we later want to support d2 or PlantUML in addition, it’s a new dialect value (see §2.1), not a new kind and no schema migration path.
Design Principle — Mermaid as Renderer, not as App. We use mermaid as a pure library (mermaid.render(id, src) → { svg }). No pan/zoom plugin, no live editor, no custom toolbar in the render output. SVG is returned, we mount it into a container. Rationale:
Library character (function, no global state, no UI shell) fits Vancetope editor embedding, analogous to marked for kind: doc and marpit for kind: slides.
Industry standard — GitHub, GitLab, Obsidian, Notion render Mermaid natively in Markdown; roundtrip to other tools is trivial.
Render errors are returned as exceptions — UI can display them as a banner, the LLM can receive them as a tool result (“source had a syntax error on line N”).
PlantUML / d2 / draw.io deliberately excluded: PlantUML requires a JVM/server, d2 is less LLM-familiar, draw.io is XML-based and not LLM-friendly. Mermaid is the only library that is simultaneously (a) pure client-side JS, (b) declarative-textual, and (c) LLM-native.
Format mapping md/json/yaml — canonical is Markdown with a single ```mermaid code fence.
Web UI activation with two tabs (Diagram / Raw).
Render path with lazy loading of the Mermaid library.
Error display for broken syntax, plus error surfacing for LLM tool calls.
What it does not define:
Mermaid sub-type validation in vance-api. The source is opaque; we do not open it. Mermaid syntax updates automatically land with library upgrades without spec changes.
In-place WYSIWYG editing. Editing in v1 is exclusively via the Raw tab on the text. Mermaid has no official editor mode; third-party tools like Mermaid Live Editor are separate apps.
Live reload of the Diagram tab when typing in the Raw tab. v1: User switches tabs to see the result. Side-by-side live preview is v2 (see §6.2).
Multiple diagrams in one document. One document = one diagram. Multiple diagrams = multiple documents. Collective display lives in a kind: doc Markdown with multiple ```mermaid fences (see §6.4).
Custom theme CSS per document. Themes are the five Mermaid built-ins (default/dark/forest/neutral/base) — anything else is v3+.
PNG/PDF export. Mermaid can output SVG; SVG download is trivial, PNG/PDF belongs in the export path of a Document operator (analogous to Records → CSV).
Interactive diagram features (clickable nodes, tooltip drilldown). Mermaid supports this with click <id> "url" directives; this happens without our involvement in the source — we do not filter it, but we also do not actively enable it.
2. Data Model
2.1 Top-Level
Field
Type
Required
Meaning
kind
string = "diagram"
yes
For dispatcher recognition.
dialect
string
no
Renderer dialect. Default "mermaid". In v1, "mermaid" is the only accepted value; unknown values fall back to the Raw editor.
diagram
DiagramHeader
no
Render metadata (theme, look). Default {}.
source
string
yes
The diagram source text in the language specified by dialect. At least one non-whitespace character.
Unknown top-level keys remain in doc.extra and are re-emitted verbatim when writing (pass-through like all other kinds).
dialect is present as a field now so that a later switch to d2 or PlantUML does not become a breaking change. v1 only writes the field if it does not match the default — the canonical Markdown default case does not require it.
2.2 DiagramHeader
Field
Type
Default
Meaning
theme
enumdefault|dark|forest|neutral|base
"default"
Mermaid built-in theme. Other values are clamped to default with a codec warning during reading.
look
enumclassic|handDrawn
"classic"
Mermaid 10+: handDrawn activates the rough.js sketch style.
fontFamily
string
null
Optional font family override. Missing → Mermaid default.
Pass-through for unknown header keys: yes, in diagram.extra (analogous to chart.extra and slides.extra).
Deliberately excluded:securityLevel is not user-configurable — we enforce securityLevel: 'strict' centrally in the renderer (see §5.4). This prevents HTML in the source from being executable; this is security-relevant and does not belong in the user document.
2.3 Mermaid as Source Language
What is accepted as source is exactly what Mermaid (version pinned in @vance/vance-face’s package.json) understands. Currently, Mermaid covers, among others:
Possible, but convention: true Mindmap documents use kind: mindmap with markmap.
Timeline
timeline
C4
C4Context / C4Container / …
Block / Packet / Architecture
block-beta / packet-beta / architecture-beta
Beta diagram types — functional but unstable.
The table is informative, not prescriptive. Vancetope does not validate the source — Mermaid does so during rendering and throws an exception for broken syntax, which appears as a banner in the UI and as an error message in the LLM tool result.
Mermaid’s own Frontmatter: Mermaid allows YAML front matter directly within the source (---\ntitle: …\nconfig:\n theme: dark\n---\nflowchart TD\n…). This is Mermaid’s own syntax and opaque to Vancetope — pass-through, no conflict with the Vancetope document front matter (which is outside the code fence).
Canonical Form (JSON):
{"$meta":{"kind":"diagram"},"diagram":{"theme":"default","look":"classic"},"source":"flowchart TD\n A[Start] --> B{Entscheidung}\n B -->|ja| C[OK]\n B -->|nein| D[Ende]\n"}
3. On-Disk Formats
3.1 Markdown (Canonical)
---kind:diagramdiagram:theme:defaultlook:classic---```mermaid
flowchart TD
A[Start] --> B{Entscheidung}
B -->|ja| C[OK]
B -->|nein| D[Ende]
**Reading Rules:**
- Front matter like all other kinds (`---` fences at the beginning of the file). Front matter is optional — without it, the kind is known from the Document Header Mapping (`HeaderStrategy`).
- Body: The codec expects **exactly one** ```` ```mermaid ```` (or ```` ```<dialect> ````) code fence. The content of the fence becomes `source`.
- Multiple fences in the body: the first one wins, codec warning, remaining fences land in `doc.extra._unparsedBody` as raw text (round-trip stable).
- No fence: empty `source`. Codec warning, render shows empty state.
- Text before/after the fence is parked in `doc.extra._preamble` / `doc.extra._postamble` as raw text — round-trip stable, but not visible in the `Diagram` tab.
**Writing Rules:**
- Front matter with `kind: diagram`. `diagram:` block only if it deviates from defaults (at least one non-default field).
- Body: a single ```` ```mermaid ```` code fence with the `source` content, separated by blank lines. A trailing newline at the end of the source is normalized (exactly one).
- `_preamble`/`_postamble`/`_unparsedBody` from `doc.extra` are re-emitted in the order `preamble → fence → postamble → unparsedBody`.
- If `dialect != "mermaid"`: the fence info string is the dialect name. This makes later d2 support a pure renderer extension.
### 3.2 JSON
Identical to the canonical form from §2:
```json
{
"$meta": { "kind": "diagram" },
"diagram": { "theme": "dark", "look": "handDrawn" },
"source": "sequenceDiagram\n Alice->>Bob: Hello\n Bob-->>Alice: Hi\n"
}
2-space indent.
Order of top-level keys: $meta, dialect?, diagram, source, then unknown pass-through keys in insertion order.
source is a string (multi-line, with \n). When programmatically building the document, insert fully formatted source text. No array form, no per-line splitting.
dialect is only emitted if ≠ "mermaid".
3.3 YAML
$meta:kind:diagramdiagram:theme:darklook:handDrawnsource:|sequenceDiagramAlice->>Bob: HelloBob-->>Alice: Hi
Single document: Top-level mapping with $meta: { kind: diagram } as the first key, followed by dialect?, diagram block, and source as a |-literal scalar. Block-style mapping, 2-space indent. | preserves newlines without escape complexity.
3.4 Codec Impact
Custom lightweight codec diagramCodec.ts in @vance/shared:
Markdown → Parse: Detach front matter, search body for Mermaid code fence (fenced_code with info string == dialect), extract first fence, rest into extra._preamble/_postamble/_unparsedBody.
Markdown → Write: Front matter + <preamble>\n\n\``\n\n\`\`\`\n\n` composition.
JSON/YAML: identical to items codec convention — $meta wrapper, optional dialect, diagram block, source as string.
Server-side: no dedicated endpoint, no server-side Mermaid rendering. The HeaderStrategy path automatically mirrors kind: diagram to DocumentDocument.kind. The source lands as part of the inlineText body and is thus RAG-indexable — the LLM can indeed find “the sequence diagram for the login flow” via full-text search.
4. Server Path
Like chart, graph, slides: no dedicated endpoint, no server-side Mermaid parser. Diagrams are display content; the backend transports them as body.
DocumentDocument.kind = "diagram" via HeaderStrategy. Search/RAG indexes the inlineText body (= the full Markdown wrapper including Mermaid source) as usual.
LLM Tool Path (informative, belongs in the Tool Spec): document_create(kind="diagram", source="flowchart TD\n ...") is the expected call. Tool implementation persists the document; the render roundtrip only happens when a client (Web UI, later Mobile) opens the document. This means the backend has no Mermaid dependency and no render latency in the hot path.
kind === 'diagram' + unknown dialect → Raw editor only, banner message in editor: “Unsupported diagram dialect: <value>”.
Otherwise (not a Diagram document): unchanged.
When switching Diagram → Raw, the parsed model is serialized back; when switching Raw → Diagram, the codec re-parses the current body and re-renders.
5.2 Diagram Tab
Read-only render based on mermaid:
Adapter diagramToMermaidInput(doc: DiagramDocument): { source: string, config: MermaidConfig } builds the Mermaid init config (theme, look, fontFamily) from the diagram block and passes through source.
mermaid is lazy-loaded (import('mermaid')), not on editor boot. Only when the Diagram tab is activated. Mermaid weighs ~700 KB minified gzipped — users who don’t open a diagram don’t pay for it. Markmap already does this for kind: mindmap.
Layout: Diagram container fills editor content to 65 vh / min. 420 px height (analogous to Mindmap and Graph). SVG is initially scaled to width: 100%, height: auto.
Pan/Zoom not in v1. Users with diagrams that are too large scroll — or split the diagram. Pan/Zoom is §6.1.
SVG download button bottom right: saves the rendered SVG as a file (<document.title>.svg). Pure client action, no server roundtrip.
5.3 Raw Tab
CodeMirror 6 with Markdown mode (for md) or JSON/YAML mode. Within the Mermaid code fence, no Mermaid-specific syntax highlighting applies in v1 — the CodeMirror Markdown mode shows the fence content as a code block (monospace, slightly offset), which is sufficient. A Mermaid-specific CodeMirror mode does not exist out-of-the-box; a custom mode would be v2/v3.
Editing the source text, live preview is not synchronized in v1 — the user switches to the Diagram tab to see the result.
5.4 Render Configuration & Security
Mermaid is called with the following init — always, not user-configurable:
mermaid.initialize({securityLevel:'strict',// blocks HTML/JS in the sourcestartOnLoad:false,// we render manually per documenttheme:doc.diagram?.theme??'default',look:doc.diagram?.look??'classic',fontFamily:doc.diagram?.fontFamily??undefined,})
securityLevel: 'strict' is mandatory. This prevents an LLM creating a Diagram document via a tool from injecting a <script> tag into the source that would then execute in the browser during rendering. click <id> "url" (Mermaid’s own interaction) remains active but is treated as secure link navigation — no JS hook.
5.5 Error Display
Mermaid throws an exception during rendering if the source is syntactically broken. The <DiagramView> catches this and displays:
Banner at the top of the Diagram tab: red alert (VAlert) with the Mermaid error message (e.g., Parse error on line 3: Expecting '...' got '...').
Below it, the original source as a code block (read-only, with line numbers) — so the user immediately sees where Mermaid stopped.
No crash, no empty tab — the editor remains usable, the Raw tab works.
For LLM tool calls (e.g., document_create(kind="diagram", source="...")), Mermaid render validation is not performed in the tool path — the backend persists blindly, the error only appears during the first client render. Rationale: a backend without a headless browser cannot reliably render Mermaid. To force the LLM to write “valid Mermaid,” an optional validator roundtrip via LightLlmService with a render probe in the client can be set up — out of scope for v1.
5.6 v1 Feature Set
Feature
v1
Note
Diagram render (Mermaid)
✓
SVG, scoped container
Theme default/dark/forest/neutral/base
✓
via diagram.theme
Look classic/handDrawn
✓
Mermaid 10+
Lazy load of the library
✓
only when activating the Diagram tab
Syntax error banner
✓
Mermaid exception as alert
SVG download
✓
bottom right, filename = Document Title
Pan / Zoom
✗
see §6.1
Mermaid CodeMirror mode
✗
see §6.3
Side-by-Side Live Preview
✗
see §6.2
In-Place WYSIWYG editing
✗
not planned — no library does this well
Multiple diagrams per document
✗
see §6.4
PNG/PDF export
✗
see §6.5
Custom theme CSS
✗
v3+
Clickable nodes (Mermaid click)
(◯)
persisted in source, activated in render (Mermaid’s own feature) — we do not filter, we do not promote
(◯) = Data passes through round-trip stable, activation via Mermaid’s own syntax.
5.7 Components
<DiagramView> — top-level Diagram tab. Receives :doc: DiagramDocument, calls adapter + Mermaid (lazy), mounts SVG into a scoped container. Holds error state and renders banner on exception.
<DiagramDownloadButton> — small button bottom right; serializes current SVG to Blob, triggers download.
Tab bar like mindmap/slides (content-tab class from DocumentApp.vue).
5.8 Visual Conventions
Default theme aligns with DaisyUI theme via Mermaid’s default/dark — light/dark follows the active DaisyUI theme (changing DaisyUI theme triggers re-render with matching Mermaid theme).
SVG is mounted without an additional frame, the editor background shines through. No card, no border.
Render latency typically <50 ms for small diagrams (<50 nodes). A subtle skeleton block shows a loading spinner for initial library loading (~700 KB).
6. Future (not v1)
6.1 Pan / Zoom
Mermaid provides pure SVG without its own pan/zoom functionality. A library like svg-pan-zoom (~10 KB, MIT) can be applied to the SVG node afterwards. Useful once real diagrams become too large for the editor viewport — until then, browser scrolling is sufficient.
6.2 Side-by-Side Live Preview
CodeMirror on the left, Mermaid render on the right, re-rendering with debounce (~300 ms) while typing. Once we have this pattern, it will also apply to kind: doc with Markdown and kind: slides with Marpit — thus, a cross-kind layout component spec, not diagram-solo. Until then: tab switching.
6.3 Mermaid-Specific CodeMirror Mode
A custom CodeMirror 6 mode for Mermaid syntax (keywords like flowchart, sequenceDiagram, arrow operators -->, block brackets). Significantly increases editing comfort but is non-trivial — no official mode exists, a custom implementation would be 200-500 lines of Lezer grammar. Awaits genuine user pain.
6.4 Multiple Diagrams per Document
Currently: one document = one diagram. If collective representations emerge (e.g., “System overview with five sub-diagrams”), the natural form would be a kind: doc Markdown document with multiple ```mermaid code fences — no schema change to kind: diagram needed. The kind: doc renderer would then need the Mermaid adapter as an additional Markdown renderer plugin. A separate spec point for kind: doc.
6.5 Export — PNG, PDF
mermaid.render() outputs SVG; PNG is possible via a canvas intermediate step (svg-to-png or similar, ~5 KB), PDF via jsPDF (likely already in the bundle for kind: chart). Server-side export via Headless Chromium would be the clean path — belongs in the export path of a Document operator, not in the editor.
6.6 LLM-side Auto-Repair
If the LLM writes a syntactically incorrect diagram, this is currently an error during rendering. Future pattern: document_create(kind="diagram", source=...) validates via LightLlmService (single-shot LLM call with Mermaid render probe as schema replacement), retries on error up to 2x. Requires browser- or Node-side Mermaid headless render in the Brain — a separate spec point, awaits demand.
6.7 Embed Directive in Slides / Markdown Documents
A kind: slides or kind: doc references a kind: diagram document via Vancetope URI:  renders the diagram inline. The convention for vance: URLs belongs in a separate spec, then this one attaches to it (same point as doc-kind-slides §6.3).
6.8 Additional Dialects: d2, PlantUML
dialect: "d2" or dialect: "plantuml". d2 is an npm package (@terrastruct/d2), runs client-side, ~300 KB — comparable to Mermaid in style and LLM prevalence. PlantUML requires a render server (Java) — not compatible with the v1 no-backend render principle; only useful as a server-side export path. Decision will only come when there is genuine user demand.
7. Open Points
Dialect validation on write: Does the codec accept arbitrary dialect values in pass-through (for forward compatibility), or does it reject unknown dialects with a codec warning? Suggestion: accept with warning, render then falls back to Raw editor (see §5.1). This allows a future Vancetope client with d2 support to read an old Vancetope v1 document without it appearing broken.
Theme auto-switch: If the browser user switches between DaisyUI Light and DaisyUI Dark, should the Diagram tab automatically switch between Mermaid default and dark? Suggestion: yes, provided diagram.theme is not explicitly set (i.e., default case). If a theme is explicitly set, the user’s choice remains.
Multiple render performance: If a user frequently clicks the Diagram tab open and closed, should Mermaid re-render it every time? Cache the last SVG output, invalidate on source/diagram change. Trivial memoization, belongs in <DiagramView> implementation.
Server-side RAG-Indexing: The inlineText body contains the raw Mermaid source. RAG finds “Sequence Diagram for Login Flow” because “sequenceDiagram” and “Login” appear as tokens — this is OK. Should additionally extracted labels (node names, edge descriptions) be indexed as a structured meta-field? v1 no, because that would require a server-side Mermaid parser, which we deliberately avoid. If searching by node name becomes relevant, full-text fallback is usually sufficient.
Document Title vs. Mermaid Frontmatter Title: Mermaid accepts a title: field in its own source front matter, which is rendered above the diagram. The Vancetope document also has a title. Conflict behavior: both remain independent, the user decides. We do not interfere with the source.