# 0019 · Diagrams are Mermaid text, drawn by the web UI

**Status:** Accepted · 2026-10-20.

## Context

A knowledge base explains systems, and systems are explained with diagrams: how the parts connect,
who calls whom, what state a thing moves through. CoDNA's own architecture was drawn outside
CoDNA, because CoDNA could not show it. Pages are markdown and nothing drew a diagram.

A picture of one could have been pasted in as an image ([0015](https://codna.me/docs/adr/0015-page-owned-files.md)), but
images do not suit diagrams here. Agents write much of what is in CoDNA, and an agent cannot add an
image: page files are uploaded by people, and the MCP tools can look at an image but not make one.
An image cannot be read back as what it says, so an agent cannot change one either. A diagram that
nobody can edit falls behind the system it describes. SVG, the one image format that is text, is
refused because it can carry script.

## Decision

**A diagram is a fenced code block marked `mermaid`, and the web UI draws it**, as GitHub does.
The page stores the text and nothing else ([0006](https://codna.me/docs/adr/0006-inline-markdown-links.md): text is never
re-serialised). An agent writes and changes a diagram as it does any other text, every change is in
the page's history as a diff, and an export holds the block as written. GitHub, Obsidian and most
other markdown tools draw it too.

- **Where it is drawn.** In reading, in a past version, and in the editor while the cursor is outside
  the block. With the cursor in it the text shows, with the drawing under it, drawn again as it is
  written. A block Mermaid cannot read shows its text, with one line on what was wrong.
- **How it looks.** In CoDNA's light and dark tokens, read from the page, and drawn again when the
  theme changes. A diagram wider than the text is shrunk to fit it, whole, and opens over the page
  as large as the window allows, with zoom and dragging to move around. The text column is kept to
  its reading measure, beside the table of contents, so a diagram does not widen it.
- **What it costs.** Mermaid is pinned to one version and loaded only by a page that has a diagram,
  as chunks of its own. A page without one loads about 2 KB more than before, and nothing of Mermaid.
- **What it may do.** Mermaid draws with `securityLevel: 'strict'` and no HTML labels, and a diagram
  cannot change either, or its stylesheet, with an `%%{init}%%` line. Its SVG is sanitised again
  with DOMPurify, and its stylesheet keeps only rules scoped to the diagram itself, so no diagram
  can restyle the page around it. Links a `click` line makes are drawn but lead nowhere: one would
  take the reader out of the app, and a page's relative links would not resolve inside a diagram.
  The web UI's Content-Security-Policy allows inline styles for the stylesheet inside the SVG, and
  scripts only from the instance (SECURITY.md, "What a page can do in the browser"); files served on
  their own keep stricter ones ([files.md](https://codna.me/docs/files.md#over-http)).
- **Elsewhere.** An excerpt, a search result and a list row say `[Diagram]` where a diagram is,
  never its code. `get_document` and export return the text, unchanged. The MCP server's
  instructions tell agents to write diagrams this way.

## Consequences

- Diagrams can be kept up to date by whoever keeps the page up to date, person or agent.
- Drawing happens in the browser, so the server has nothing to render, store or cache. It also means
  nothing outside the web UI draws one: an excerpt says `[Diagram]`, and a notification would show
  the text.
- The words inside a diagram are not searched, as code is not. What matters belongs in the text
  around it as well.
- A diagram is laid out by the reader's browser, with the reader's fonts, so it can differ slightly
  from one machine to another. Upgrading Mermaid can change layouts too, which is why it is pinned.
- Mermaid is several megabytes across its chunks. The first page with a diagram waits for it once;
  after that it is cached like the rest of the app.
- A large diagram's words are small in the column; it is read in the full view. Drawn first at its
  own size, scrolling sideways in the column, a wide one showed a third of itself at a time, which
  was harder to read than the whole of it small and one click from full size.

## Alternatives rejected

- **Images.** Agents cannot add them or change them, and a picture of a diagram cannot be diffed,
  searched or reviewed.
- **Drawing on the server.** Mermaid lays out with a browser's text measurement. Running it in the Go
  binary would take a headless browser or a JavaScript engine with a DOM, against
  [0003](https://codna.me/docs/adr/0003-go-single-binary.md), for a picture the reader's browser can draw.
- **Other diagram languages.** PlantUML needs a Java server; Graphviz and D2 are less often written
  by people and agents, and fewer markdown tools draw them. Mermaid is what GitHub and Obsidian
  draw, and what agents already write.
- **Drawing inside a sandboxed iframe.** It would isolate the SVG fully, at the cost of sizing,
  theming and scrolling an iframe per diagram. Strict mode, sanitising and scoping the stylesheet
  close what an iframe would.
