# Migrating from Mermaid

> Page: https://docs.plotwork.dev/migrating-from-mermaid. Index of every page: https://docs.plotwork.dev/llms.txt

How to move existing Mermaid diagrams to Plotwork with a converter that is deterministic, and how an AI agent finishes the job. It is written for the agent first: every step names the tool to call and what the result means. People can follow the same steps over the HTTP API.

## TL;DR

| Question | Answer |
|---|---|
| What converts? | Flowcharts (`flowchart`, `graph`), `sequenceDiagram`, `erDiagram`, `stateDiagram`, `classDiagram`, `gantt`, `gitGraph`, `timeline`, `journey`, `mindmap`, `treemap-beta` (and `treemap`), `pie`, `quadrantChart`, `C4Context`, `C4Container`, `C4Component`, `C4Dynamic`, `C4Deployment`, `sankey-beta` (and `sankey`), `xychart-beta` (and `xychart`), and `treeView-beta` (and `treeView`). Each becomes the Plotwork kind of the same name: flow, sequence, schema, state, class, gantt, git, timeline, journey, mindmap, treemap, pie, quadrant, c4, sankey, xychart, or tree. |
| What does not? | Kanban, radar, and the other kinds in [the list below](#mermaid-kinds-plotwork-does-not-draw). One diagnostic names the kind and says what to do instead. |
| One diagram | Call `convert_mermaid` with the Mermaid text. It returns the Plotwork source, the diagnostics, the validation, and an outline. |
| A whole document | Call `sync_markdown` with `convertMermaid: true`. It returns the rewritten document and the picture files to write. |
| HTTP API | `POST /v1/diagrams/convert-mermaid` for one diagram and `POST /v1/markdown/sync` for a document. Calling them directly needs the [Pro plan](https://docs.plotwork.dev/plans-and-billing.md) and an API key. |
| What is lost? | Strokes, text colours, themes, links, tooltips, activations, and a few more. A fill that has a [tone](#fills-become-tones) is kept as that tone. Every loss is reported with a diagnostic and a hint. Nothing is dropped silently. |
| Is it a model? | No. The converter is plain code. The same Mermaid always gives the same Plotwork. |
| What is left for the agent? | Read every diagnostic, give each node a type that means something, add a title and icons, then validate and render. |

## Why migrate

GitHub already draws Mermaid fences in the page, so Mermaid works with no setup. Plotwork gives you these in return for the move:

| What you get | How |
|---|---|
| One look in light and dark | The theme owns every colour. There is no `classDef`, `style`, or `%%{init}%%` to vary from one diagram to the next. An author chooses a node type or a tone for meaning. |
| A static picture | The output is an SVG file in the repository. It needs no JavaScript, no browser, and no network to display. |
| Output that diffs cleanly | The same source gives the same bytes, so a change to a diagram shows as a small change in the diff. |
| Errors an agent can repair | Every mistake has a line, a column, and a hint, and all of them come back in one pass. |
| A closed icon vocabulary | An icon is `set:name` from a fixed list, and an unknown name is an error with a suggestion. |
| Text that never overflows | Boxes grow to fit their text, with the same padding on every side. |

## When not to migrate

Keep the Mermaid fence when the diagram is one of the kinds in the next table. GitHub keeps drawing it. A fence that cannot be converted is left exactly as it was, so a document can hold both kinds.

### Mermaid kinds Plotwork does not draw

| Mermaid diagram | Closest Plotwork kind | What to do |
|---|---|---|
| requirement diagram | `flow` | Redraw it as a flow diagram with one node per requirement and element, or keep it as Mermaid in its own mermaid fence. |
| block diagram | `flow` | Redraw it as a flow diagram with a group for each block that holds others, or keep it as Mermaid in its own mermaid fence. |
| packet diagram | `schema` | Redraw it as a schema diagram with one column per field, or keep it as Mermaid in its own mermaid fence. |
| kanban board | none | Keep it as Mermaid in its own mermaid fence; GitHub draws it. A Markdown task list per column is the plain alternative. |
| architecture diagram | `flow` | Redraw it as a flow diagram: a group for each Mermaid group and a node with a type and an icon for each service. Or keep it as Mermaid in its own mermaid fence. |
| ZenUML sequence diagram | `sequence` | Redraw it as a Plotwork sequence diagram (actors, messages, and alt, opt, loop, par blocks), or keep it as Mermaid in its own mermaid fence. |
| radar chart | none | Keep it as Mermaid in its own mermaid fence; GitHub draws it. |

These kinds used to be on this list and now convert to their own kinds: C4 diagrams, class diagrams, Gantt charts, git graphs, mind maps, pie charts, quadrant charts, Sankey diagrams, timelines, tree views, treemaps, user journeys, and XY charts. A state diagram is now a state diagram and no longer a flow diagram. [Other Mermaid kinds that convert](#other-mermaid-kinds-that-convert) says where each one is explained.

## The migration recipe for an agent

Follow these steps in order. Each one is a tool call or a decision.

1. **Convert.** For one diagram, call `convert_mermaid` with the text inside the fence. For a document, call `sync_markdown` with `convertMermaid: true`.
2. **Read every diagnostic.** They are data: `code`, `severity`, `line`, `column`, `message`, and a `hint` that says what to do. Lines and columns point into the Mermaid you sent. A code that starts with `M1` is an error and nothing was converted. A code that starts with `M2` is a warning and the conversion went on.
3. **Resolve each one** with the guidance in [what does not carry over](#what-does-not-carry-over-and-why) and the [diagnostic codes](#diagnostic-codes). Most need no action. Some need a choice, such as which node an edge into a subgraph should reach.
4. **Make it meaningful.** The converter chooses a node type only when the shape or a short label says what the node is. Every other node is a plain `step`. Choose a better type for each (see [how a node type is chosen](#how-a-node-type-is-chosen)), give the diagram a title, and name the groups. Take the icon suggestions in `validation.warnings`: each says which `icon=` to write. Find others with `search_icons` or `map_icons`.
5. **Validate.** Call `draw_diagram` on the edited Plotwork source. It returns every error and warning, and draws nothing until the source is valid.
6. **Render.** Call `draw_diagram` again for the pictures, or put the source in a `diagram` fence and call `sync_markdown`. See [Diagrams in Markdown and on GitHub](https://docs.plotwork.dev/markdown-and-github.md).

### Worked example: one call and its result

The call:

```json
{
  "tool": "convert_mermaid",
  "arguments": {
    "source": "flowchart TD\n  user[User] --> db[(Orders DB)]\n  classDef error fill:#f66\n  class db error",
    "render": false
  }
}
```

The `source` argument, written out one line at a time:

```mermaid-source
flowchart TD
  user[User] --> db[(Orders DB)]
  classDef error fill:#f66
  class db error
```

The result, shortened to the fields an agent reads first:

```json
{
  "ok": true,
  "kind": "flow",
  "mermaidType": "flowchart",
  "source": "flow direction=down\n\nnode user person \"User\"\nnode db database \"Orders DB\" tone=red\n\nuser -> db",
  "diagnostics": [
    {
      "code": "M214",
      "severity": "warning",
      "line": 1,
      "message": "The diagram has no title.",
      "hint": "Add a title to the header line of the Plotwork source, or a title: line in the Mermaid front matter."
    },
    {
      "code": "M204",
      "severity": "warning",
      "line": 3,
      "message": "The fill #f66 of the class \"error\" became the tone red.",
      "hint": "Check that the tone says what it means. The theme draws it in light and dark."
    }
  ],
  "stats": {
    "nodes": 2,
    "edges": 1,
    "groups": 0,
    "dropped": 0,
    "renamed": 0
  },
  "validation": {
    "valid": true,
    "errors": [],
    "warnings": []
  },
  "outline": [
    "Nodes:",
    "- User, person",
    "- Orders DB, database",
    "Connections:",
    "- User to Orders DB"
  ],
  "next": "Read the 2 diagnostics (they point into the Mermaid) and fix what the hints say. Then choose a meaningful type for every node left as step, add a title, and take the icon suggestions in validation.warnings. Call draw_diagram on the edited source to check and draw it, or sync_markdown to place it in a document."
}
```

The `source` of the result, written out one line at a time:

```plotwork
flow direction=down

node user person "User"
node db database "Orders DB" tone=red

user -> db
```

The field `ok` is true only when `source` passes the Plotwork parser. When it is false, `source` is empty and `diagnostics` holds the reason, so the converter never returns source that would fail validation.

### Worked example: a whole document

Ask the agent: "Replace the Mermaid diagrams in README.md." The tool sequence is:

| Step | Call | What to do with the result |
|---|---|---|
| 1 | `sync_markdown` with `markdown` (the file text), `documentPath: "README.md"`, `convertMermaid: true`, `mode: "check"` | A dry run. Read `report`: each Mermaid fence is `converted` or `failed`, with diagnostics. Nothing is rewritten. |
| 2 | Fix what the report says, or accept it | Fences that fail are left as Mermaid and reported. |
| 3 | `sync_markdown` again with `mode: "sync"` | The result has `markdown` (the rewritten document) and `files` (every SVG, light and dark). |
| 4 | Write `markdown` to `README.md` and each `files` entry to its `path` | The tool writes nothing itself; it has no file system. |
| 5 | Review the change in git | Read the diff before it is committed. |

The result, shortened:

```json
{
  "mode": "sync",
  "markdown": "<the whole document, rewritten>",
  "changed": true,
  "files": [
    {
      "path": "assets/diagrams/readme-sign-in-flow.svg",
      "content": "<svg text>",
      "encoding": "utf8"
    },
    {
      "path": "assets/diagrams/readme-sign-in-flow.dark.svg",
      "content": "<svg text>",
      "encoding": "utf8"
    }
  ],
  "report": [
    {
      "id": "readme-sign-in-flow",
      "source": "mermaid",
      "kind": "flow",
      "line": 12,
      "status": "converted",
      "diagnostics": [],
      "message": null
    }
  ],
  "summary": {
    "diagrams": 1,
    "synced": 0,
    "converted": 1,
    "unchanged": 0,
    "skipped": 0,
    "failed": 0,
    "files": 2
  },
  "caps": {
    "hit": false,
    "messages": []
  }
}
```

A call handles up to 30 diagrams and a document of up to 10 million characters. If a document has more, `caps` says which fences were skipped; send them in a second call. Nothing is cut silently.

## Concept mapping

These tables are the converter's own data, so they cannot drift from what it does.

### Flowchart shapes

| Mermaid shape | Example | Plotwork type | Why |
|---|---|---|---|
| Rectangle | `A[Text]` | `step` | The neutral shape. The label may suggest a type (see the keyword table). |
| Rounded rectangle | `A(Text)` | `step` | The same as a rectangle. |
| Stadium | `A([Text])` | `terminal` | A start or an end of a flow, which is what a terminal is. |
| Subroutine | `A[[Text]]` | `step` | A plain step. Plotwork has no framed box. |
| Cylinder | `A[(Text)]` | `database` | A data store. A label such as Redis or Kafka picks the more exact cache or queue. |
| Circle | `A((Text))` | `terminal` | Used for connectors and for starts and ends, so a terminal. |
| Double circle | `A(((Text)))` | `terminal` | An end of a flow, so a terminal. |
| Rhombus | `A{Text}` | `decision` | A question with branches, which is what a decision is. |
| Hexagon | `A{{Text}}` | `step` | A plain step. Plotwork has no hexagon. |
| Asymmetric | `A>Text]` | `step` | A plain step. |
| Parallelogram | `A[/Text/]` | `step` | A plain step. Input and output are not a separate type. |
| Parallelogram, leaning left | `A[\Text\]` | `step` | A plain step. |
| Trapezoid | `A[/Text\]` | `step` | A plain step. |
| Trapezoid, inverted | `A[\Text/]` | `step` | A plain step. |

Shapes written in the newer `A@{ shape: name, label: "text" }` form map the same way:

| Shape names for `@{ shape: name }` | Plotwork type | Why |
|---|---|---|
| `lin-cyl`, `disk`, `h-cyl`, `das`, `horizontal-cylinder`, `bow-rect`, `stored-data` | `storage` | Storage that is not a database. |
| `cloud` | `external` | Something outside your system, which is what an external node is. |
| `doc`, `docs`, `documents`, `lin-doc`, `tag-doc`, `st-doc`, `flag`, `paper-tape` | `step` | A plain step. |
| `sm-circ`, `small-circle`, `start` | `terminal` | The start of a flow. |
| `fr-circ`, `framed-circle`, `stop` | `terminal` | The end of a flow. |
| `st-rect`, `stacked-rectangle`, `lin-rect`, `lin-proc`, `lined-process`, `shaded-process`, `div-rect`, `div-proc`, `divided-process`, `divided-rectangle`, `notch-rect`, `card`, `notched-rectangle`, `sl-rect`, `manual-input`, `win-pane`, `internal-storage`, `curv-trap`, `display`, `delay`, `half-rounded-rectangle`, `tri`, `extract`, `flip-tri`, `manual-file`, `hourglass`, `collate`, `bolt`, `com-link`, `lightning-bolt`, `cross-circ`, `summary`, `f-circ`, `filled-circle`, `junction`, `text`, `brace`, `brace-r`, `braces`, `fork`, `join`, `bang`, `processes`, `procs`, `bow-tie-rectangle` | `step` | A plain step with a warning, since the shape itself is lost. |

### Edges

Edge text written as `-->|text|` or `-- text -->` becomes the edge label. A chain such as `A --> B --> C` and a fan such as `A & B --> C & D` become one edge for each pair. An edge animation, `e1@{ animate: true }`, is kept as `animate=on` on the line. The edge id and the speed are dropped and reported, and a link with no arrowhead stays still, because dashes travel toward an arrowhead.

| Mermaid link | Plotwork arrow | What changes |
|---|---|---|
| `A --> B` | `->` | Nothing. |
| `A --- B` | `--` | Nothing. |
| `A -.-> B` | `-->` | Nothing. |
| `A -.- B` | `--` | Plotwork has no dashed line without an arrowhead, so the line is drawn solid. |
| `A ==> B` | `->` | Plotwork draws every edge with one stroke weight. |
| `A === B` | `--` | Plotwork draws every edge with one stroke weight. |
| `A --o B` | `->` | Plotwork has one arrowhead, so a circle or a cross at the end of a link becomes an arrowhead. |
| `A --x B` | `->` | Plotwork has one arrowhead, so a circle or a cross at the end of a link becomes an arrowhead. |
| `A <--> B` | `<->` | Nothing. |
| `A <-.-> B` | `<-->` | Nothing. |
| `A <==> B` | `<->` | Plotwork draws every edge with one stroke weight. |
| `A o--o B` | `<->` | Plotwork has one arrowhead, so a circle or a cross at the end of a link becomes an arrowhead. |
| `A x--x B` | `<->` | Plotwork has one arrowhead, so a circle or a cross at the end of a link becomes an arrowhead. |
| `A ~~~ B` | none | Dropped with a warning. It is a layout trick, and layout is done for you. |

### Subgraphs

| Mermaid | Plotwork | Note |
|---|---|---|
| `subgraph id [Title]` | `group id "Title" {`, its nodes, and `}` | The title is the label. A group has no direction of its own. |
| `subgraph Title with spaces` | `group Title_with_spaces "Title with spaces" {` | The text is the label, and the id is made valid, with a warning. |
| `Nested subgraphs` | Nested groups | Up to 24 levels deep. |
| `A node mentioned inside a subgraph` | A node declared inside the group | A node belongs to the first subgraph it is mentioned in. A second subgraph that mentions it gets a warning. |
| `A --> subgraphId` | An edge to a node inside the group | Plotwork edges connect nodes. An edge into a subgraph goes to its first node that nothing inside points at; an edge out of one comes from its last node that points at nothing inside. A warning names the node chosen. |
| `direction TB inside a subgraph` | Dropped with a warning | Plotwork has one reading direction for the whole diagram. |
| `An empty subgraph` | Dropped with a warning | A group with nothing in it draws nothing worth keeping. |
| `A subgraph with no end` | Closed at the end of the diagram, with a warning | An extra end is ignored, with a warning. |

### Sequence diagrams

Arrows:

| Mermaid arrow | Meaning | Plotwork arrow | What changes |
|---|---|---|---|
| `<<-->>` | Dotted, arrowhead at both ends | `<-->` | Nothing. |
| `<<->>` | Solid, arrowhead at both ends | `<->` | Nothing. |
| `-->>` | Dotted line with an arrowhead: a reply | `-->` | Nothing. |
| `->>` | Solid line with an arrowhead: a call | `->` | Nothing. |
| `--x` | Dotted line ending in a cross | `-->` | Plotwork has one arrowhead, so a cross at the end of a message becomes an arrowhead. Say what failed in the label. |
| `--)` | Dotted line with an open arrow: asynchronous | `..>` | Plotwork draws an asynchronous message as a dotted line with an arrowhead. |
| `-->` | Dotted line with no arrowhead | `--` | Plotwork has no dashed line without an arrowhead, so the line is drawn solid. |
| `-x` | Solid line ending in a cross | `->` | Plotwork has one arrowhead, so a cross at the end of a message becomes an arrowhead. Say what failed in the label. |
| `-)` | Solid line with an open arrow: asynchronous | `..>` | Plotwork draws an asynchronous message as a dotted line with an arrowhead. |
| `->` | Solid line with no arrowhead | `--` | Nothing. |

Blocks:

| Mermaid block | Plotwork block | Note |
|---|---|---|
| `loop` | `loop` | The same. |
| `alt` | `alt` | The same. |
| `opt` | `opt` | The same. |
| `par` | `par` | The same. |
| `critical` | `alt` | Becomes an alt: the critical region is the first branch and each option is an else branch. |
| `break` | `opt` | Becomes an opt, which is the nearest frame for steps that happen only on one path. |
| `rect` | none | The coloured background is dropped and the messages inside are kept. |

Everything else:

| Mermaid | Plotwork | Note |
|---|---|---|
| `participant A as Alice` | `actor a step "Alice"` | The neutral type step, unless the name says what it is, as Database or Browser do. Choose a type. |
| `actor A as Bob` | `actor a person "Bob"` | The stick figure means a person. |
| `A messages B without declaring them` | Actors declared in the order they first appear | Plotwork needs every actor declared; the converter adds them. |
| `Note over A,B: text` | `note over a, b : "text"` | The same. |
| `Note left of A: text, Note right of A: text` | `note over a : "text"` | Plotwork notes sit over a lifeline. A warning says the side was lost. |
| `A->>B: text with <br/>` | A message label on one line | Plotwork wraps text itself. |
| `A->>A: text` | `a -> a : "text"` | A message to oneself is kept. |
| `autonumber` | `numbered=on` on the header | Each message gets a numbered badge. A message before the `autonumber` line, or after `autonumber off`, gets `step=off`. |
| `autonumber 10, autonumber 10 5` | `numbered=on start=10`, and `step=15` on a message whose number the count would not give | The numbers Mermaid draws are the numbers Plotwork draws, up to 999. |
| `title Text` | The diagram title | A front matter title wins when both are given. |

### Entity relationship diagrams

A Mermaid relationship names two entities and no columns. A Plotwork relation joins one column to another, so the converter finds the key columns, and adds a column when it cannot. Each added column is reported.

| Meaning | Left marker | Right marker | Plotwork |
|---|---|---|---|
| Exactly one | `\|\|` | `\|\|` | The key it points at; the foreign key column is not nullable. |
| Zero or one | `\|o` | `o\|` | The foreign key column is `nullable`. |
| One or more | `}\|` | `\|{` | A foreign key on this side (many to one). The "at least one" minimum is not drawn. |
| Zero or more | `}o` | `o{` | A foreign key on this side (many to one). |

Whole relationships:

| Mermaid | Meaning | Plotwork |
|---|---|---|
| `A \|\|--o{ B : has` | One A, zero or more B | B holds a foreign key to A. |
| `A \|\|--\|\| B : has` | One A, exactly one B | B holds a `unique` foreign key to A, which draws one to one. |
| `A }o--o\| B : has` | Many A, zero or one B | A holds a `nullable` foreign key to B. |
| `A }o--o{ B : has` | Many to many | A join table with a foreign key to each side. |

Lines and keys:

| Mermaid line | Meaning | Plotwork |
|---|---|---|
| `--` | Identifying | A normal relation. Plotwork draws one line style. |
| `..` | Non-identifying | A normal relation, with a warning that the dotted line is not drawn. |

| Mermaid key | Plotwork flag | Meaning |
|---|---|---|
| `PK` | `pk` | Primary key |
| `FK` | `fk` | Foreign key |
| `UK` | `unique` | Unique key |

### State diagrams

A state diagram becomes a Plotwork state diagram, which has its own kind: states are rounded cards, transitions are labelled lines, composite states are frames, and the start and end markers, choices, forks, joins and notes are drawn as such. Older versions of the converter turned a state diagram into a flow diagram of groups and `terminal` nodes, so a state diagram that was converted earlier reads differently when it is converted again. See [State diagrams](https://docs.plotwork.dev/state-diagrams.md).

| Mermaid | Plotwork | Note |
|---|---|---|
| `A --> B : event` | A transition `a -> b : "event"` | The label is kept as written, so `event [guard] / action` keeps its guard and its action. |
| `state A` | `state a "A"` | A rounded card. Give it a `tone=` for a meaning such as an error or a success. |
| `state "Long name" as A` | `state a "Long name"` | The id stays A. |
| `A : description` | The state `sub=` text | Each description line stays its own line under the name. |
| `[*] --> A` | `start -> a` | The start marker of the scope the line is in: the diagram, or the composite state. |
| `A --> [*]` | `a -> end` | The end marker of the scope the line is in. |
| `state A { inner states }` | A composite state `state a "A" {` with its states inside | Each composite state has its own start and end markers, and a transition may enter or leave the whole state. |
| `--` | `region {` blocks inside the composite state | The states before the first `--` are the first region. A dashed line separates the regions. |
| `state A <<choice>>` | `choice a` | A pill with a branch icon. Label the branches with their guards. |
| `state A <<fork>> or <<join>>` | `fork a` or `join a` | A bar that splits or merges parallel paths. |
| `direction LR` | `direction=right` in the header | Only at the top level. Inside a composite state it is dropped. |
| `note right of A : text` | `note a : "text" side=right` | A note beside the state, across the reading direction. A note on a composite state sits inside its frame. |
| `classDef, class, :::name, style` | Dropped with a warning | The theme owns colour. A class name such as `error` gives the state the tone red. |

### Other Mermaid kinds that convert

More Mermaid kinds convert into a Plotwork kind of their own. Each has a guide that shows what is kept and what is dropped. The codes in the last column are the ones that only that kind raises, and the [diagnostic codes](#diagnostic-codes) table explains every code.

| Mermaid | Plotwork kind | Guide | Codes of its own |
|---|---|---|---|
| `classDiagram` | `class` | [Class diagrams](https://docs.plotwork.dev/class-diagrams.md) | `M240`, `M241`, `M242` |
| `gantt` | `gantt` | [Gantt charts](https://docs.plotwork.dev/gantt-charts.md) | `M130`, `M131`, `M132`, `M230` to `M238` |
| `gitGraph` | `git` | [Git graphs](https://docs.plotwork.dev/git-graphs.md) | `M120`, `M220`, `M221`, `M222` |
| `timeline` | `timeline` | [Timelines](https://docs.plotwork.dev/timelines.md) | `M260` |
| `journey` | `journey` | [User journeys](https://docs.plotwork.dev/user-journeys.md) | `M270`, `M271` |
| `mindmap` | `mindmap` | [Mind maps](https://docs.plotwork.dev/mind-maps.md) | `M180`, `M280`, `M281`, `M282` |
| `stateDiagram` | `state` | [State diagrams](https://docs.plotwork.dev/state-diagrams.md) | `M250`, `M251` |
| `treemap-beta`, `treemap` | `treemap` | [Treemaps](https://docs.plotwork.dev/treemaps.md) | None of its own |
| `pie` | `pie` | [Pie charts](https://docs.plotwork.dev/pie-charts.md) | None of its own |
| `quadrantChart` | `quadrant` | [Quadrant charts](https://docs.plotwork.dev/quadrant-charts.md) | None of its own |
| `C4Context`, `C4Container`, `C4Component`, `C4Dynamic`, `C4Deployment` | `c4` | [C4 diagrams](https://docs.plotwork.dev/c4-diagrams.md) | None of its own |
| `sankey-beta`, `sankey` | `sankey` | [Sankey diagrams](https://docs.plotwork.dev/sankey-diagrams.md) | None of its own |
| `xychart-beta`, `xychart` | `xychart` | [XY charts](https://docs.plotwork.dev/xy-charts.md) | None of its own |
| `treeView-beta`, `treeView` | `tree` | [Tree diagrams](https://docs.plotwork.dev/tree-diagrams.md) | None of its own |

Three kinds have no Mermaid type to read: [Layered architecture](https://docs.plotwork.dev/layered-architecture.md), [Matrices](https://docs.plotwork.dev/matrices.md), and [Swimlane diagrams](https://docs.plotwork.dev/swimlane-diagrams.md). Each guide says how to move a Mermaid diagram or a Markdown table to it by hand.

## How a node type is chosen

The converter is conservative. It picks a type in this order and stops at the first that applies:

1. The construct itself decides, as for a flowchart rhombus (a `decision`) or a stadium (a `terminal`).
2. A class name that carries meaning, such as `database`, `queue`, `external`, or `error`. The converter reports that it used the class.
3. The shape: a cylinder is a `database`, a rhombus is a `decision`, a stadium or a circle is a `terminal`, a cloud is `external`. A label keyword may refine a cylinder to a cache, a queue, or storage.
4. A keyword in a short label of at most three words, and only when exactly one type matches. "Orders DB" is a `database`; "Redis queue" matches two types and stays neutral; a sentence is never read.
5. Otherwise the neutral `step`, which the converter lists in one warning so you choose a better one.

It writes an icon only when the label names a brand. "PostgreSQL", "Redis cache", or a second line such as "Stripe API" gets that brand's mark (`icon=simple:postgresql`). A decision, a start or end, and a plain `step` never get one, and a name that is only a word for a kind of thing ("Message Queue") keeps the icon of its type. The Plotwork validator suggests the other icons from the labels, and those suggestions come back in `validation.warnings`.

An HTTP status code at the start of a label sets a tone when the node has none: `200` and the other 2xx codes are green, and 4xx and 5xx codes are red. "Return 429 Too Many Requests" is red. A 3xx code gets no tone, because a redirect is neither good nor bad.

The words it looks for:

| Type | Whole label | Last word of a short label | Product names in a short label |
|---|---|---|---|
| `database` | `database`, `databases`, `db`, `datastore`, `data store`, `sql` | `database`, `db`, `datastore` | `postgres`, `postgresql`, `mysql`, `mariadb`, `sqlite`, `mongodb`, `mongo`, `dynamodb`, `cassandra`, `cockroachdb`, `oracle`, `mssql` |
| `cache` | `cache`, `caches` | `cache` | `redis`, `memcached`, `valkey` |
| `queue` | `queue`, `queues`, `topic`, `message bus`, `event bus` | `queue`, `topic`, `bus` | `kafka`, `rabbitmq`, `sqs`, `nats`, `pubsub`, `kinesis` |
| `storage` | `storage`, `bucket`, `buckets`, `blob storage`, `file storage` | `bucket`, `storage` | `s3`, `gcs` |
| `person` | `user`, `users`, `customer`, `customers`, `admin`, `administrator`, `developer`, `visitor`, `end user`, `operator`, `person`, `user request` | none | none |
| `client` | `browser`, `client`, `clients`, `frontend`, `front end`, `web app`, `mobile app`, `ui`, `cli`, `spa`, `webapp`, `client request` | `browser` | none |
| `gateway` | `gateway`, `api gateway`, `load balancer`, `reverse proxy`, `proxy`, `cdn`, `ingress` | `gateway`, `balancer`, `proxy` | `nginx`, `envoy`, `traefik`, `haproxy`, `kong` |
| `service` | `api`, `service`, `backend`, `back end`, `server`, `microservice`, `rest api`, `web server`, `app server` | `service`, `api`, `server`, `microservice`, `backend` | none |
| `function` | `worker`, `workers`, `worker pool`, `cron`, `cron job`, `scheduler`, `lambda`, `function`, `job`, `batch job` | `worker`, `cron`, `scheduler`, `lambda`, `function` | `lambda` |
| `external` | `external`, `external system`, `third party`, `3rd party`, `external api`, `partner`, `payment provider`, `email provider`, `sms provider`, `payment processor` | none | `stripe`, `sendgrid`, `twilio`, `paypal`, `mailgun`, `braintree` |
| `identity` | `idp`, `identity provider`, `sso`, `auth server`, `authorization server`, `oauth`, `oidc` | none | `okta`, `auth0`, `keycloak` |
| `secret` | `secret`, `secrets`, `vault`, `key vault`, `kms`, `secret store` | none | none |

Class names that give a type or a tone:

| Class names | Becomes |
|---|---|
| `database`, `db`, `datastore`, `store` | type `database` |
| `cache` | type `cache` |
| `queue`, `topic`, `broker` | type `queue` |
| `storage`, `bucket` | type `storage` |
| `external`, `thirdparty`, `ext`, `vendor` | type `external` |
| `user`, `person`, `actor` | type `person` |
| `client`, `browser`, `frontend` | type `client` |
| `service`, `api`, `backend` | type `service` |
| `gateway`, `proxy` | type `gateway` |
| `worker`, `job`, `function`, `lambda` | type `function` |
| `decision` | type `decision` |
| `start`, `end`, `terminal` | type `terminal` |
| `error`, `danger`, `fail`, `failure`, `critical` | tone `red` |
| `warning`, `warn`, `caution` | tone `amber` |
| `success`, `ok`, `done`, `pass`, `passed` | tone `green` |

### Fills become tones

A `style` line, a `classDef` line, and a `class` or `:::name` that points at one can set a fill. The converter reads the fill and keeps it as the tone of the node (or the group) when the colour is clear: it works out the hue of the colour and takes the tone whose band holds it. Each one is reported with `M204`, the first few on their own and the rest in one summary, so a diagram painted by an agent is not buried in warnings. A tone is a meaning, and the theme draws it in light and dark.

| Hue of the fill | Tone | Colours that land there |
|---|---|---|
| 345 to 15 degrees | `red` | red, crimson, tomato |
| 15 to 38 degrees | `orange` | orange, salmon |
| 38 to 68 degrees | `amber` | yellow, gold, khaki |
| 68 to 160 degrees | `green` | lime, green, light green |
| 160 to 200 degrees | `aqua` | teal, cyan, turquoise |
| 200 to 255 degrees | `blue` | sky blue, blue, navy, a periwinkle such as #bbf |
| 255 to 320 degrees | `violet` | purple, violet, indigo, lavender, a pastel such as #f9f |

These fills become no tone, and are reported as dropped styling with `M200` along with the strokes, text colours, and widths:

| Fill | Why it becomes no tone |
|---|---|
| Greys, such as #ddd and #333 | A grey is the page's own neutral, so there is nothing to keep. |
| Near white and near black, such as #fff and #111 | Neither says a hue; they are the surface and the ink. |
| Pink and magenta, from 320 to 345 degrees | Plotwork has no pink, and the nearest tone would be a guess. |
| A fill that is mostly transparent, or a colour that cannot be read | It is not a fill an agent chose to show. |

A fill set on the node itself wins over a tone a class name such as `error` suggests.

### Edge labels that get a status icon

An edge label that states an outcome gets a status icon, written as `icon=status:name` on the line. The icon brings its own colour, so a branch such as "Rate limited" says what happened without a colour code to learn. A label matches only when all of it is one of the phrases below, in any case and with its punctuation ignored, so "Sends the order" and "Webhook" are never given an outcome they do not state. The labels `yes`, `no`, `true`, and `false` get their tick and cross from the language itself, so the converter writes nothing for them.

| Status icon | Edge labels that get it |
|---|---|
| `status:yes` | `y`, `ok`, `okay`, `pass`, `passed`, `valid`, `correct` |
| `status:no` | `n`, `invalid`, `incorrect` |
| `status:success` | `success`, `successful`, `succeeded`, `done`, `complete`, `completed`, `approved`, `accepted` |
| `status:error` | `error`, `errors`, `failed`, `failure`, `fail`, `rejected`, `exception`, `crash`, `crashed` |
| `status:verified` | `authenticated`, `verified`, `signed in`, `logged in` |
| `status:allowed` | `allowed`, `authorized`, `authorised`, `permitted`, `granted`, `access granted` |
| `status:denied` | `denied`, `forbidden`, `unauthorized`, `unauthorised`, `unauthenticated`, `not authenticated`, `not authorized`, `not allowed`, `access denied`, `permission denied` |
| `status:blocked` | `blocked`, `banned`, `throttled`, `rate limited`, `rate limit exceeded`, `too many requests` |
| `status:warning` | `warning`, `warn`, `degraded` |
| `status:expired` | `expired`, `timeout`, `timed out` |
| `status:retry` | `retry`, `retrying`, `try again` |
| `status:pending` | `pending`, `waiting` |
| `status:skipped` | `skip`, `skipped` |
| `status:running` | `running`, `in progress`, `processing` |
| `status:revoked` | `revoked` |

## What does not carry over and why

Plotwork owns colour, spacing, and layout, so a diagram cannot set them. The rule for every row is the same: choose a node type or a tone for meaning, and let the theme choose the colour.

| Mermaid | Applies to | Why it does not carry over | What to do instead |
|---|---|---|---|
| `classDef` | flowchart, state | The theme owns every colour, in light and dark, so a diagram cannot set one. | Choose a node type or a tone for what the node means; the theme chooses the colour. |
| `class` and `:::name` | flowchart, state | The theme owns every colour, in light and dark, so a diagram cannot set one. | Choose a node type or a tone for what the node means; the theme chooses the colour. |
| `style` | flowchart, state | The theme owns every colour, in light and dark, so a diagram cannot set one. | Choose a node type or a tone for what the node means; the theme chooses the colour. |
| What a `style` or `classDef` line sets besides a fill that has a tone | flowchart | The theme owns every colour, in light and dark, so a diagram cannot set one. A fill is kept as the nearest tone; strokes, text colours, widths, and a fill with no tone are left out. | Choose a node type or a tone for what the node means; the theme chooses the colour. |
| `linkStyle` | flowchart | Plotwork draws every edge the same way. | Use an arrow form for meaning: `->` for a call, `-->` for a reply or optional link, `..>` for an asynchronous call. |
| `%%{init}%%` directives, themes, `themeVariables`, `curve` | all | Theme and layout settings belong to Plotwork, not to a diagram. | Remove it. Use `direction=` in the header for the reading direction. |
| Front matter `config:` | all | Theme and layout settings belong to Plotwork, not to a diagram. | Remove it. A `title:` is kept as the diagram title. |
| `click` callbacks and links | flowchart, state | A Plotwork diagram is a static picture with no script and no links inside it. | Link to the page in the surrounding text, or put the name of the target in the node `sub` text. |
| Tooltips | flowchart | A static picture has no hover. | Put the detail in the node `sub` text or in the diagram `desc=`. |
| Actor `link` and `links` | sequence | A static picture has no menus or links. | Link to the page in the surrounding text. |
| `direction` inside a subgraph | flowchart | Plotwork has one reading direction for the whole diagram. | Set `direction=` on the header, or split the subgraph into its own diagram. |
| Extra dashes in a link, as in `---->` | flowchart | Layout places nodes; an author cannot ask for longer edges. | Nothing: the layout spaces nodes for you. |
| Invisible link `~~~` | flowchart | It is a trick to move nodes, and layout does that for you. | Group the nodes with a `group` if they belong together. |
| Edge ids | flowchart | Plotwork names an edge by the two nodes it joins, so an id has nothing to refer to. | Nothing. An id that only animates the edge is not lost: `e1@{ animate: true }` becomes `animate=on` on the line. |
| Animation speeds `fast` and `slow` | flowchart | Plotwork animates every line at one calm pace, so a speed has nothing to set. | The line is still animated with `animate=on`. Say in the label if a flow is meant to be fast. |
| `curve` on an edge | flowchart | Layout routes every edge, with square bends and rounded corners, so a curve cannot be chosen. | Nothing: layout draws the edge for you. |
| Activations `+`, `-`, `activate`, `deactivate` | sequence | Plotwork draws lifelines without activation bars. | Say who is busy in the message labels or a `note over`. |
| `create` and `destroy` | sequence | Every Plotwork participant has a lifeline for the whole diagram. | Declare the participant at the top and say it is created or destroyed in a `note over`. |
| `box` around participants | sequence | Plotwork has no frame around participants. | Put the grouping in the participant `sub` text, such as the name of the system. |
| `rect` backgrounds | sequence | The theme owns every colour, in light and dark, so a diagram cannot set one. | The messages inside are kept. Use `opt` or `loop` if the highlighted part means something. |
| State `classDef`, `class`, and `style` | state | The theme owns every colour, in light and dark, so a diagram cannot set one. | A `tone=` on a state is a meaning, not a colour. A class name such as `error` or `success` gives the state that tone, and the class itself is dropped. |
| Entity `style` | ER | The theme owns every colour, in light and dark, so a diagram cannot set one. | A `tone=` on a table is a meaning, not a colour. |
| Non-identifying relationships `..` | ER | Plotwork draws one line style for a relation. | Mark an optional reference with `nullable` on the foreign key column. |
| "One or more" on the many side | ER | A relation draws many to one; a minimum count is a rule, not a shape. | Say it in the relation label if it matters. |

Other text is cleaned and the change is reported: HTML tags and Markdown marks are removed, `<br/>` and `\n` become a line break (a second line of a node label becomes its `sub=` text), entities such as `#quot;` and `&amp;` are decoded, and Font Awesome tokens such as `fa:fa-database` are removed with the closest bundled icon named in the hint. Quotes and backslashes are escaped so every label keeps its exact text.

Ids that are not valid Plotwork ids (spaces, dots, accents, a leading digit) are renamed the same way every time, and each rename is reported. The label keeps the original text.

## Diagnostic codes

| Code | Severity | What it means | What to do |
|---|---|---|---|
| `M100` | error | The text is empty or does not start with a Mermaid diagram type. | Check that the first line is a diagram type such as `flowchart TD` or `sequenceDiagram`. |
| `M101` | error | Plotwork does not draw this kind of Mermaid diagram. | Keep it as Mermaid in its own fence, or redraw it as the closest Plotwork kind, which the hint names. |
| `M102` | error | The diagram is over a size limit: too much text, too many nodes or edges, or nesting that is too deep. | Split it into several diagrams along a subgraph, then convert each. |
| `M103` | error | The diagram has a header but nothing to draw. | Add nodes or messages to the Mermaid, or delete the fence. |
| `M104` | error | The converter produced Plotwork that did not pass validation, so it returned nothing. | Report the Mermaid that caused it. Redraw the diagram by hand in the meantime. |
| `M110` | error | A statement could not be read as Mermaid, so nothing was converted. | Fix the line the diagnostic points at, or convert with `lenient` to skip unreadable lines and keep the rest. |
| `M200` | warning | Strokes, text colours, classes, themes, or other styling were dropped. A fill that has a tone is not here: it is kept as a tone (M204). | Choose a node type or a tone for what a node means. The theme chooses the colour. |
| `M201` | warning | A click action, link, or tooltip was dropped. | Link to the page in the surrounding text. A diagram is a static picture. |
| `M202` | warning | A hint about layout, such as a link length or a direction inside a subgraph, was dropped. | Nothing. Plotwork places nodes itself. Set `direction=` on the header if the reading direction matters. |
| `M203` | warning | Something that carried meaning has no Plotwork form and was dropped, such as activations or state notes. | Say it in a label, a `sub=` text, or a `note over` if the reader needs it. |
| `M204` | warning | A construct was replaced by the nearest Plotwork one, such as a cross end becoming an arrowhead or a fill becoming the nearest tone. | Read the message. Edit the Plotwork if the nearest form says the wrong thing. |
| `M205` | warning | Label text was cleaned: HTML tags, Markdown marks, or line breaks were removed or moved. | Compare the label in the Plotwork with the Mermaid. A second line became the `sub=` text of a node. |
| `M206` | warning | An id was not a valid Plotwork id and was renamed. | Nothing, unless you refer to the id elsewhere. The label keeps the original text. |
| `M207` | warning | A node type or tone was taken from a class name such as `database` or `error`. | Check that the type is right. The class itself was dropped. |
| `M208` | warning | Nodes got the neutral type `step` because nothing in the Mermaid says what they are. | Choose a type for each node that means something: `database`, `service`, `queue`, `person`, and so on. |
| `M209` | warning | An edge repeated an earlier one exactly and was dropped. | Nothing. Keep both only if they mean different things, and then give them different labels. |
| `M210` | warning | An edge pointed at a subgraph, and Plotwork edges connect nodes, so it was redrawn to a node inside. | Check that the node named in the message is the one the edge means. |
| `M211` | warning | A relationship names no columns, and Plotwork draws a relation between two columns, so one was added. | Rename the added column, or replace it with the real foreign key. |
| `M212` | warning | A many-to-many relationship was written as a join table with a foreign key to each side. | Rename the join table, and add the columns it holds. |
| `M213` | warning | A node appeared in more than one subgraph, and Plotwork puts a node in one group. | Check that the group the message names is the one you want. |
| `M214` | warning | The diagram has no title. A title names the picture, its file, and its alt text. | Add a title to the header line of the Plotwork source. |
| `M215` | warning | A Font Awesome icon in a label was removed. Plotwork icons are a closed set. | Set `icon=` on the node. The hint names the closest bundled icon. |
| `M216` | warning | An empty subgraph, state, or entity was dropped, or a label was empty and the id was used. | Add content to it in the Mermaid first if it should be drawn. |
| `M217` | warning | A subgraph or block was never closed, or an `end` had nothing to close, and the converter repaired it. | Check where the block should end. |
| `M218` | warning | A statement could not be read and was skipped because the conversion is lenient. | Fix the Mermaid line, or add the edge or node by hand in the Plotwork source. |
| `M250` | warning | A note on a composite state was drawn inside its frame, below the states it holds, because the frame has no free side. | Check that the note still reads well there. A note on a simple state keeps its side. |
| `M251` | warning | A state was named `start` or `end`, which Plotwork keeps for the start and end markers, so it was renamed. | Nothing, unless you refer to the id elsewhere. The label keeps the original name. |
| `M270` | warning | A journey task had a score outside 1 to 5, or a fraction, and Plotwork keeps whole scores from 1 to 5, so it was clamped or rounded. | Check that the task still reads as you meant. 1 is frustrated and 5 is delighted. |
| `M271` | warning | A journey task named an empty actor, or the same actor in two spellings that differ only in case or spacing, so the empty name was dropped or the spellings were merged. | Nothing, unless the two names were meant to be different people. Give them different words. |
| `M260` | warning | A timeline line had a # that is not part of an entity, and Mermaid reads everything after it as a comment, so the converter cut the line there as well. | Check that the text before the # says what you meant. Write the # as the entity `#35;` in the Mermaid if it was part of the text. |
| `M120` | error | A gitGraph statement names a branch or commit that does not exist, makes a branch that already exists, merges a branch into itself, or branches before the first commit, so the history cannot be replayed. | Fix the line the diagnostic points at, or convert with `lenient` to skip statements that cannot be replayed. |
| `M220` | warning | Mermaid draws a generated hash on a commit that has no id, and Plotwork draws a commit with no message as a bare dot. | Give the commit an `id:` in the Mermaid if the picture should say something next to it. |
| `M221` | warning | A `cherry-pick` named a `parent:`, and Plotwork has no choice of parent: a cherry-pick copies one commit. | Nothing, unless the picked commit is a merge and the parent mattered. Say which side in the commit message. |
| `M222` | warning | A commit had both an `id:` and a `msg:`, and Mermaid draws only the id, so the message was dropped. | Put the text the picture should show in the `id:`, or in the message of the Plotwork commit. |
| `M130` | error | A Gantt chart is scheduled in hours or minutes, or its `dateFormat` holds a time of day. Plotwork schedules in whole days, so nothing was converted. | Redraw the plan in days, or keep the chart as Mermaid in its own mermaid fence. |
| `M131` | error | A Gantt task comes after, or ends where, a task that is not in the chart, or waits for itself in a circle, so its dates cannot be worked out. | Fix the id the diagnostic names, or give the task a start date. Convert with `lenient` to skip tasks that cannot be placed. |
| `M132` | error | A Gantt `dateFormat` uses a token Plotwork does not read, such as a month name, so the dates cannot be read. | Write the dates as YYYY-MM-DD, DD/MM/YYYY, or another format of a year, a month, and a day in numbers. |
| `M230` | warning | A Gantt `axisFormat` was dropped. Plotwork writes the labels of the axis itself, from the step it draws. | Set `unit=` on the header to choose the step of the axis: day, week, month, quarter, or year. |
| `M231` | warning | A Gantt `excludes` or `includes` has no Plotwork form, apart from weekends and single days, which become `weekends=skip` and `holidays=`. | Use `weekends=skip` and `holidays=` on the header, and check that the lengths still add up. |
| `M232` | warning | A Gantt `tickInterval` was dropped because it is not one day, week, or month, which Plotwork writes as `unit=`. | Set `unit=` on the header, or leave it out so the step follows the span of the plan. |
| `M233` | warning | A Gantt `todayMarker` was dropped. Mermaid marks the day the chart is drawn, and Plotwork has no clock. | Add `today=YYYY-MM-DD` to the header to mark a day, and move it as the plan does. |
| `M234` | warning | A Gantt milestone had a length, and Mermaid draws it in the middle of it, so it sits on one day in Plotwork. | Check that the day is the one you mean, and set `at=` to change it. |
| `M235` | warning | A Gantt length changed: a fraction was rounded, a length of zero became one day, or a week was written in days because Mermaid counts it as seven working days. | Check the length in the Plotwork source, and write it as a whole number of days, weeks, or months. |
| `M236` | warning | A Gantt task tag such as `vert` has no Plotwork form and was dropped. The tags `done`, `active`, `crit`, and `milestone` are kept. | Use a milestone for a point in time, or `today=` for the current day. |
| `M237` | warning | A Gantt task ended where another task starts (`until`), and is written with the day that was, so it no longer moves with that task. | Check the date. Use `after=` on the other task if it should follow this one instead. |
| `M238` | warning | A Gantt layout setting (`topAxis`, `weekday`, `weekend`, `displayMode`) was dropped. Plotwork chooses the layout. | Nothing: the axis is above the chart, the week starts on Monday, and weekends are Saturday and Sunday. |
| `M240` | warning | A class member was written type first (`String name`, `getName() String`, `List~int~ getIds()`), and Plotwork writes a member name first, with the type after a colon, so it was turned round. | Check that the member reads as you meant. When neither word reads as a type, or both do, the text is kept as it was. |
| `M241` | warning | A class relation has no exact Plotwork form (a lollipop interface, a dashed line with no marker, or a marker at both ends), so the nearest of the eight relations was drawn. | Read the message for what was drawn. Edit the Plotwork if the nearest relation says the wrong thing. |
| `M242` | warning | A class had more than one annotation (`<<interface>>`, `<<Service>>`), and a Plotwork class has a type and one stereotype word, so the extra one was dropped. | Keep the annotation that matters, or say the other in a note on the class. |
| `M180` | error | A mind map has one root, and a later line is indented as far as it, or less, so it has no parent. Nothing was converted. | Indent the line under the node it belongs to, or move it into its own mindmap block. |
| `M280` | warning | A mind map node shape has no exact Plotwork form, such as a bang or a cloud, so the nearest shape was drawn. | Read the message for the shape that was drawn. Set `shape=` on the topic if another one reads better. |
| `M281` | warning | A mind map `::icon()` line was dropped. Plotwork icons are a closed set. | Set `icon=` on the topic. The hint names the closest bundled icon. |
| `M282` | warning | A mind map node had text before its brackets. Mermaid reads that as an id and does not draw it, so only the text inside the brackets was kept. | Put all of the label inside the brackets if the text before them belongs in it. |

Diagnostics from the converter point into the Mermaid. The Plotwork validator's own warnings, such as an icon suggestion, point into the converted source, and they come back separately in `validation`.

## Before and after

Each example shows the Mermaid source, the picture Mermaid draws from it, the Plotwork the converter produced, the picture Plotwork draws from that, and a table of what differs, with nothing edited by hand. After the conversion, an agent would still read the diagnostics, choose types for the nodes left as `step`, and take the icon suggestions.

The Mermaid pictures were drawn with Mermaid 12.1.0, in its default and dark themes, with Arial text. They are fixed pictures, drawn once from the source above them. They are not drawn by this page.

Some differences hold for every diagram:

| What | Mermaid | Plotwork |
|---|---|---|
| Meaning | The shape of a node, and a style you write for each color. | The type of a node, which sets its icon, tone, and shape. A color is never written. |
| Text for a reader who cannot see it | A role and the kind of diagram. A title and a description only when the source has `accTitle` and `accDescr` lines. | A title, a description, an outline of the nodes and connections, and the source the picture was drawn from, in every picture. |
| Text width | Measured in a browser when the diagram is drawn, so one source can give boxes of different sizes on different machines. | Measured with font metrics built into the renderer, so one source gives the same picture on every machine. |
| Light and dark | A theme chosen when the diagram is drawn. | A light picture and a dark picture of every diagram, chosen by the page.

### A flowchart with a subgraph and a decision

Mermaid:

```mermaid-source
---
title: Sign-in flow
---
flowchart TD
  user[User] --> gw[API gateway]
  gw --> auth{Credentials valid?}
  auth -->|yes| session[Create session]
  auth -->|no| denied([Access denied])
  subgraph store [Data]
    db[(Users DB)]
    audit[Audit log]
  end
  auth -.-> db
  session --> audit
  classDef database fill:#9cf
  class audit database
```

Mermaid draws it like this:

*Picture: How Mermaid draws the sign-in flow: a title, boxes joined by arrows, a diamond for the decision, a rounded end, a cylinder for the database, and a filled box around the Data group.*

The converter reports the `database` type it took from the class of that name for the audit log, the class's blue fill kept as the tone blue, and the one node left as a plain `step`. The Plotwork:

**Diagram source**

```diagram
flow "Sign-in flow" direction=down

node user person "User"
node gw gateway "API gateway"
node auth decision "Credentials valid?"
node session step "Create session"
node denied terminal "Access denied"
group store "Data" {
  node db database "Users DB"
  node audit database "Audit log" tone=blue
}

user -> gw
gw -> auth
auth -> session : "yes"
auth -> denied : "no"
auth --> db
session -> audit
```

*Picture: Sign-in flow: 7 nodes, 6 connections.*

**Text description**

**Nodes:**

- User, person
- API gateway, gateway
- Credentials valid?, decision
- Create session, step
- Access denied, terminal
- Data tier: Data
  - Users DB, database
  - Audit log, database

**Connections:**

- User to API gateway
- API gateway to Credentials valid?
- Credentials valid? to Create session: yes
- Credentials valid? to Access denied: no
- Credentials valid? to Users DB (dashed)
- Create session to Audit log

What differs between the two pictures:

| What | Mermaid | Plotwork |
|---|---|---|
| Title | Drawn above the diagram, from the front matter. | Not drawn. It names the picture for a screen reader. |
| What a node is | The shape: a box, a diamond for the decision, a rounded end, a cylinder for the database. Every shape has the same lavender fill unless a style says otherwise. | The type: an icon and a tone for each node, such as a person, a gateway, an amber decision, and a database. The end is a pill. |
| Color | `classDef database fill:#9cf` paints Audit log blue, and the subgraph has a pale yellow fill. | No color is written in the source. Audit log takes the type `database` from the class name, and the class's fill becomes `tone=blue`, which the theme draws in light and dark. |
| Edge labels | The words `yes` and `no` on the lines. | The same words, with a green tick and a red cross added, because those labels get a status icon. |
| The dotted link | A dotted line (`-.->`). | A dashed line (`-->`), which is the line for a link that is optional or indirect. |
| The group | A filled box with its name centered at the top. | A frame with a corner tab that holds the name. |

### A sequence diagram with an alt block

Mermaid:

```mermaid-source
---
title: Token refresh
---
sequenceDiagram
  autonumber
  actor U as User
  participant A as App
  participant I as Identity provider
  U->>A: Open the app
  A->>+I: Refresh token
  alt token valid
    I-->>-A: New access token
  else token revoked
    I-->>A: 401 sign in again
    A->>U: Ask to sign in
  end
  Note over A,I: Tokens last 15 minutes
```

Mermaid draws it like this:

*Picture: How Mermaid draws the token refresh: a title, a stick figure and two boxes at the top and again at the bottom, numbered messages, an activation bar, a dotted alt frame, and a yellow note.*

The converter turns `autonumber` into `numbered=on` on the header, reports the dropped activation marks, and reports the participant `App` left as a plain `step`. The `actor` became a `person`, and the name Identity provider became an `identity`. The Plotwork:

**Diagram source**

```diagram
sequence "Token refresh" numbered=on

actor U person "User"
actor A step "App"
actor I identity "Identity provider"

U -> A : "Open the app"
A -> I : "Refresh token"
alt "token valid" {
  I --> A : "New access token"
} else "token revoked" {
  I --> A : "401 sign in again"
  A -> U : "Ask to sign in"
}
note over A, I : "Tokens last 15 minutes"
```

*Picture: Token refresh: 3 participants.*

**Text description**

**Participants:**

- User, person
- App, step
- Identity provider, identity

**Steps:**

- 1. User to App: Open the app
- 2. App to Identity provider: Refresh token
- alt: token valid
  - 3. Identity provider to App (dashed): New access token
- else: token revoked
  - 4. Identity provider to App (dashed): 401 sign in again
  - 5. App to User: Ask to sign in
- Note over App and Identity provider: Tokens last 15 minutes

What differs between the two pictures:

| What | Mermaid | Plotwork |
|---|---|---|
| Participants | A stick figure for the actor and a box for each participant, drawn at the top and again at the bottom. | A card for each, at the top only, with an icon from its type: a person, and an identity provider. App stays a plain card, because its type is `step`. |
| Numbers and activation | `autonumber` numbers each message, and `activate` draws a bar on the Identity provider. | The numbers carry over as `numbered=on`, so each message gets the same numbered badge. The activation bar is dropped and reported. |
| The alt block | A dotted frame with an `alt` tab. The conditions sit in brackets at the center, over the lifeline of App. | A rounded frame with an `alt` chip and an `else` chip, each beside its condition, at the left. |
| Replies | Dashed lines with filled arrowheads. | Dashed lines with open arrowheads. |
| The note | A yellow box. | A tinted box with an accent bar. |

### An entity relationship diagram

Mermaid:

```mermaid-source
---
title: Orders
---
erDiagram
  CUSTOMER ||--o{ ORDER : places
  ORDER ||--|{ LINE_ITEM : contains
  CUSTOMER {
    int id PK
    string email UK
    string name
  }
  ORDER {
    int id PK
    int customer_id FK
    date placed_at
  }
  LINE_ITEM {
    int order_id FK
    string sku
    int quantity
  }
```

Mermaid draws it like this:

*Picture: How Mermaid draws the orders: a title and three tables of type, name, and key columns, stacked in one column and joined by crow's foot lines with a label on each.*

The many side of each relationship holds the foreign key, and the converter matched each one to a column that was already declared as `FK`, so it added none. It reports that the "one or more" minimum on `contains` is not drawn. The Plotwork:

**Diagram source**

```diagram
schema "Orders"

table CUSTOMER {
  id int pk
  email string unique
  name string
}

table ORDER {
  id int pk
  customer_id int fk
  placed_at date
}

table LINE_ITEM {
  order_id int fk
  sku string
  quantity int
}

ORDER.customer_id -> CUSTOMER.id : "places"
LINE_ITEM.order_id -> ORDER.id : "contains"
```

*Picture: Orders: 3 tables, 2 relations.*

**Text description**

**Tables:**

- CUSTOMER
  - id int (primary key)
  - email string (unique)
  - name string
- ORDER
  - id int (primary key)
  - customer_id int (foreign key)
  - placed_at date
- LINE_ITEM
  - order_id int (foreign key)
  - sku string
  - quantity int

**Relations:**

- ORDER.customer_id references CUSTOMER.id (many to one): places
- LINE_ITEM.order_id references ORDER.id (many to one): contains

What differs between the two pictures:

| What | Mermaid | Plotwork |
|---|---|---|
| A table | Three columns: the type, the name, and the key. | A card with an icon in its header. The key is a badge before the name, and the type sits at the right edge. |
| The unique key | Written `UK` in the source and drawn `UK`. | The converter turns `UK` into the flag `unique`, which is drawn as `UQ`. |
| The ends of a relationship | Each end says its minimum and its maximum: a double bar for exactly one, a circle and a crow's foot for zero or more. | A tick for the one side and a crow's foot for the many side. The "one or more" minimum on `contains` is not drawn, and the converter reports that. |
| The label | In a box on the line. | In a pill on the line. |
| Layout | The three tables stack in one column. | The three tables sit in a row. |

## Converting a documentation folder safely

Go one file at a time, and read before you write. Ask your agent to run the steps, or make the calls yourself.

| Step | Call | Why |
|---|---|---|
| 1. Dry run | `sync_markdown` with the document, its path, `convertMermaid: true`, and `mode: "check"` | Reports each Mermaid fence and changes nothing. Repeat for every Markdown file. |
| 2. Read the report | | Each fence has a status and its diagnostics. A `failed` fence could not be converted and is left exactly as it was. |
| 3. Fix or accept | | Fences that fail stay as Mermaid. To convert a diagram that has a line that cannot be read, call `convert_mermaid` on it with `lenient: true`, which skips the line with a warning. |
| 4. Write | `sync_markdown` again, with `mode: "sync"` | Replaces each convertible fence with a `diagram` fence and returns the picture files and blocks. Everything else in the file is unchanged, byte for byte. Save the `markdown` over the file and each entry of `files` at its path. |
| 5. Review | `git diff` | Read every changed diagram before it is committed. |

To convert a whole folder, ask the agent to call `sync_markdown` on each file. Keep the dry run first for every file.

For a single diagram, call `convert_mermaid` with the Mermaid text. It returns the Plotwork source and the diagnostics as data, and you decide where to save the source.

## FAQ

| Question | Answer |
|---|---|
| Does it handle my theme and colours? | Only as meaning. A class or style fill becomes the nearest tone, reported with `M204`, and the theme draws that tone in light and dark. Themes, strokes, text colours, and a fill with no tone are dropped with a diagnostic. |
| What about links and click actions? | Dropped with a diagnostic. A diagram is a static picture. Link to the page in the text around it. |
| Can I convert a whole repository? | Yes. Ask the agent to call `sync_markdown` on each Markdown file. Dry run first. |
| How do I review the result? | Read the diagnostics, then the outline (`outline` in the result), then the picture, then the git diff. |
| Why is a node a plain step? | Nothing in the Mermaid says what it is. Choose a type: database, service, queue, person, gateway, function, external. |
| Why did an edge move? | Plotwork edges connect nodes, so an edge into a subgraph goes to a node inside it. A diagnostic names the node. |
| What if one line cannot be read? | By default nothing is converted, so a diagram never loses a line unnoticed. The error names the line. Set `lenient: true` on `convert_mermaid` to skip such lines with a warning and convert the rest, then add what was skipped by hand. |
| Can I keep some diagrams as Mermaid? | Yes. A fence that is not converted stays as it was, and GitHub keeps drawing it. |
| Does the converter call a model or the network? | No. It is plain code. It does not fetch URLs, read files, or run anything from the diagram. |
| What are the size limits? | 50,000 characters, 300 nodes, 600 edges, and 24 levels of nesting for one diagram. Larger input comes back as an error that says to split it. |

## See also

| Doc | Why |
|---|---|
| [Writing diagrams](https://docs.plotwork.dev/writing-diagrams.md) | The Plotwork language by example |
| [Writing diagrams with an agent](https://docs.plotwork.dev/mcp/agent-loop.md) | The validate and fix loop, and the one-call workflows |
| [Diagrams in Markdown and on GitHub](https://docs.plotwork.dev/markdown-and-github.md) | How a fence becomes a picture |
| [Tool reference](https://docs.plotwork.dev/mcp/tools.md) | `convert_mermaid`, `sync_markdown`, and the other tools |
| [Language reference](https://docs.plotwork.dev/reference/language.md) | Every word the language accepts, and every diagnostic code |
