agentic-mermaid 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT_NATIVE.md +671 -0
- package/CHANGELOG.md +19 -0
- package/Instructions_for_agents.md +89 -0
- package/README.md +8 -8
- package/assets/hero.png +0 -0
- package/dist/agent-core.d.ts +2 -2
- package/dist/agent-core.js +2 -2
- package/dist/agent.d.ts +2 -2
- package/dist/agent.js +4 -4
- package/dist/agentic-mermaid-mcp.js +5 -5
- package/dist/am.js +8 -15
- package/dist/{chunk-QKDLMOJJ.js → chunk-2P5FKUQC.js} +7 -9
- package/dist/{chunk-WN3YDOBG.js → chunk-2S3VSS6I.js} +4 -4
- package/dist/{chunk-CDPKJ2BQ.js → chunk-2SILJDW2.js} +2 -2
- package/dist/{chunk-ZYMMKQ3I.js → chunk-BJYMBT4I.js} +299 -973
- package/dist/{chunk-WI35L7IR.js → chunk-FDM43XU7.js} +2 -2
- package/dist/{chunk-4QECACIA.js → chunk-MYPDVZCO.js} +19 -19
- package/dist/{index-D54dqSdC.d.ts → index-DWPa2MJs.d.ts} +7 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp-cli-5J47WEY3.js +17 -0
- package/docs/README.md +34 -34
- package/docs/agent-api-cookbook.md +317 -0
- package/docs/agent-mutation-policy.md +104 -0
- package/docs/agent-workflow-examples.md +46 -0
- package/docs/api.md +418 -0
- package/docs/ascii.md +113 -0
- package/docs/code-mode-impact.md +214 -0
- package/docs/comparison.md +106 -0
- package/docs/computational-aesthetics-prototype-plan.md +252 -0
- package/docs/config.md +121 -0
- package/docs/custom-fonts.md +265 -0
- package/docs/custom-style-cookbook.md +275 -0
- package/docs/diagram-families.md +218 -0
- package/docs/features.md +154 -0
- package/docs/fork-differences.md +133 -0
- package/docs/getting-started.md +182 -0
- package/docs/issue-derived-test-cases.md +163 -0
- package/docs/mcp-code-mode-rationale.md +108 -0
- package/docs/mcp-http-transport.md +230 -0
- package/docs/mermaid-layout-complaints.md +851 -0
- package/docs/mutation-testing.md +285 -0
- package/docs/quality.md +224 -0
- package/docs/react.md +97 -0
- package/docs/style-authoring.md +336 -0
- package/docs/svg-semantic-contract.md +49 -0
- package/docs/testing-strategy.md +443 -0
- package/docs/theming.md +149 -0
- package/package.json +21 -6
- package/server.json +2 -2
- package/skills/agentic-mermaid-live-editor/SKILL.md +1 -1
- package/dist/mcp-cli-SBQSTJFC.js +0 -17
|
@@ -0,0 +1,851 @@
|
|
|
1
|
+
# Mermaid layout complaints: catalog, root causes, and coverage
|
|
2
|
+
|
|
3
|
+
Status: evidence and capability-coverage scorecard, not a roadmap or backlog.
|
|
4
|
+
`TODO.md` is the sole authority for scheduled work. Historical issues and specs
|
|
5
|
+
below retain candidate contract evidence only; they do not schedule
|
|
6
|
+
implementation unless a stable root TODO item promotes them.
|
|
7
|
+
|
|
8
|
+
This document maps what users actually complain about in Mermaid's layout →
|
|
9
|
+
why those failures happen → which Agentic Mermaid work addresses each one
|
|
10
|
+
(landed, retained as candidate evidence, or deliberately out of scope). It is
|
|
11
|
+
the durable version of a June 2026 research pass and should be updated when
|
|
12
|
+
complaint-relevant work lands.
|
|
13
|
+
|
|
14
|
+
Companions:
|
|
15
|
+
|
|
16
|
+
- [`issue-derived-test-cases.md`](./issue-derived-test-cases.md) — fixture-level
|
|
17
|
+
coverage map for specific upstream issues.
|
|
18
|
+
- [`quality.md`](./quality.md) — the project's definition of "good looking" and
|
|
19
|
+
its metric gates.
|
|
20
|
+
- [Issue #25](https://github.com/adewale/agentic-mermaid/issues/25) and
|
|
21
|
+
[issue #26](https://github.com/adewale/agentic-mermaid/issues/26) — the
|
|
22
|
+
principled routing/contract specs that several entries below point into.
|
|
23
|
+
- [PR #24](https://github.com/adewale/agentic-mermaid/pull/24) — the Gantt
|
|
24
|
+
spec and implementation, the model for chart-family layout contracts.
|
|
25
|
+
- [Issue #41](https://github.com/adewale/agentic-mermaid/issues/41) — the
|
|
26
|
+
diagram-family citizenship matrix that now keeps supported families honest
|
|
27
|
+
about parser, serializer, renderer, verifier, and test obligations.
|
|
28
|
+
|
|
29
|
+
Method: complaint evidence was gathered in June 2026 from the
|
|
30
|
+
`mermaid-js/mermaid` tracker (reaction counts "R" are as of that date), Hacker
|
|
31
|
+
News, Reddit, comparison blogs, and Mermaid's own documentation. Root-cause
|
|
32
|
+
evidence comes from the graph-drawing literature, competing products'
|
|
33
|
+
documentation, diagram interchange standards, and the recorded history of the
|
|
34
|
+
three codebases this project descends from (Mermaid, `mermaid-ascii`,
|
|
35
|
+
`beautiful-mermaid`).
|
|
36
|
+
|
|
37
|
+
Status legend used below:
|
|
38
|
+
|
|
39
|
+
| Status | Meaning |
|
|
40
|
+
|---|---|
|
|
41
|
+
| **landed** | shipped in this repo with tests |
|
|
42
|
+
| **partial** | some of the complaint class is covered, the rest is not |
|
|
43
|
+
| **candidate evidence** | described in a historical issue/spec, but unscheduled unless promoted to root `TODO.md` |
|
|
44
|
+
| **out of scope** | deliberately not addressed; the reason is stated |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Part 1 — The complaint catalog
|
|
49
|
+
|
|
50
|
+
Thirteen recurring complaint classes, ordered roughly by loudness. Each entry
|
|
51
|
+
names its root causes (`R1`–`R5`, defined in Part 2) and its fixture/test
|
|
52
|
+
coverage in this repo.
|
|
53
|
+
|
|
54
|
+
### C1. No manual positioning
|
|
55
|
+
|
|
56
|
+
Mermaid is auto-layout only: no coordinates, no pinning, no post-hoc nudge.
|
|
57
|
+
When the engine makes a bad call, the sanctioned workaround is invisible
|
|
58
|
+
`~~~` links — Mermaid's own docs describe them as the way "to alter the
|
|
59
|
+
default positioning of a node," and GitLab maintains a handbook page of such
|
|
60
|
+
tricks. This is the loudest and oldest theme.
|
|
61
|
+
|
|
62
|
+
- Evidence: [mermaid#270](https://github.com/mermaid-js/mermaid/issues/270)
|
|
63
|
+
(11R, open since 2016),
|
|
64
|
+
[mermaid#2483](https://github.com/mermaid-js/mermaid/issues/2483) (12R),
|
|
65
|
+
[mermaid#5420 "Add positioning for elk layout renderer"](https://github.com/mermaid-js/mermaid/issues/5420)
|
|
66
|
+
(47R, zero comments — pure demand). HN: "if you don't like the way the
|
|
67
|
+
diagram is visually laid out there's not really an easy way to change it"
|
|
68
|
+
([41262756](https://news.ycombinator.com/item?id=41262756)).
|
|
69
|
+
- Root cause: R3 (missing control surface).
|
|
70
|
+
- Status here: **manual coordinates and post-hoc nudging remain out of scope.**
|
|
71
|
+
`BUILD-24` now owns a narrower response: typed, deterministic structural
|
|
72
|
+
constraints for rank/layer, adjacency, and edge-length preference. They are
|
|
73
|
+
render/mutation metadata, not Agentic-only Mermaid syntax, and therefore do
|
|
74
|
+
not contradict #25's rule against inventing route syntax. Deterministic
|
|
75
|
+
quality measurement and route certificates remain the feedback loop; the
|
|
76
|
+
hints let an agent state topology-preserving intent without deleting a real
|
|
77
|
+
edge. This still does not provide arbitrary node coordinates or dragging.
|
|
78
|
+
|
|
79
|
+
### C2. Layout collapses at scale
|
|
80
|
+
|
|
81
|
+
Past roughly 20–50 nodes: whitespace oceans, extreme aspect ratios, nodes
|
|
82
|
+
unreadably small once the diagram is fit to screen.
|
|
83
|
+
|
|
84
|
+
- Evidence: [mermaid#1984 "Massive whitespace"](https://github.com/mermaid-js/mermaid/issues/1984)
|
|
85
|
+
(39R, open since 2021),
|
|
86
|
+
[mermaid#3262](https://github.com/mermaid-js/mermaid/issues/3262),
|
|
87
|
+
[mermaid#2807 "ER graphs tend to get REALLY wide"](https://github.com/mermaid-js/mermaid/issues/2807)
|
|
88
|
+
(19R). HN: "more and more acres of white space"
|
|
89
|
+
([30781954](https://news.ycombinator.com/item?id=30781954)).
|
|
90
|
+
- Root cause: R1 (layered-algorithm sprawl), aggravated in Mermaid by R4
|
|
91
|
+
(dagre, fixed spacing constants).
|
|
92
|
+
- Fixture/test: `whitespaceBalance` (5–55% band) and `aspectRatio` (0.2–5.0
|
|
93
|
+
band) in `src/agent/quality.ts` flag both failure directions;
|
|
94
|
+
`eval/layout-compare/` reports deltas corpus-wide. Generated 20/50/100-node
|
|
95
|
+
flowchart corpora now assert finite aspect ratios and non-degenerate
|
|
96
|
+
whitespace in `src/__tests__/agent-quality.test.ts`.
|
|
97
|
+
- Status here: **partial — detection only.** Nothing in the backlog improves
|
|
98
|
+
large-graph compaction, and the source-order priority can *worsen* width:
|
|
99
|
+
`docs/quality.md` documents the Auth Flow trade, and
|
|
100
|
+
`src/__tests__/agent-auth-flow.test.ts` pins quality with a widened aspect
|
|
101
|
+
band (`[0.2, 7]`) for exactly that reason. One candidate mitigation that
|
|
102
|
+
needs no syntax invention: the standards-era answer to oversized charts
|
|
103
|
+
was never better compaction but **decomposition** — ANSI X3.5/ISO 5807
|
|
104
|
+
prescribe named on-page/off-page connectors to split a chart when it
|
|
105
|
+
outgrows a page or lines would cross. The agent-loop analogue: when
|
|
106
|
+
`checkQuality` flags an oversized diagram, propose or perform splitting
|
|
107
|
+
into multiple linked diagrams through the existing mutation API. Mermaid
|
|
108
|
+
has no continuation concept (users fake it with duplicated nodes), so
|
|
109
|
+
this would be a differentiator rather than a compatibility surface.
|
|
110
|
+
|
|
111
|
+
### C3. Subgraphs: direction ignored, titles overlapped, clusters colliding
|
|
112
|
+
|
|
113
|
+
The most-reacted pure layout bug in Mermaid's tracker. `direction` inside a
|
|
114
|
+
subgraph is silently ignored the moment any inner node links outside —
|
|
115
|
+
documented as a limitation, unfixed since 2021. Titles get covered by nodes;
|
|
116
|
+
sibling clusters overlap.
|
|
117
|
+
|
|
118
|
+
- Evidence: [mermaid#2509](https://github.com/mermaid-js/mermaid/issues/2509)
|
|
119
|
+
(189R, 50 comments),
|
|
120
|
+
[mermaid#3806 title overlap](https://github.com/mermaid-js/mermaid/issues/3806)
|
|
121
|
+
(54R), [mermaid#4990 cluster overlap](https://github.com/mermaid-js/mermaid/issues/4990)
|
|
122
|
+
(32R), [mermaid#287](https://github.com/mermaid-js/mermaid/issues/287) (29R).
|
|
123
|
+
- Root cause: R1 (compound layout is genuinely hard), R4 (dagre's cluster
|
|
124
|
+
support is weak; title boxes not reserved during layout).
|
|
125
|
+
- Fixture/test: `src/__tests__/subgraph-direction.test.ts` pins SVG geometry
|
|
126
|
+
for `direction TB` inside an LR flowchart *with an external link to an
|
|
127
|
+
inner node* — the exact mermaid#2509 case — plus ASCII one-row inner LR
|
|
128
|
+
layout, and container edges attaching to the subgraph border without
|
|
129
|
+
phantom node boxes (BUILD-14), in both Unicode and 7-bit ASCII, nested,
|
|
130
|
+
labeled/styled, and multi-edge variants. Group titles reserve layout space
|
|
131
|
+
via the header-aware `elk.padding` in `src/layout-engine.ts`; breaches are
|
|
132
|
+
a Tier 1 `GROUP_BREACH` warning.
|
|
133
|
+
- Status here: **landed** (PRs #17, #21, #22). Residual: cross-hierarchy and
|
|
134
|
+
container-edge *route classes* — making the SVG router treat boundary
|
|
135
|
+
edges semantically rather than via representative children — remain candidate
|
|
136
|
+
contract evidence in #25 §8.3 and #26 workstream 2, not scheduled work.
|
|
137
|
+
|
|
138
|
+
### C4. Erratic edge routing: avoidable crossings, overlaps, doglegs
|
|
139
|
+
|
|
140
|
+
Here and in the scorecard, **C4 means complaint category 4**, not Mermaid's C4
|
|
141
|
+
diagram family.
|
|
142
|
+
|
|
143
|
+
Edges cross when they obviously need not, overlap each other or pass through
|
|
144
|
+
nodes, and acquire small unexplained doglegs ("hitches"). Users untangle by
|
|
145
|
+
trial-and-error reordering of source lines.
|
|
146
|
+
|
|
147
|
+
- Evidence: [mermaid#6476 "Unnecessary crossing"](https://github.com/mermaid-js/mermaid/issues/6476),
|
|
148
|
+
[mermaid#5601 planar graphs rendered non-planar](https://github.com/mermaid-js/mermaid/issues/5601),
|
|
149
|
+
[mermaid#1006](https://github.com/mermaid-js/mermaid/issues/1006) ("it often
|
|
150
|
+
really handles edges in an erradic way" — closed *Wont Fix*),
|
|
151
|
+
[mermaid#5060](https://github.com/mermaid-js/mermaid/issues/5060),
|
|
152
|
+
[mermaid#2792 edges overlap boxes](https://github.com/mermaid-js/mermaid/issues/2792).
|
|
153
|
+
- Root cause: R1 (crossing minimization is NP-hard, solved per-layer by
|
|
154
|
+
heuristic; routing is a late phase that cannot revisit placement), R4
|
|
155
|
+
(dagre routes splines without obstacle avoidance), R5 (post-layout passes
|
|
156
|
+
move geometry without rerouting).
|
|
157
|
+
- Fixture/test: the worked example is issue #25's login/MFA flow. Rendered
|
|
158
|
+
with the current engine (June 2026, ASCII):
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
| User |---->| Login Page |<No->| Valid Credentials? |-Yes>| MFA Enabled? |----No-->| Create Session | Yes-+--| Code Valid? |
|
|
162
|
+
... | +-----No-------+ |
|
|
163
|
+
... +------Yes------>| Enter MFA Code |----------------+
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`F --> G` (`Code Valid?` → `Create Session`) runs backwards; the `No`
|
|
167
|
+
feedback loops read ambiguously inline. Yet `verifyMermaid` returns
|
|
168
|
+
`ok: true` with zero warnings, and every perceptual metric passes except
|
|
169
|
+
aspect ratio (6.23 vs the 5.0 band): `edgeCrossings: 0`,
|
|
170
|
+
`labelLegibility: 1`. That is the measured gap between today's generic
|
|
171
|
+
metrics and the visible failure — the gap #25/#26 exist to close.
|
|
172
|
+
Current coverage: `edgeCrossings` gate (≤5% of edge pairs),
|
|
173
|
+
`ROUTE_SELF_CROSS` Tier 2 warning, edge-vs-node collision heuristics in
|
|
174
|
+
`src/__tests__/layout-quality-heuristics.test.ts`, deterministic FIFO A*
|
|
175
|
+
tie-breaking in the ASCII pathfinder
|
|
176
|
+
(`src/__tests__/ascii-pathfinder-determinism.test.ts`).
|
|
177
|
+
- Status here: **partial today, with the route-contract core landed and the
|
|
178
|
+
broader program retained as candidate evidence.** #25 defines route classes
|
|
179
|
+
(`primary-forward`/`feedback`/fan trunks/…), the straight-primary and
|
|
180
|
+
hitch invariants (§11.1–11.2), the certifying simplifier (§12), and the
|
|
181
|
+
hard rule "no node-coordinate mutation after route extraction unless all
|
|
182
|
+
incident routes are rerouted or recertified" (§9). The current tests now
|
|
183
|
+
include route-certificate geometry and property coverage across dotted,
|
|
184
|
+
thick, bidirectional, marker, and labeled edge forms. The still-open
|
|
185
|
+
route/port issues (#32, #35, #38 and the #26 umbrella) retain unscheduled
|
|
186
|
+
evidence; any implementation or cross-renderer adoption requires promotion
|
|
187
|
+
to root `TODO.md`.
|
|
188
|
+
|
|
189
|
+
### C5. Edge labels overlap edges, nodes, and each other
|
|
190
|
+
|
|
191
|
+
Mermaid places labels at edge midpoints with no collision handling: labels
|
|
192
|
+
sit on other edges, get struck through by their own line, or detach from the
|
|
193
|
+
edge they describe.
|
|
194
|
+
|
|
195
|
+
- Evidence: [mermaid#490](https://github.com/mermaid-js/mermaid/issues/490)
|
|
196
|
+
(2017 — labels struck through by their own edge, asking for "integrated
|
|
197
|
+
logic which tries to avoid lines going over the texts": literally the
|
|
198
|
+
integrated-labeling concept yFiles ships, see R3),
|
|
199
|
+
[mermaid#2131 mis-aligned state labels](https://github.com/mermaid-js/mermaid/issues/2131),
|
|
200
|
+
[mermaid#2525 "messages appear with line through them"](https://github.com/mermaid-js/mermaid/issues/2525),
|
|
201
|
+
[mermaid#1233](https://github.com/mermaid-js/mermaid/issues/1233).
|
|
202
|
+
- Root cause: R1 (label placement is NP-hard and classically solved *after*
|
|
203
|
+
layout; dagre models labels as mid-edge dummy nodes), R4/R5 (renderers do
|
|
204
|
+
not collision-check label boxes afterwards).
|
|
205
|
+
- Fixture/test: this engine passes edge labels into layout as first-class
|
|
206
|
+
inline ELK labels (`elk.edgeLabels.inline: 'true'`,
|
|
207
|
+
`src/layout-engine.ts`), so they reserve space instead of being overlaid;
|
|
208
|
+
`labelEdgeProximity` (≥4px to any non-attached node, other edge-label
|
|
209
|
+
box, or other edge path) gates regressions in the graph families, and the
|
|
210
|
+
2026-07 overlap audit added a cross-family occlusion gate
|
|
211
|
+
(`eval/overlap-audit` + `label-overlap-gate.test.ts`: curated corpus at
|
|
212
|
+
zero label/box overlap findings, per-family fuzz ratchets) after finding
|
|
213
|
+
label collisions the proximity metric alone had missed in five families.
|
|
214
|
+
ASCII branch labels are pinned to sibling branch segments, not shared
|
|
215
|
+
trunks: `src/__tests__/ascii-fanout-trunk-labeled.test.ts`,
|
|
216
|
+
`ascii-pathfinder-trunk.test.ts`, plus exact goldens under
|
|
217
|
+
`src/__tests__/testdata/{ascii,unicode}/`.
|
|
218
|
+
- Status here: **partial.** Landed: label-aware layout + proximity gate +
|
|
219
|
+
ASCII branch-label placement. Specced: the label invariant (#25 §11.4 —
|
|
220
|
+
labels are obstacles *and* capacity constraints; trunks may not host
|
|
221
|
+
per-sibling labels) and label-capacity checks before straightening (§12).
|
|
222
|
+
|
|
223
|
+
### C6. No rank/order control; tiny edits reshuffle the diagram
|
|
224
|
+
|
|
225
|
+
Users cannot say "these two nodes at the same level" or "keep my declared
|
|
226
|
+
order," and adding one edge can scramble a previously good layout — which
|
|
227
|
+
also makes diagrams churn across edits.
|
|
228
|
+
|
|
229
|
+
- Evidence: [mermaid#3723 same-rank request](https://github.com/mermaid-js/mermaid/issues/3723)
|
|
230
|
+
(86R — the most-upvoted open layout-control ask),
|
|
231
|
+
[mermaid#815 "Maintain the order of the nodes"](https://github.com/mermaid-js/mermaid/issues/815)
|
|
232
|
+
(45R), [mermaid#2834](https://github.com/mermaid-js/mermaid/issues/2834),
|
|
233
|
+
[mermaid#6527 "flowchart-elk … reshuffled every time you add a node"](https://github.com/mermaid-js/mermaid/issues/6527).
|
|
234
|
+
- Root cause: R3 (no constraint surface) over R1 (layered layouts are
|
|
235
|
+
input-order sensitive unless order is made an explicit objective).
|
|
236
|
+
- Fixture/test: source order *is* the constraint surface here:
|
|
237
|
+
`elk.layered.considerModelOrder.strategy: NODES_AND_EDGES`,
|
|
238
|
+
`cycleBreaking.strategy: MODEL_ORDER`, and
|
|
239
|
+
`crossingMinimization.forceNodeModelOrder: true`
|
|
240
|
+
(`src/layout-engine.ts`) make author order the layout's primary input.
|
|
241
|
+
`src/__tests__/agent-auth-flow.test.ts` asserts the primary LR chain stays
|
|
242
|
+
in source order (`A < B < … < H` by x-center) and feedback edges route
|
|
243
|
+
backwards — the mermaid#5227 "backwards arrows" class. Edit stability is
|
|
244
|
+
the determinism guarantee plus typed mutation: a one-op change re-lays-out
|
|
245
|
+
as a pure function of the new source
|
|
246
|
+
(`src/__tests__/agent-determinism.test.ts`). The same file now pins
|
|
247
|
+
edit-stability for small source mutations: label edits, inserted nodes,
|
|
248
|
+
appended leaves, style-only changes, and feedback-edge additions must
|
|
249
|
+
preserve the left-to-right order of the unchanged primary chain.
|
|
250
|
+
- Status here: **partial.** Landed: deterministic source-order layout (the
|
|
251
|
+
practical answer to #815/#6527). Not offered: an explicit same-rank
|
|
252
|
+
constraint (#3723's literal ask) — that is C1's policy decision again.
|
|
253
|
+
Graphviz has had `rank=same` since the 1990s (see R3); if Mermaid core
|
|
254
|
+
ever standardizes an equivalent, this fork's policy is to follow, not
|
|
255
|
+
invent.
|
|
256
|
+
|
|
257
|
+
### C7. "It's just ugly": dated default aesthetics
|
|
258
|
+
|
|
259
|
+
An aesthetic judgment independent of geometric correctness: default colors,
|
|
260
|
+
shapes, curves, and contrast read as dated next to D2 or hand-drawn
|
|
261
|
+
diagrams; some embeddings are unreadable (dark-theme contrast).
|
|
262
|
+
|
|
263
|
+
- Evidence: HN "why does it have to be so ugly"
|
|
264
|
+
([30339032](https://news.ycombinator.com/item?id=30339032)); "Both PlantUML
|
|
265
|
+
and Mermaid mostly produce (Mermaid more) ugly-looking diagrams"
|
|
266
|
+
([33704875](https://news.ycombinator.com/item?id=33704875));
|
|
267
|
+
[Cursor forum on unreadable contrast](https://forum.cursor.com/t/mermaid-diagrams-are-hard-to-read-poor-contrast/153850).
|
|
268
|
+
- Root cause: R5 (renderer/theming defaults, not the layout algorithm).
|
|
269
|
+
Mermaid's v11 `handDrawn`/`neo` "looks" are its own remediation.
|
|
270
|
+
- Fixture/test: inherited from upstream Beautiful Mermaid: two-color theme
|
|
271
|
+
derivation, 21 built-in themes, Shiki/VS Code theme compatibility. Fork
|
|
272
|
+
additions: Style + Palette stacks, Tufte/Salmon theme families (PR #10),
|
|
273
|
+
auto-contrast on custom fills
|
|
274
|
+
(upstream #115 class) pinned by
|
|
275
|
+
`src/__tests__/renderer-contrast.test.ts`.
|
|
276
|
+
- Status here: **landed** (largely upstream's credit; see
|
|
277
|
+
[`comparison.md`](./comparison.md)).
|
|
278
|
+
|
|
279
|
+
### C8. Text clipped, overflowing, or hand-wrapped
|
|
280
|
+
|
|
281
|
+
Node boxes don't reliably fit their text. In Mermaid the classic causes are
|
|
282
|
+
HTML labels in `foreignObject` (SVG exports drop them), browser font metrics
|
|
283
|
+
at layout time, and no width budget for CJK/long words.
|
|
284
|
+
|
|
285
|
+
- Evidence: [mermaid#58](https://github.com/mermaid-js/mermaid/issues/58)
|
|
286
|
+
("Generated SVG works poorly outside web browsers" — opened December
|
|
287
|
+
2014, six weeks after Mermaid's first release, still open),
|
|
288
|
+
[mermaid#2688 foreignObject](https://github.com/mermaid-js/mermaid/issues/2688)
|
|
289
|
+
(24R), [mermaid#790](https://github.com/mermaid-js/mermaid/issues/790),
|
|
290
|
+
[mermaid#4918](https://github.com/mermaid-js/mermaid/issues/4918),
|
|
291
|
+
[mermaid#7359 Korean clipped on export](https://github.com/mermaid-js/mermaid/issues/7359);
|
|
292
|
+
GitLab converted diagrams to tables rather than fight it
|
|
293
|
+
([gitlab#583608](https://gitlab.com/gitlab-org/gitlab/-/issues/583608)).
|
|
294
|
+
- Root cause: R5 (browser-first text pipeline), R2 for the ASCII lineage
|
|
295
|
+
(`string.length` width assumptions on fullwidth text).
|
|
296
|
+
- Fixture/test: structurally avoided here — text is plain SVG `<text>`
|
|
297
|
+
measured by deterministic metrics (`src/text-metrics.ts`), never
|
|
298
|
+
`foreignObject`; PNG embeds pinned fonts (DejaVu default plus the built-in style faces) so rasterization cannot
|
|
299
|
+
font-substitute. Guards: Tier 1 `LABEL_OVERFLOW`, `labelLegibility` ≥85%
|
|
300
|
+
gate, CJK fullwidth handling
|
|
301
|
+
(`src/__tests__/ascii-cjk-width.test.ts`, upstream #119 class), ER/class
|
|
302
|
+
label truncation fixes with exact goldens (upstream #121 class,
|
|
303
|
+
`er-integration.test.ts`, `class-integration.test.ts`).
|
|
304
|
+
- Status here: **landed**, with an honest gap: the 7px/char SVG heuristic is
|
|
305
|
+
approximate (`docs/quality.md`), and the shared text-measurement contract
|
|
306
|
+
that would unify SVG/ASCII/quality measurement (CJK, emoji, ambiguous
|
|
307
|
+
width) remains candidate evidence in #26 workstream 8. Regression coverage
|
|
308
|
+
now includes
|
|
309
|
+
emoji-presentation selectors and East Asian ambiguous-width symbols, so
|
|
310
|
+
common Unicode labels cannot silently drift between SVG metrics and
|
|
311
|
+
terminal-width assumptions.
|
|
312
|
+
|
|
313
|
+
### C9. Stuck on dagre; ELK bolted on as an escape hatch
|
|
314
|
+
|
|
315
|
+
The meta-complaint: Mermaid's default engine is dagre — widely described as
|
|
316
|
+
abandoned — and the community's fix (ELK) is opt-in, a separate package,
|
|
317
|
+
unavailable in GitHub's native rendering, and not itself a cure-all.
|
|
318
|
+
|
|
319
|
+
- Evidence: "Dagre is pretty good but I think ELK is better and Dagre has
|
|
320
|
+
been abandoned"
|
|
321
|
+
([r/programming](https://www.reddit.com/r/programming/comments/z1qoxc/d2_is_now_open_source_a_new_modern_language_that/ixlwqce/));
|
|
322
|
+
Mermaid docs: "The elk renderer is better for larger and/or more complex
|
|
323
|
+
diagrams" ([flowchart docs](https://mermaid.js.org/syntax/flowchart.html));
|
|
324
|
+
HN: "the layout renderer is pretty bad … the layout algorithm is so bad"
|
|
325
|
+
([41277795](https://news.ycombinator.com/item?id=41277795)).
|
|
326
|
+
- Root cause: R4.
|
|
327
|
+
- Status here: **landed structurally.** ELK layered is this engine's only
|
|
328
|
+
graph layout (synchronous bundled `elkjs`, `src/elk-instance.ts`), with
|
|
329
|
+
pinned options (`src/layout-engine.ts`) and #25 §10 as the documented
|
|
330
|
+
option policy — node placement is Brandes–Köpf, the same default
|
|
331
|
+
Mermaid's own ELK package exposes via `nodePlacementStrategy`, so
|
|
332
|
+
opting into `flowchart-elk` in Mermaid lands on the placement family this
|
|
333
|
+
engine uses by default. Two honest caveats: (1) ELK is not a panacea — upstream
|
|
334
|
+
#83 (TD flowcharts flipping horizontal) was *ELK cycle-breaking* behavior,
|
|
335
|
+
countered here by model-order options; (2) none of this changes what
|
|
336
|
+
GitHub/GitLab render natively (see Part 4).
|
|
337
|
+
|
|
338
|
+
### C10. Self-loops and back-edges render awkwardly
|
|
339
|
+
|
|
340
|
+
A node linking to itself draws a kinked doubled-back path; loops in flows
|
|
341
|
+
take long unexplained detours.
|
|
342
|
+
|
|
343
|
+
- Evidence: [mermaid#6336](https://github.com/mermaid-js/mermaid/issues/6336)
|
|
344
|
+
(31R; the issue contrasts Graphviz doing it "So much more natural"),
|
|
345
|
+
[mermaid#6049](https://github.com/mermaid-js/mermaid/issues/6049) (11R —
|
|
346
|
+
also a version regression: "pretty" in 11.0.2, "Ugly since 11.1.1").
|
|
347
|
+
- Root cause: R1/R4 (layered frameworks have no native self-loop concept;
|
|
348
|
+
the route is synthesized late).
|
|
349
|
+
- Fixture/test: ASCII self-loops currently render compactly (verified June
|
|
350
|
+
2026: `B --> B` draws a tight two-cell side loop). `ROUTE_SELF_CROSS`
|
|
351
|
+
(Tier 2) and self-loop clearance heuristics
|
|
352
|
+
(`layout-quality-heuristics.test.ts`) guard regressions. SVG self-loop
|
|
353
|
+
port policy (N/S side by clearance) is #25 §8.1; mermaid#6049 is listed as
|
|
354
|
+
candidate fixture evidence in [`issue-derived-test-cases.md`](./issue-derived-test-cases.md).
|
|
355
|
+
- Status here: **partial** (ASCII decent + guarded; SVG semantics retained as
|
|
356
|
+
candidate evidence, not scheduled).
|
|
357
|
+
|
|
358
|
+
### C11. Unstable output across versions and renderers
|
|
359
|
+
|
|
360
|
+
The same source renders differently in the live editor, CLI, GitHub, and
|
|
361
|
+
Obsidian, and upgrades silently regress committed diagrams.
|
|
362
|
+
|
|
363
|
+
- Evidence: [mermaid#5813 v11 regression](https://github.com/mermaid-js/mermaid/issues/5813)
|
|
364
|
+
(28R), [mermaid#1485 live editor vs CLI](https://github.com/mermaid-js/mermaid/issues/1485),
|
|
365
|
+
[mermaid#5969 editor silently uses ELK](https://github.com/mermaid-js/mermaid/issues/5969),
|
|
366
|
+
[gitlab#583608](https://gitlab.com/gitlab-org/gitlab/-/issues/583608),
|
|
367
|
+
[Obsidian forum "Mermaid arrows are a mess"](https://forum.obsidian.md/t/mermaid-arrows-are-a-mess/104633).
|
|
368
|
+
- Root cause: R5 (browser/CSS/version-coupled rendering) plus R1 (layout
|
|
369
|
+
sensitivity amplifies small engine changes into big visual diffs).
|
|
370
|
+
- Fixture/test: this is the fork's categorical answer rather than a
|
|
371
|
+
mitigation. Byte-identical output across runs, processes, and runtimes is
|
|
372
|
+
CI-gated (`agent-determinism.test.ts`); ASCII output is hash-stable across
|
|
373
|
+
the full 271-entry docs corpus (`ascii-determinism.test.ts`); PNG bytes
|
|
374
|
+
are pinned via exact-version resvg + bundled fonts
|
|
375
|
+
(`agent-png-determinism.test.ts`). Intentional layout changes must pass
|
|
376
|
+
the before/after comparison harness (`eval/layout-compare/`,
|
|
377
|
+
regressions-first verdicts) and update exact goldens — so every visual
|
|
378
|
+
change is deliberate, evidenced, and reviewed.
|
|
379
|
+
- Status here: **landed.** No Mermaid-syntax renderer we know of makes this
|
|
380
|
+
guarantee.
|
|
381
|
+
|
|
382
|
+
### C12. Sequence-diagram alignment and overlap
|
|
383
|
+
|
|
384
|
+
Sequence diagrams (bespoke layouter in Mermaid, not dagre) have their own
|
|
385
|
+
class: uncontrollable/overlapping activations, off-center `alt`/`loop`
|
|
386
|
+
titles, notes colliding with lifelines.
|
|
387
|
+
|
|
388
|
+
- Evidence: [mermaid#1765 activations](https://github.com/mermaid-js/mermaid/issues/1765)
|
|
389
|
+
(28R), [mermaid#3216](https://github.com/mermaid-js/mermaid/issues/3216),
|
|
390
|
+
[mermaid#7651](https://github.com/mermaid-js/mermaid/issues/7651),
|
|
391
|
+
[mermaid#7682](https://github.com/mermaid-js/mermaid/issues/7682).
|
|
392
|
+
- Root cause: R5 (hand-rolled per-family geometry, no shared invariants) +
|
|
393
|
+
R3 (no spacing overrides).
|
|
394
|
+
- Fixture/test: layout geometry tests exist
|
|
395
|
+
(`src/__tests__/sequence-layout.test.ts`, `ascii-sequence-blocks.test.ts`)
|
|
396
|
+
and the sequence `RenderedLayout` adapter feeds the generic metrics.
|
|
397
|
+
Sequence property coverage now checks activation spans, note stacking and
|
|
398
|
+
bounds, and block/divider containment/non-overlap geometry over generated
|
|
399
|
+
activation/note/block combinations. The broader productized verifier set
|
|
400
|
+
— lifeline x-order, message y-order, activation span containment, and
|
|
401
|
+
note/block containment as user-facing warnings — remains #25 §14.2 and
|
|
402
|
+
#26 workstream 11.
|
|
403
|
+
- Status here: **partial, with first family-specific properties landed.**
|
|
404
|
+
|
|
405
|
+
### C13. Chart families: hard-coded geometry breaks on real data
|
|
406
|
+
|
|
407
|
+
Gantt axis dates overlap on long ranges, pie titles/labels clip, and radar axis
|
|
408
|
+
labels compress. These are
|
|
409
|
+
not dagre problems — each chart type has bespoke geometry with no collision
|
|
410
|
+
handling.
|
|
411
|
+
|
|
412
|
+
- Evidence: [mermaid#1301 gantt axis overlap](https://github.com/mermaid-js/mermaid/issues/1301)
|
|
413
|
+
(23R), [mermaid#6232 pie title cut off](https://github.com/mermaid-js/mermaid/issues/6232),
|
|
414
|
+
and [mermaid#7683 radar labels clipped](https://github.com/mermaid-js/mermaid/issues/7683).
|
|
415
|
+
- Root cause: R5 (per-family renderer-first geometry; semantics inferred at
|
|
416
|
+
draw time — the exact anti-pattern #26's guiding principle names).
|
|
417
|
+
- Fixture/test: pie and quadrant landed source-level with geometry tests
|
|
418
|
+
(PR #22; quadrant point placement verified against the upstream Mermaid
|
|
419
|
+
reference). Gantt is now implemented from PR #24's principled model — a
|
|
420
|
+
pure, clock-free schedule resolver *before* layout, family validators
|
|
421
|
+
(deterministic task intervals, milestone zero-width markers,
|
|
422
|
+
non-overlapping compact rows) designed against precisely the mermaid#1301
|
|
423
|
+
class. Mindmap now has deterministic tree geometry; Radar has deterministic
|
|
424
|
+
radial layout, bounded labels, ring ticks, and cross-format render contracts.
|
|
425
|
+
- Status here: **covered for every registered chart family**; remaining work is
|
|
426
|
+
tracked as family-specific quality improvement, not missing Radar support.
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## Part 2 — Root causes
|
|
431
|
+
|
|
432
|
+
Five root causes explain the catalog. R1–R3 apply to every auto-layout
|
|
433
|
+
text-to-diagram tool, including this one; R4–R5 are specific lineages.
|
|
434
|
+
|
|
435
|
+
### R1. The layered framework solves NP-hard problems in irreversible phases
|
|
436
|
+
|
|
437
|
+
Every engine in this story — dagre, ELK, dot — descends from the layered
|
|
438
|
+
method of
|
|
439
|
+
[Sugiyama, Tagawa & Toda 1981](https://doi.org/10.1109/TSMC.1981.4308636)
|
|
440
|
+
(IEEE Trans. SMC-11(2)), extended since into the standard pipeline: break a
|
|
441
|
+
directed graph drawing into sequential phases (cycle removal → layer
|
|
442
|
+
assignment → crossing minimization → coordinate assignment → edge
|
|
443
|
+
routing). The framework is powerful and is the right baseline for flow-like
|
|
444
|
+
diagrams, but it has three structural consequences users experience
|
|
445
|
+
directly:
|
|
446
|
+
|
|
447
|
+
1. **The subproblems are intractable, so every phase is heuristic.**
|
|
448
|
+
Crossing number is NP-complete
|
|
449
|
+
([Garey & Johnson 1983](https://dl.acm.org/doi/10.1137/0604033), SIAM
|
|
450
|
+
J. Alg. Disc. Meth. 4(3)); crossing minimization stays NP-complete even
|
|
451
|
+
for two layers with one layer's order already fixed
|
|
452
|
+
([Eades & Wormald 1994](https://link.springer.com/article/10.1007/BF01187020),
|
|
453
|
+
Algorithmica 11(4)) — the exact subproblem inside every layered engine;
|
|
454
|
+
label placement is NP-complete even in simple variants
|
|
455
|
+
([Marks & Shieber 1991](https://dash.harvard.edu/entities/publication/73120379-247a-6bd4-e053-0100007fdf3b),
|
|
456
|
+
Harvard TR-05-91), and edge-label placement specifically is NP-hard
|
|
457
|
+
([Kakoulis & Tollis 2001](https://www.sciencedirect.com/science/article/pii/S0925772100000250),
|
|
458
|
+
Comput. Geom. 18(1)). No engine "chooses" the avoidable crossing in
|
|
459
|
+
complaint C4 — it fails to find the better optimum it cannot afford to
|
|
460
|
+
search for.
|
|
461
|
+
2. **Phases cannot revisit earlier decisions.** Layering fixes ranks before
|
|
462
|
+
crossing minimization sees them; coordinates are assigned before routing.
|
|
463
|
+
A route that needs a node nudged one lane over cannot get it. This is
|
|
464
|
+
the literature's own characterization of the framework's failure mode —
|
|
465
|
+
"decisions made at previous steps influence later steps and yet cannot
|
|
466
|
+
be undone"
|
|
467
|
+
([Healy & Nikolov, *Handbook of Graph Drawing and Visualization* ch. 13](https://cs.brown.edu/people/rtamassi/gdhandbook/chapters/hierarchical.pdf));
|
|
468
|
+
"since layer assignment and crossing reduction are realized as
|
|
469
|
+
independent steps, the resulting drawing might have many unnecessary
|
|
470
|
+
crossings caused by an unfortunate layer assignment"
|
|
471
|
+
([Chimani, Gutwenger, Mutzel & Wong, JGAA 15(1), 2011](https://jgaa.info/index.php/jgaa/article/view/paper220/2748)).
|
|
472
|
+
It is why locally-reasonable pipelines emit globally wrong results — the
|
|
473
|
+
"hitch" of issue #25 is a phase-boundary artifact, and #25 §2 names it a
|
|
474
|
+
*contract failure*: nothing in the pipeline encodes "this edge should be
|
|
475
|
+
straight when its lane is clear" as an invariant any phase must preserve.
|
|
476
|
+
3. **Labels and shapes are afterthoughts in the classic formulation.**
|
|
477
|
+
Labels enter as dummy nodes or post-hoc annotations (C5); non-rectangular
|
|
478
|
+
shapes are approximated by their bounding boxes until a final clipping
|
|
479
|
+
pass (#25 principle 6).
|
|
480
|
+
|
|
481
|
+
Empirical work confirms users are right to care:
|
|
482
|
+
[Purchase 1997](https://link.springer.com/chapter/10.1007/3-540-63938-1_67)
|
|
483
|
+
(Graph Drawing, LNCS 1353) found reducing edge crossings "by far the most
|
|
484
|
+
important" aesthetic for human comprehension, with bend minimization also
|
|
485
|
+
significant — exactly the two artifacts the phase structure produces.
|
|
486
|
+
Compound (subgraph) layout multiplies the difficulty: nested clusters with
|
|
487
|
+
cross-cluster edges constrain layering and crossing minimization
|
|
488
|
+
simultaneously
|
|
489
|
+
([Sander 1996](https://publikationen.sulb.uni-saarland.de/bitstream/20.500.11880/25862/1/tr-A03-96.pdf),
|
|
490
|
+
TR A/03/96, Universität des Saarlandes), which is why C3 is hard for
|
|
491
|
+
everyone — and weak cluster handling is dagre's most visible gap (open for
|
|
492
|
+
years in its own tracker:
|
|
493
|
+
[dagre#125](https://github.com/dagrejs/dagre/issues/125),
|
|
494
|
+
[dagre#238](https://github.com/dagrejs/dagre/issues/238)).
|
|
495
|
+
|
|
496
|
+
### R2. Greedy per-edge grid routing in the ASCII lineage
|
|
497
|
+
|
|
498
|
+
[`mermaid-ascii`](https://github.com/AlexanderGrooff/mermaid-ascii)
|
|
499
|
+
(Alexander Grooff, Go) works in three stages — parse, map components onto a
|
|
500
|
+
character grid, route each edge independently by pathfinding — and this
|
|
501
|
+
repo's ASCII engine is a TypeScript port of it
|
|
502
|
+
(`src/ascii/edge-routing.ts` is "ported from
|
|
503
|
+
AlexanderGrooff/mermaid-ascii cmd/direction.go + cmd/mapping_edge.go").
|
|
504
|
+
Per-edge greedy routing with no shared intent produces the ASCII-specific
|
|
505
|
+
complaint classes visible in both trackers: in the original,
|
|
506
|
+
single-edge-per-node-pair limits, floating arrowheads on labeled
|
|
507
|
+
back-edges, and East Asian width breakage
|
|
508
|
+
([mermaid-ascii#56](https://github.com/AlexanderGrooff/mermaid-ascii/issues/56),
|
|
509
|
+
[#63](https://github.com/AlexanderGrooff/mermaid-ascii/issues/63),
|
|
510
|
+
[#59](https://github.com/AlexanderGrooff/mermaid-ascii/issues/59)); in
|
|
511
|
+
this lineage, equal-cost path nondeterminism, labels landing on shared
|
|
512
|
+
detours instead of their own branch (upstream #111/#112), fan-in groups
|
|
513
|
+
merging into ambiguous trunks (#68/#69), connectors floating off node
|
|
514
|
+
borders, phantom boxes for subgraph-id endpoints, and fullwidth/CJK text
|
|
515
|
+
breaking column math (#119, #121) — inherited and then fixed here
|
|
516
|
+
case-by-case. The durable fix is the same as for R1: route intent shared
|
|
517
|
+
across edges before geometry (#25 §13 — ASCII consumes route classes;
|
|
518
|
+
trunks, reserved feedback channels, label capacity).
|
|
519
|
+
|
|
520
|
+
### R3. Layout is authored content everywhere except text-to-diagram tools
|
|
521
|
+
|
|
522
|
+
The standards world treats positions as part of the artifact:
|
|
523
|
+
[OMG Diagram Definition](https://www.omg.org/spec/DD/) distinguishes
|
|
524
|
+
"graphical information … users have control over, such as position of
|
|
525
|
+
nodes and line routing points," which is "captured for interchange between
|
|
526
|
+
tools," from styling fixed by the notation — and its predecessor UML DI
|
|
527
|
+
spec states the failure mode plainly: without diagram interchange, "all
|
|
528
|
+
diagram information is lost" when a model moves between tools. BPMN DI is a
|
|
529
|
+
normative part of BPMN 2.0
|
|
530
|
+
([ISO/IEC 19510](https://www.iso.org/standard/62652.html)): "the
|
|
531
|
+
interchange of laid out shapes and edges," carrying shape bounds and edge
|
|
532
|
+
waypoints. UML 2.5 normatively defines notation but is silent on layout,
|
|
533
|
+
delegating persistence to the DD-based UML DI annex. Even drafting
|
|
534
|
+
conventions were standardized:
|
|
535
|
+
[ANSI X3.5-1970 §4.4.1](https://www.bitsavers.org/pdf/ansi/X3/X3.005-1970_Flowchart_Symbols_and_Their_Usage_in_Information_Processing.pdf)
|
|
536
|
+
is normative that "normal direction of flow is from left to right and top
|
|
537
|
+
to bottom," with arrowheads *required* only on reverse-direction lines —
|
|
538
|
+
and its successor [ISO 5807:1985](https://www.iso.org/standard/11955.html)
|
|
539
|
+
keeps the conditional-arrowhead rule (§9.3.1: "solid or open arrowheads
|
|
540
|
+
shall be added to indicate direction of flow where necessary (see
|
|
541
|
+
10.2.1.2)"; clause 10 "Conventions" carries the direction rules). Issue
|
|
542
|
+
#25's primary-forward/feedback route classes are that half-century-old
|
|
543
|
+
convention, restated as a checkable invariant — and the same convention
|
|
544
|
+
family yields further validator candidates (unlabeled decision branches;
|
|
545
|
+
single-entry/labeled-exit analysis), tracked in
|
|
546
|
+
[`issue-derived-test-cases.md`](./issue-derived-test-cases.md).
|
|
547
|
+
|
|
548
|
+
Mature tools likewise expose constraint surfaces. Graphviz dot has had
|
|
549
|
+
[`rank=same`/`min`/`max`](https://graphviz.org/docs/attrs/rank/),
|
|
550
|
+
[compass-point ports](https://graphviz.org/docs/attr-types/portPos/)
|
|
551
|
+
(`a:port:nw -> b`), per-edge
|
|
552
|
+
[`constraint`](https://graphviz.org/docs/attrs/constraint/)/[`weight`](https://graphviz.org/docs/attrs/weight/)
|
|
553
|
+
knobs, and obstacle-avoiding spline routing
|
|
554
|
+
([`splines=true`: "edges are drawn as splines routed around nodes"](https://graphviz.org/docs/attrs/splines/))
|
|
555
|
+
since the 1990s. D2 ships [`near`](https://d2lang.com/tour/positions/)
|
|
556
|
+
(anchor a shape near a named other shape),
|
|
557
|
+
[grid diagrams](https://d2lang.com/tour/grid-diagrams/) for exact
|
|
558
|
+
row/column placement, and the architecture-tuned
|
|
559
|
+
[TALA engine](https://d2lang.com/tour/tala/) — "first-class consideration
|
|
560
|
+
for containers," position locking via `top`/`left`, per-container
|
|
561
|
+
direction, dynamic label positioning (and, honestly documented, its own
|
|
562
|
+
instability: a small label change "can cascade into an entirely different
|
|
563
|
+
layout"). yFiles distinguishes post-hoc "generic" labeling from
|
|
564
|
+
[*integrated* labeling computed during layout](https://docs.yworks.com/yfiles-html/dguide/layout/label_placement.html),
|
|
565
|
+
which "prevent[s] label overlaps completely."
|
|
566
|
+
|
|
567
|
+
Text-to-diagram tools deliberately drop that half of the artifact: source
|
|
568
|
+
in, geometry recomputed every render, nothing persisted, no constraints.
|
|
569
|
+
That is the actual root of C1 and C6 — not dagre. It is also a real trade
|
|
570
|
+
(zero-friction authoring, diffable sources, no stale coordinates), which is
|
|
571
|
+
why this project keeps the contract but changes who absorbs the cost: the
|
|
572
|
+
layout is a *deterministic function* of source (C6, C11), its quality is
|
|
573
|
+
*measurable* (`checkQuality`), and its decisions are to become
|
|
574
|
+
*explainable* (#25 certificates) — so an editing program gets back the
|
|
575
|
+
agency a human loses by not being able to drag nodes.
|
|
576
|
+
|
|
577
|
+
### R4. dagre: a partial dot port, frozen
|
|
578
|
+
|
|
579
|
+
Mermaid (2014, Knut Sveidqvist) was built browser-first on d3 + dagre-d3;
|
|
580
|
+
layout was delegated wholesale to dagre, Chris Pettitt's JavaScript
|
|
581
|
+
implementation of the Sugiyama/dot pipeline — a delegation Mermaid's
|
|
582
|
+
maintainer named himself when opening
|
|
583
|
+
[mermaid#867 "Support multiple layout algorithms"](https://github.com/mermaid-js/mermaid/issues/867)
|
|
584
|
+
in 2019: "Currently the layout mechanism mermaid is using is a dagre
|
|
585
|
+
algorithm."
|
|
586
|
+
|
|
587
|
+
What dagre ports — and what it omits — maps directly onto the catalog. The
|
|
588
|
+
dot paper
|
|
589
|
+
([Gansner, Koutsofios, North & Vo 1993](https://www.graphviz.org/documentation/TSE93.pdf),
|
|
590
|
+
IEEE TSE 19(3)) is a *four*-pass algorithm: ranking, ordering, coordinates,
|
|
591
|
+
then a spline-routing pass that computes piecewise-Bézier edges inside a
|
|
592
|
+
feasible region so they avoid node obstacles — the paper explicitly calls
|
|
593
|
+
out tools that "make no attempt to avoid situations where line segments
|
|
594
|
+
overlap unrelated nodes." dot also guarantees that edge labels, modeled as
|
|
595
|
+
virtual nodes, "never overlap other nodes, edges or labels," and exposes
|
|
596
|
+
rank constraints. dagre implements the first three passes (its coordinate
|
|
597
|
+
assignment is
|
|
598
|
+
[Brandes & Köpf 2001](https://link.springer.com/chapter/10.1007/3-540-45848-4_3),
|
|
599
|
+
in `lib/position/bk.js`) and then stops: there is no routing phase at all —
|
|
600
|
+
edges are interpolated through dummy-node points.
|
|
601
|
+
[dagre-d3#291](https://github.com/dagrejs/dagre-d3/issues/291) is the
|
|
602
|
+
user-visible result, and complaint categories C4/C5 are the Mermaid-visible
|
|
603
|
+
results. Cluster
|
|
604
|
+
support follows Sander/Forster on paper (per
|
|
605
|
+
[dagre's wiki](https://github.com/dagrejs/dagre/wiki)) but fails on nesting
|
|
606
|
+
and compound edges in practice
|
|
607
|
+
([dagre#125](https://github.com/dagrejs/dagre/issues/125),
|
|
608
|
+
[dagre#238](https://github.com/dagrejs/dagre/issues/238)) — C3. dot-style
|
|
609
|
+
rank constraints existed in early dagre, were dropped in the 0.7 rewrite,
|
|
610
|
+
and the restoration request —
|
|
611
|
+
[dagre#159 "Add back support for rank constraints"](https://github.com/dagrejs/dagre/issues/159)
|
|
612
|
+
— has been open since November 2014.
|
|
613
|
+
|
|
614
|
+
The "abandoned" framing needs one nuance. D2's engine-comparison page
|
|
615
|
+
states ["Unmaintained. Development stopped in 2018"](https://d2lang.com/tour/dagre/)
|
|
616
|
+
("battle tested (thanks to MermaidJS, which exclusively uses Dagre for its
|
|
617
|
+
flowcharts)"; cons include "makes some inexplicable edge routing decisions
|
|
618
|
+
occasionally"), and that was accurate for the era it described — but the
|
|
619
|
+
`@dagrejs` org resumed releases in 2023 (1.0.0 May 2023; 3.0.0 March 2026).
|
|
620
|
+
What has *not* changed is the feature picture: no routing phase, no ports,
|
|
621
|
+
rank constraints still unrestored after a decade, and Mermaid ships the
|
|
622
|
+
dormant `dagre-d3` line via the `dagre-d3-es` fork.
|
|
623
|
+
|
|
624
|
+
Mermaid's own remediations concede the diagnosis. The migration ask —
|
|
625
|
+
[mermaid#1969 "Migrate flowchart from dagre to elkjs"](https://github.com/mermaid-js/mermaid/issues/1969)
|
|
626
|
+
(2021: dagre draws "edges … through other blocks instead of around them")
|
|
627
|
+
— was answered by the maintainer himself with the experimental
|
|
628
|
+
`flowchart-elk` renderer in v9.4.0 (February 2023), then v11.0.0 (August
|
|
629
|
+
2024) made layout pluggable with ELK as the opt-in
|
|
630
|
+
[`@mermaid-js/layout-elk`](https://www.npmjs.com/package/@mermaid-js/layout-elk)
|
|
631
|
+
package ("better for larger and/or more complex diagrams," per its own
|
|
632
|
+
docs) — selected via frontmatter `config.layout`, and per the
|
|
633
|
+
[syntax reference](https://mermaid.js.org/intro/syntax-reference.html)
|
|
634
|
+
"currently … supported for flowcharts and state diagrams" only. The
|
|
635
|
+
edge-overlap ask
|
|
636
|
+
[mermaid#1006](https://github.com/mermaid-js/mermaid/issues/1006) was
|
|
637
|
+
closed *Wont Fix*. GitHub's native rendering still runs the dagre default —
|
|
638
|
+
so most readers see the unfixed engine (complaint categories C2, C3, C4, C9),
|
|
639
|
+
and even opted-in users get the better engine for two of ~25 families. PlantUML made the
|
|
640
|
+
opposite bet decades earlier: it
|
|
641
|
+
[delegates class/state/component/usecase layout to Graphviz dot](https://plantuml.com/graphviz-dot),
|
|
642
|
+
inheriting dot's router and rank constraints along with its weaknesses.
|
|
643
|
+
|
|
644
|
+
This engine's answer is architectural: ELK layered
|
|
645
|
+
([Schulze, Spönemann & von Hanxleden 2014](https://www.sciencedirect.com/science/article/abs/pii/S1045926X13000943),
|
|
646
|
+
JVLC 25(2) — the port-constraint extension of Sugiyama) as the only graph
|
|
647
|
+
engine, bundled synchronously, with model-order options pinned so source
|
|
648
|
+
order drives cycle breaking and crossing minimization
|
|
649
|
+
([`considerModelOrder`](https://eclipse.dev/elk/reference/options/org-eclipse-elk-layered-considerModelOrder-strategy.html):
|
|
650
|
+
"preserves the order of nodes and edges in the model file if this does not
|
|
651
|
+
lead to additional edge crossings"; the backing paper is Domrös &
|
|
652
|
+
von Hanxleden, IVAPP 2022). ELK brings the port constraints
|
|
653
|
+
(`FIXED_SIDE`/`FIXED_ORDER`/`FIXED_POS`) that #25 §8 builds on and dagre
|
|
654
|
+
never had, plus compound layout and an optional libavoid-based
|
|
655
|
+
obstacle-avoiding router. It is not sufficient by itself: upstream #83
|
|
656
|
+
showed ELK's default cycle breaking flipping TD diagrams horizontal until
|
|
657
|
+
model order was enforced; ELK's own docs restrict what composes with
|
|
658
|
+
`INCLUDE_CHILDREN` hierarchy handling (which is why
|
|
659
|
+
`src/layout-engine.ts` falls back to `SEPARATE` when a subgraph overrides
|
|
660
|
+
direction); and ELK still knows nothing about *semantic* edge roles, which
|
|
661
|
+
is exactly the intent layer #25 adds.
|
|
662
|
+
|
|
663
|
+
### R5. Renderer-first semantics, in three generations
|
|
664
|
+
|
|
665
|
+
The recurring design flaw across all three codebases is the one #26's
|
|
666
|
+
guiding principle names: *the renderer is the first place semantics are
|
|
667
|
+
guessed.*
|
|
668
|
+
|
|
669
|
+
- **Mermaid (November 2014–):** born, per its own 0.1.0 README, to "avoid
|
|
670
|
+
heavy tools like Visio" with "a simple markdown-like script language" —
|
|
671
|
+
the design optimized authoring friction, not layout quality. Per-family
|
|
672
|
+
Jison grammars feed per-family bespoke renderers (the sequence renderer
|
|
673
|
+
uses no layout engine at all — hand-maintained vertical-position
|
|
674
|
+
bookkeeping, hence C12); text is measured via the browser DOM with HTML
|
|
675
|
+
labels in `foreignObject` — the C8 export-clipping family, first reported
|
|
676
|
+
six weeks after the first release
|
|
677
|
+
([mermaid#58](https://github.com/mermaid-js/mermaid/issues/58), December
|
|
678
|
+
2014, still open) — styling is coupled to page CSS, chart geometry is
|
|
679
|
+
hard-coded per type with no collision handling (C13), and there is no IR
|
|
680
|
+
contract between parse and render, so every embedder/version pair renders
|
|
681
|
+
differently (C11).
|
|
682
|
+
- **`mermaid-ascii`:** grid geometry computed during drawing; labels and
|
|
683
|
+
containers handled by local painter logic (R2).
|
|
684
|
+
- **`beautiful-mermaid` (Craft/Luki Labs, January 2026):** re-implemented
|
|
685
|
+
rendering as synchronous zero-DOM TypeScript with bundled elkjs (run
|
|
686
|
+
synchronously via a worker bypass, `src/elk-instance.ts`) — fixing C8 and
|
|
687
|
+
the browser half of C11 — but kept lenient line-by-line regex parsers
|
|
688
|
+
(the ER cardinality parser silently dropped valid relationships — upstream
|
|
689
|
+
#124, fixed here in PR #17) and accumulated post-ELK geometry passes:
|
|
690
|
+
layer alignment introduced in upstream v1.0.1 ("Layout quality
|
|
691
|
+
improvements and layer alignment"), edge-point orthogonalization, and
|
|
692
|
+
post-routing shape clipping. Each pass is locally reasonable; together
|
|
693
|
+
they move geometry after routes are extracted, which is #25's diagnosed
|
|
694
|
+
source of hitches and stale bends (§2, §9).
|
|
695
|
+
|
|
696
|
+
This fork's trajectory is the inversion of that flaw: parse to a typed
|
|
697
|
+
model, derive layout intent, compute geometry, certify, then render —
|
|
698
|
+
with renderers as projections of shared intent rather than the place where
|
|
699
|
+
intent is invented.
|
|
700
|
+
|
|
701
|
+
### Root causes → historical issue #26 workstream evidence
|
|
702
|
+
|
|
703
|
+
This crosswalk preserves the research decomposition only. It does not make the
|
|
704
|
+
issue workstreams an active schedule; root `TODO.md` must own any promoted work.
|
|
705
|
+
|
|
706
|
+
| Root cause | #26 workstream(s) |
|
|
707
|
+
|---|---|
|
|
708
|
+
| R1 phase separation, no invariants | WS1 layout-intent/certificates; WS4 certifying simplifier |
|
|
709
|
+
| R1 labels as afterthoughts | WS8 text-measurement contract; #25 §11.4 label invariant |
|
|
710
|
+
| R1 compound difficulty | WS2 route classes (`cross-hierarchy`, `container-edge`) |
|
|
711
|
+
| R2 greedy grid routing | WS6 ASCII/Unicode as first-class projections of route intent |
|
|
712
|
+
| R3 no control surface | WS14 analysis outputs (explain, don't invent syntax); #25 certificates |
|
|
713
|
+
| R4 rectangular/port-less model | WS3 semantic ports and shape-aware anchors |
|
|
714
|
+
| R5 renderer-first semantics | WS5 Gantt resolver model (landed); WS7 class/ER contracts; WS10 region tree; WS13 preservation ladder |
|
|
715
|
+
| Unreviewable visual change (meta) | WS12 reproducible before/after evidence |
|
|
716
|
+
| Generic metrics miss failures (meta, complaint C4 worked example) | WS11 family-specific validators |
|
|
717
|
+
|
|
718
|
+
---
|
|
719
|
+
|
|
720
|
+
## Part 3 — Coverage scorecard
|
|
721
|
+
|
|
722
|
+
| # | Complaint | Loudest evidence | Status here | Key references |
|
|
723
|
+
|---|---|---|---|---|
|
|
724
|
+
| C1 | Manual positioning | mermaid#5420 (47R), #270 (2016–) | **out of scope** by policy | #25 OQ2; agent loop as substitute |
|
|
725
|
+
| C2 | Scale collapse | mermaid#1984 (39R) | **partial** (detection only) | `quality.ts` bands; `eval/layout-compare/` |
|
|
726
|
+
| C3 | Subgraphs | mermaid#2509 (189R) | **landed** | `subgraph-direction.test.ts`; PRs #17/#21/#22 |
|
|
727
|
+
| C4 | Erratic routing | mermaid#6476, #5601 | **partial**; candidate fix evidence only | #25 §11–12; MFA worked example above |
|
|
728
|
+
| C5 | Edge-label overlap | mermaid#2131 | **partial** | inline ELK labels; `labelEdgeProximity`; #25 §11.4 |
|
|
729
|
+
| C6 | Order control / edit stability | mermaid#3723 (86R), #815 (45R) | **partial** | model-order ELK opts; `agent-auth-flow.test.ts`; edit-stability tests |
|
|
730
|
+
| C7 | Default aesthetics | HN 30339032 | **landed** (upstream + fork) | theming; Style + Palette; contrast tests |
|
|
731
|
+
| C8 | Text clipping | mermaid#2688 (24R) | **landed** | zero-DOM text; `LABEL_OVERFLOW`; CJK tests; WS8 candidate evidence |
|
|
732
|
+
| C9 | dagre stagnation | Mermaid docs' own ELK advice | **landed** structurally | `layout-engine.ts`; `elk-instance.ts`; #25 §10 |
|
|
733
|
+
| C10 | Self-loops | mermaid#6336 (31R) | **partial** | clearance heuristics; #25 §8.1 candidate evidence |
|
|
734
|
+
| C11 | Version/renderer instability | mermaid#5813 (28R) | **landed** | determinism suite; `eval/layout-compare/`; goldens |
|
|
735
|
+
| C12 | Sequence alignment | mermaid#1765 (28R) | **partial**; first properties landed | #25 §14.2; #26 WS11; `sequence-layout.test.ts` |
|
|
736
|
+
| C13 | Chart-family geometry | mermaid#1301 (23R) | **partial** | PR #24 (gantt); pie/quadrant/gantt geometry tests |
|
|
737
|
+
|
|
738
|
+
---
|
|
739
|
+
|
|
740
|
+
## Part 4 — What this project deliberately does not address
|
|
741
|
+
|
|
742
|
+
Stated plainly so the scorecard cannot oversell:
|
|
743
|
+
|
|
744
|
+
1. **Manual positioning (C1).** Arbitrary coordinates and post-layout nudging
|
|
745
|
+
remain deliberately unsupported. `BUILD-24` schedules only typed structural
|
|
746
|
+
auto-layout constraints (rank, adjacency, short-edge preference), without
|
|
747
|
+
inventing Mermaid source syntax. If Mermaid core standardizes compatible
|
|
748
|
+
source hints, this fork can map them into the same typed contract.
|
|
749
|
+
2. **Large-diagram compaction (C2).** Detection exists; no compaction work
|
|
750
|
+
is scheduled. Source-order priority can make wide diagrams wider — the
|
|
751
|
+
Auth Flow aspect band in `agent-auth-flow.test.ts` is widened to 7
|
|
752
|
+
because of it. The only candidate direction on record is the
|
|
753
|
+
decomposition idea noted in C2, which sidesteps compaction rather than
|
|
754
|
+
solving it.
|
|
755
|
+
3. **Reach.** Most complainers experience Mermaid through GitHub/GitLab/
|
|
756
|
+
Notion native rendering, which no fork renderer can change. Work here
|
|
757
|
+
applies to diagrams rendered through this stack (agent pipelines, CI,
|
|
758
|
+
terminals, the editor) plus whatever is upstreamed per the
|
|
759
|
+
[upstreaming strategy](./fork-differences.md#upstreaming-strategy).
|
|
760
|
+
4. **Family coverage.** The checked runtime registry is the source of truth for
|
|
761
|
+
supported families. Every registered family, including Radar, is rendered and
|
|
762
|
+
characterized; Mermaid families outside that registry remain unsupported.
|
|
763
|
+
5. **Metric honesty.** Today's perceptual metrics pass diagrams whose
|
|
764
|
+
routing is visibly wrong (complaint C4 worked example). Unless and until the
|
|
765
|
+
#25 certificate and #26 family-validator candidates are promoted in root
|
|
766
|
+
`TODO.md` and land, `verify.ok` + `checkQuality` must not be read as "looks
|
|
767
|
+
right" — `docs/quality.md` already says this; the MFA fixture proves it.
|
|
768
|
+
|
|
769
|
+
---
|
|
770
|
+
|
|
771
|
+
## Sources
|
|
772
|
+
|
|
773
|
+
Complaint evidence (June 2026): the `mermaid-js/mermaid` issues linked
|
|
774
|
+
inline above; Mermaid flowchart docs
|
|
775
|
+
(<https://mermaid.js.org/syntax/flowchart.html>); Hacker News items linked
|
|
776
|
+
inline; the GitLab handbook Mermaid-layout page
|
|
777
|
+
(<https://handbook.gitlab.com/handbook/tools-and-tips/mermaid/>);
|
|
778
|
+
diagrams.so's diagram-as-code comparison
|
|
779
|
+
(<https://diagrams.so/learn/diagram-as-code-comparison>).
|
|
780
|
+
|
|
781
|
+
Literature (all verified June 2026; venue/page details via dblp or
|
|
782
|
+
publisher pages):
|
|
783
|
+
|
|
784
|
+
- Sugiyama, Tagawa & Toda, "Methods for Visual Understanding of
|
|
785
|
+
Hierarchical System Structures," IEEE Trans. SMC-11(2), 1981.
|
|
786
|
+
<https://doi.org/10.1109/TSMC.1981.4308636>
|
|
787
|
+
- Garey & Johnson, "Crossing Number is NP-Complete," SIAM J. Algebraic
|
|
788
|
+
Discrete Methods 4(3), 1983. <https://dl.acm.org/doi/10.1137/0604033>
|
|
789
|
+
- Eades & Wormald, "Edge crossings in drawings of bipartite graphs,"
|
|
790
|
+
Algorithmica 11(4), 1994.
|
|
791
|
+
<https://link.springer.com/article/10.1007/BF01187020>
|
|
792
|
+
- Marks & Shieber, "The Computational Complexity of Cartographic Label
|
|
793
|
+
Placement," Harvard TR-05-91, 1991.
|
|
794
|
+
<https://dash.harvard.edu/entities/publication/73120379-247a-6bd4-e053-0100007fdf3b>
|
|
795
|
+
- Kakoulis & Tollis, "On the complexity of the Edge Label Placement
|
|
796
|
+
problem," Comput. Geom. 18(1), 2001.
|
|
797
|
+
<https://www.sciencedirect.com/science/article/pii/S0925772100000250>
|
|
798
|
+
- Gansner, Koutsofios, North & Vo, "A Technique for Drawing Directed
|
|
799
|
+
Graphs," IEEE TSE 19(3), 1993.
|
|
800
|
+
<https://www.graphviz.org/documentation/TSE93.pdf>
|
|
801
|
+
- Brandes & Köpf, "Fast and Simple Horizontal Coordinate Assignment,"
|
|
802
|
+
GD 2001, LNCS 2265.
|
|
803
|
+
<https://link.springer.com/chapter/10.1007/3-540-45848-4_3> (authors'
|
|
804
|
+
2020 erratum: <https://arxiv.org/abs/2008.01252>)
|
|
805
|
+
- Schulze, Spönemann & von Hanxleden, "Drawing layered graphs with port
|
|
806
|
+
constraints," JVLC 25(2), 2014.
|
|
807
|
+
<https://www.sciencedirect.com/science/article/abs/pii/S1045926X13000943>
|
|
808
|
+
- Domrös & von Hanxleden, "Preserving Order during Crossing Minimization
|
|
809
|
+
in Sugiyama Layouts," IVAPP 2022.
|
|
810
|
+
<https://www.scitepress.org/Papers/2022/108338/108338.pdf>
|
|
811
|
+
- Purchase, "Which aesthetic has the greatest effect on human
|
|
812
|
+
understanding?", GD 1997, LNCS 1353.
|
|
813
|
+
<https://link.springer.com/chapter/10.1007/3-540-63938-1_67>
|
|
814
|
+
- Sander, "Layout of Compound Directed Graphs," TR A/03/96, Universität
|
|
815
|
+
des Saarlandes, 1996.
|
|
816
|
+
<https://publikationen.sulb.uni-saarland.de/bitstream/20.500.11880/25862/1/tr-A03-96.pdf>
|
|
817
|
+
- Wybrow, Marriott & Stuckey, "Orthogonal Connector Routing," GD 2009
|
|
818
|
+
(libavoid). <https://users.monash.edu/~mwybrow/papers/wybrow-gd-2009.pdf>
|
|
819
|
+
|
|
820
|
+
Products: Graphviz attribute docs (`rank`, `portPos`, `splines`,
|
|
821
|
+
`constraint`, `weight`, linked inline); D2/Terrastruct docs (TALA, `near`,
|
|
822
|
+
grid diagrams, dagre/ELK engine pages, linked inline); yFiles automatic
|
|
823
|
+
label placement
|
|
824
|
+
(<https://docs.yworks.com/yfiles-html/dguide/layout/label_placement.html>);
|
|
825
|
+
PlantUML–Graphviz delegation (<https://plantuml.com/graphviz-dot>);
|
|
826
|
+
Mermaid v11.0.0 release
|
|
827
|
+
(<https://github.com/mermaid-js/mermaid/releases/tag/v11.0.0>) and
|
|
828
|
+
`@mermaid-js/layout-elk`
|
|
829
|
+
(<https://www.npmjs.com/package/@mermaid-js/layout-elk>); ELK option
|
|
830
|
+
reference (<https://eclipse.dev/elk/reference.html>) and the ELK overview
|
|
831
|
+
paper (<https://arxiv.org/abs/2311.00533>).
|
|
832
|
+
|
|
833
|
+
Standards: OMG Diagram Definition v1.1 (<https://www.omg.org/spec/DD/>);
|
|
834
|
+
BPMN 2.0.2 (<https://www.omg.org/spec/BPMN/2.0.2/>) / ISO/IEC 19510:2013
|
|
835
|
+
(<https://www.iso.org/standard/62652.html>); UML 2.5.1
|
|
836
|
+
(<https://www.omg.org/spec/UML/2.5.1/About-UML>); ISO 5807:1985
|
|
837
|
+
(<https://www.iso.org/standard/11955.html>); ANSI X3.5-1970 (scan:
|
|
838
|
+
<https://www.bitsavers.org/pdf/ansi/X3/X3.005-1970_Flowchart_Symbols_and_Their_Usage_in_Information_Processing.pdf>).
|
|
839
|
+
|
|
840
|
+
Origins: Mermaid 0.1.0 (November 2014, Knut Sveidqvist;
|
|
841
|
+
<https://github.com/mermaid-js/mermaid/releases/tag/0.1.0> — the 0.1.0
|
|
842
|
+
README credits d3 and dagre-d3 and states the Visio-avoidance motive);
|
|
843
|
+
dagre (Chris Pettitt; wiki reading list
|
|
844
|
+
<https://github.com/dagrejs/dagre/wiki>; rank-constraint regression
|
|
845
|
+
dagre#159; `@dagrejs` revival releases 2023–2026); `mermaid-ascii`
|
|
846
|
+
(Alexander Grooff, <https://github.com/AlexanderGrooff/mermaid-ascii>;
|
|
847
|
+
Show HN October 2024 <https://news.ycombinator.com/item?id=41847407>);
|
|
848
|
+
`beautiful-mermaid` (Luki Labs/Craft, npm January 2026; HN thread
|
|
849
|
+
<https://news.ycombinator.com/item?id=46804828>; upstream v1.0.1
|
|
850
|
+
layout/layer-alignment commit; upstream issues
|
|
851
|
+
#63/#68/#69/#83/#89/#98/#111/#112/#113/#115/#119/#121/#124).
|