What this fork fixes
Every pair below is rendered by this project's own renderer. The before side runs the code as it existed immediately before the fix landed — the tree at that commit's parent — so nothing here is hand-drawn or reconstructed. The generator fails the build if any pair renders identically, because a before/after that looks the same would claim a fix it does not demonstrate.
Arrows with no surrounding space dropped the edge
The bare-node-id scanner greedily ate the arrow’s leading dashes, producing a node literally named A-- and no edge at all — silently, with no error.
Before: one mis-named node and no arrows. After: three nodes, two edges.
Brackets inside a quoted label corrupted the label
The shape-delimiter scanner treated the first ] inside a quoted string as the node’s closing bracket, truncating the label mid-word.
Before: the label is cut off at the inner bracket. After: it reads in full.
CJK subgraph ids produced an empty id
The subgraph-id pattern was ASCII-only ([\w-]+), so a non-ASCII id fell through to title slugification — which stripped every character, leaving an empty id.
Before: the subgraph frame is missing or mislabelled. After: it renders with its title.
CJK labels overflowed their own ASCII box
Box width was measured in UTF-16 code units, but a CJK glyph occupies two terminal columns — so the right border landed inside the label it was supposed to enclose.


Before: the right border sits inside the text and rows are ragged. After: every row is the same width.
Circle and cross edge terminators dropped the target node
A --o B and A --x B were not recognised by the arrow regex, so parsing broke out of the edge loop early — losing the edge and the node it pointed at.


Before: only A and B render, with no edges at all — C is gone. After: all three nodes and both edges.
ER “zero or more” cardinality was silently dropped
Cardinality strings were normalised by sorting their characters, which conflated }o with the unrelated {o/o{ and left it unrecognised. The relationship was dropped, and with only one relationship in the diagram, so were both entities.

Before: nothing renders at all — losing the cardinality dropped the only relationship, and with it both entities. After: the full diagram, crow’s-foot marker included.
Bidirectional arrows rendered invisible in some renderers
orient="auto-start-reverse" already rotates the start arrowhead 180°, but its polygon was also pre-reversed — a double reversal that pointed the head into the line. librsvg and Inkscape drew it as degenerate or invisible.
<marker id="arrowhead-start" markerWidth="8" markerHeight="5" refX="1" refY="2.5" orient="auto-start-reverse">
<polygon points="8 0, 0 2.5, 8 5" fill="var(--_arrow)" stroke="var(--_arrow)" stroke-width="0.75" stroke-linejoin="round" />
</marker><marker id="arrowhead-start" markerWidth="10.5" markerHeight="12.8" refX="9.5" refY="6.4" markerUnits="userSpaceOnUse" orient="auto-start-reverse">
<polygon points="0 0, 10.5 6.4, 0 12.8" fill="var(--_arrow)" stroke="var(--_arrow)" stroke-width="0.75" stroke-linejoin="round" />
</marker>Chrome tolerates the double reversal, so both sides look the same in a browser — which is why this pair shows the marker definition instead of the picture. Compare the points: before they run 8 0, 0 2.5, 8 5 (already reversed), after 0 0, 8 2.5, 0 5 (shared with the forward marker, letting auto-start-reverse do the rotation once).
Nested subgraph direction overrides were ignored
A direction inside a nested subgraph had no effect, and edges crossing a subgraph boundary fell back to a naive path or failed to route.
Before: the inner group ignores its direction and the crossing edge routes poorly.
Fan-in roots were interleaved instead of grouped
Root nodes were placed without regard to what they fed into, so A1, A2 → A and B1, B2 → B interleaved on the grid instead of sitting together above their own target. The same PR also bounded A* pathfinding and made root detection order-independent, fixing two heap-exhaustion crashes on dense graphs.


Before: the top row reads A1, B1, A2, B2 — the two groups interleaved. After: A1, A2, B1, B2, each pair above its own target.
Class diagrams grew a blank compartment
A class with methods but no attributes rendered an empty middle compartment where the attribute list would have been.


Before: an empty compartment between the name and the methods. After: none.
A class shorthand before the brackets discarded the label
A:::external[External User] — the shape patterns require the id to sit immediately before its brackets, so the ::: token in between made every pattern miss and the label was thrown away.
Before: the node shows its bare id. After: it shows its label.
A trailing semicolon on `class` produced a stray node
class B highlight; is valid Mermaid, but the regex was anchored without the semicolon — so the statement fell through to node parsing and rendered a node labelled "class".
Before: a spurious "class" node appears. After: B is simply styled.
A CSS `var(...)` font was quoted into a dead font name
{ font: 'var(--font-family-body)' } is the natural way to inherit a host page's font, but the value was wrapped in quotes (an inert font name, not a var() call) and also URL-encoded into a Google Fonts @import that can never resolve.
text { font-family: 'var(--font-family-body)', system-ui, sans-serif;text { font-family: var(--font-family-body), system-ui, sans-serif;The browser renders both the same until a host page defines the variable, so this pair shows the font-family rule. Before: the reference is quoted, an inert font name (the build also emitted a failing Google Fonts @import for it). After: it is unquoted, so the host page's variable resolves, and the @import is gone.
Sequence notes before the first message vanished
A note written before any message parses with afterIndex: -1, but layout only ever looked notes up by a real message index — so it was never positioned or drawn.


Before: the leading note is missing entirely. After: it renders above the first message.
ER entity aliases discarded the alias and every attribute
The entity-header pattern did not recognize id[Alias] bodies at all, so the line fell back to matching only the bare id — losing the alias and the entire attribute block that followed it.
Before: two boxes labeled only "p" and "a", each showing "(no attributes)" — the alias line failed to parse past the id. After: "Person" and "Customer Account", each with its attribute.
ER `direction` directive had no effect on layout
The top-level direction statement was parsed but never threaded into the ELK layout options, so direction TB rendered identically to the default left-to-right layout.
Before: entities lay out left-to-right regardless of the direction TB statement. After: they stack top-to-bottom.
An edge-less subgraph member merged two sibling subgraph frames
Root-node placement treated an edge-less node as a shared "root" regardless of which subgraph it belonged to, so one subgraph’s bounding box could balloon out to enclose an unrelated sibling — corrupting both frames and interleaving their titles.


Before: the two subgraph frames merge into one box with a garbled title. After: two distinct, disjoint frames.
`~~~` invisible-link syntax was not recognized
The arrow regex had no alternative for ~~~, so Mermaid’s invisible-link syntax — used to connect or order nodes without drawing a connector — failed to parse as an edge at all.


Before: C is disconnected from the rest of the graph — the ~~~ link is dropped entirely. After: C sits in the layout slot the invisible link puts it in, with no visible connector drawn to it.
A node’s custom class never reached the rendered SVG
The class name from :::className or class A,B className was resolved for inline fill/stroke, but never written onto the element itself, so external CSS had nothing to select.
<g class="node" data-id="A"
<g class="node highlight" data-id="A"
Both sides look identical on screen — the fill color already worked before this fix. Compare the markup: before, node A’s group carries only class="node"; after, class="node highlight", so a stylesheet rule like .highlight { } can finally target it.
CJK state names failed to parse in state diagrams
stateDiagram transition and state-name patterns were ASCII-only (\w), so a diagram made entirely of Chinese, Japanese, or Korean state names failed to match a single one of them.
Before: a blank canvas — every state name failed to parse, leaving nothing to draw. After: all three states and both transitions render.
Text-embedded edge labels (`-- Yes -->`) were not parsed
Mermaid’s inline label syntax on an arrow body — as opposed to the -->|Yes| pipe form — had no parser support. The label vanished, and the mis-tokenization also swallowed the bracketed label off the node the arrow pointed at.
Before: the "Yes"/"No" edge labels are gone, and C and D render as bare ids instead of "Go to the park" / "Stay indoors". After: every edge and every node keeps its label.
Long ER relationship labels were truncated in ASCII output
Labels longer than a fixed 6-character inter-entity gap were silently cut off — "ordered in" rendered as "ordere" — and even short labels sat flush against both entity boxes.


Before: the second relationship label reads "ordere". After: it reads "ordered in" in full, with padding from both boxes.
A decision node’s edge label could land on a stray tee character
The box-start connector always drew a tee/junction glyph based on the edge’s exit direction alone, with no check that a real perpendicular border line existed at that cell — so a label that shifted the attachment point left a disconnected ├ floating with blank cells on both sides.


Before: a stray ├ sits disconnected on the "Yes" edge’s row. After: a plain ─ line in its place.
Long class relationship labels on narrow classes were truncated
A class’s column was sized from its box alone, never from the relationship label that had to fit beside it — so single-letter classes with labels like inheritance had every label squeezed into the box’s own width and cut to ….


Before: five of the six labels read inheri…, composi…, and so on. After: the columns spread apart just enough for all six to render in full.
More than two relationships between narrow classes overwrote each other
Each relationship in a group got its own column offset, but the offsets were then clamped back inside the box — a width-5 box only has room for three — so several relationships collapsed onto one connection point and the last one drawn silently overwrote the rest.


Before: only four survives, on two lines. After: one, two, three, and four each sit on their own lane, joined to the boxes by a short jog, with four distinct arrowheads.
A detoured relationship’s label ignored its own routed path
A relationship’s label always anchored on the straight-line midpoint between its source and target boxes, even when its line detoured around an intermediate box to avoid it — so the label sat wherever the unrouted line would have gone, not the actual one, reading as though it terminated at the wrong box.
┌────────────────────────────┐
│ Controller │
├────────────────────────────┤
│ - Model: model │
│ - View: view │
├────────────────────────────┤
│ + handleInput(event): void │
│ + updateModel(data): void │
└────────────────────────────┘
refreshes ───────────┐
updates │
▼ │
┌───────────────────────────┐│
│ Model ││
├───────────────────────────┤│
│ - Map: data ││
├───────────────────────────┤│
│ + getData(): Map ││
│ + setData(key, val): void ││
│ + notify(): void ││
└───────────────────────────┘│
▲ ┊ │
reads notifies │
│ ▼─────▼────────────┘
┌──────────────────┐
│ View │
├──────────────────┤
│ - Model: model │
├──────────────────┤
│ + render(): void │
│ + update(): void │
└──────────────────┘
┌────────────────────────────┐
│ Controller │
├────────────────────────────┤
│ - model Model │
│ - view View │
├────────────────────────────┤
│ + handleInput(event): void │
│ + updateModel(data): void │
└────────────────────────────┘
│
├──────────────┐
updates │
│ │
▼ │
┌───────────────────────────┐│
│ Model ││
├───────────────────────────┤│
│ - data Map ││
├───────────────────────────┤│
│ + getData(): Map ││ refreshes
│ + setData(key, val): void ││
│ + notify(): void ││
└───────────────────────────┘│
▲ ┊ │
│ ┊ │
│ ┊ │
reads notifies │
│ ┊ │
│ ┌─────┊────────┘
│ ▼ ▼
┌──────────────────┐
│ View │
├──────────────────┤
│ - model Model │
├──────────────────┤
│ + render(): void │
│ + update(): void │
└──────────────────┘
Before: refreshes and updates sit stacked on adjacent rows above the single arrowhead entering Model, reading as though both terminate there. After: refreshes sits beside its own detour line, past Model’s right border, clearly distinct from updates.
Class member generics kept mermaid’s raw `~T~` tildes
Mermaid converts List~Observer~ to List<Observer> before rendering; the class parser here kept the tildes, so both the SVG and ASCII output showed List~Observer~ — flagged by the weekly form-judge audit (#418).


Before: the EventEmitter attribute reads List~Observer~. After: it reads List<Observer>, the form real mermaid renders.
An edge label could float next to a different edge’s line
When an edge’s route fell back to a direct path (every L-shaped route and A* blocked — e.g. the third edge leaving the same side of a node), its label was centered on the never-drawn diagonal between the two legs that actually get drawn, landing in open grid next to an unrelated edge’s connector: ┆thick two columns away from the thick edge’s own ┃. Flagged by the weekly form-judge audit (#418).


Before: "thick" sits glued to the dotted edge’s ┆ column. After: it sits on the thick edge’s own ┃ line, like "dotted" does on its ┆ line.
State diagrams ignored classDef, class, and ::: styling
State diagrams silently dropped classDef, class A,B name, and State:::name, and S1:::foo --> S2 swallowed S1 into a bogus ::foo --> S2 state, so styled states rendered unstyled with a stray label.
Before: a stray ::hot label on the first transition and no colors. After: S1 and S3 are red, S2 is blue.
A space before the edge label pipe dropped the target node
A --> |deploy| B required the |label| to touch the arrow, so with a space before the pipe the parser lost both the label and the node it pointed at.
Before: the labels and the nodes B and C are missing. After: three nodes, two labelled edges.
ASCII vertical edge labels were printed over the edge line
A label on a vertical edge was drawn on top of the stroke, so the line ran through the gaps between its words (long─label) or vanished under the text.
┌────────────┐
│ │
│ A │
│ │
└──────┬─────┘
│
long label
│
│
▼
┌────────────┐
│ │
│ B │
│ │
└──────┬─────┘
│
ab
│
│
▼
┌────────────┐
│ │
│ C │
│ │
└────────────┘┌────────────┐
│ │
│ A │
│ │
└──────┬─────┘
│
│
long label
│
▼
┌────────────┐
│ │
│ B │
│ │
└──────┬─────┘
│
│
ab
│
▼
┌────────────┐
│ │
│ C │
│ │
└────────────┘Before: the label replaces the edge line on its row. After: the line runs unbroken and the label sits one cell beside it.
A cyclic top-down flowchart could be laid out upside down
The layout engine broke cycles with its own heuristic and could reverse the wrong edge, drawing the whole flow backwards (the last step at the top). Cycles are now broken the way mermaid.js does, depth-first from the first node.
Before: the flow is inverted, starting from the wrong node. After: main is at the top and the flow reads downward.
A self-arrow inside alt/loop was drawn outside its block
The block’s right wall was sized from lifeline positions only, ignoring a self-arrow’s loop glyphs and label, so the loop’s corners were overwritten by the wall.
┌───┐ ┌───┐ │ A │ │ B │ └─┬─┘ └─┬─┘ │ │ ┌loop [┐ │ │ │ │ │ │ ├───│ │ │ │ │ a rather long self message │ ◀───│ │ │ │ │ │ └──────┘ │ │ │ │ done │ │─────────▶ │ │ ┌─┴─┐ ┌─┴─┐ │ A │ │ B │ └───┘ └───┘
┌───┐ ┌───┐
│ A │ │ B │
└─┬─┘ └─┬─┘
│ │
┌─ loop ─────── [retry] ────────┐ │
│ │ │ │
│ a rather long self message │ │
│ ├───┐ │ │
│ │ │ │ │
│ ◀───┘ │ │
│ │ │ │
└───────────────────────────────┘ │
│ │
│ done │
│──────────────────▶
│ │
┌─┴─┐ ┌─┴─┐
│ A │ │ B │
└───┘ └───┘Before: the loop wall clips the self-arrow. After: the block encloses the whole arrow and label.
ASCII fan-out edges bled their line style into each other
Edges with different styles leaving one node shared a routing corridor, and the later thick edge overwrote the dotted edge’s glyphs. Separately, two edges routed through a shared node could land on the same column and read as one line.
╭────────────╮ ╭─────────────╮ ╭──────────────╮
│ │ │ │ │ │
│ Mobile App ├────►│ API Gateway ├────►│ User Service │
│ │ │ │ │ │
╰────────────╯ ╰──────┬━━━━━━━━━━━━━──────────────╯
┆ ┃
┆ ┃
┆ ┃
┆ ┃
┆ ┃
┆ ┃──────────────╮
┆ ┃ │
└┄┄┄┄┄┄┄┄┄┄┄►┃ Cache │
┃ │
┃──────────────╯
┃
┃
┃
┃
┃
┃──────────────╮
▼ │
│ Queue │
│ │
╰──────────────╯ ╭──────────────╮
│ │
┌───────────►│ User Service │
│ │ │
│ ╰──────────────╯
│
│
│
│
╭────────────╮ ╭──────┴──────╮ ╭──────────────╮
│ │ │ │ │ │
│ Mobile App ├────►│ API Gateway ├┄┄┄┄►│ Cache │
│ │ │ │ │ │
╰────────────╯ ╰──────┬──────╯ ╰──────────────╯
┃
┃
┃
┃
┃ ╭──────────────╮
┃ │ │
└━━━━━━━━━━━►│ Queue │
│ │
╰──────────────╯Before: the dotted edge’s leg is drawn with heavy glyphs. After: each edge keeps its own style.
An invalid colour in style/classDef painted the node black
A value that is not a CSS colour (fill:notacolor) was written into the SVG as an attribute, which browsers paint black (or stroke as none). Mermaid ignores the bad declaration, and so do we now.
Before: the first node is solid black and the second has no border. After: both fall back to the theme paint, and the valid fill and stroke width stay.
An ER relationship beside a shorter entity landed on its border
The relationship row was the left entity’s midpoint, so beside a shorter entity it fell on that box’s bottom border and the cardinality marker ran into the corner.


Before: the ╟ marker collides with ORDER’s └ corner. After: the line meets ORDER at a ├ junction inside the box.
A narrower stacked ER entity sat off-center, so its edge jogged
Each stacked ER row started at the left edge, so a narrower entity was left-aligned under its neighbor while the relationship attaches at each box’s center, and the edge jogged sideways between two off-center stems.


Before: PROJECT is left-aligned and the edge jogs left halfway down. After: PROJECT is centered under INSTALL and one straight vertical edge joins them.
A C4 empty boundary frame ran past the right edge of the SVG
An empty deployment node got its default 216px width after the diagram width was measured, so its frame was drawn partly outside the SVG and clipped at the edge.
Before: the Empty frame is cut off at the right edge. After: the SVG is wider and the frame is fully drawn. mermaid 12.2.0 rejects this input, so this shows only what our output does.
Disconnected pairs with wide edge labels crashed the layout
ELK’s post-compaction pass threw Invalid hitboxes for scanline constraint calculation when a graph had two or more components with wide inline edge labels, so nothing rendered. The layout now retries once without post-compaction.
Before: an error instead of a diagram. After: four labelled pairs.
An ASCII fan-out arrowhead sat one column off its stem
Under an even-width box, the bundled arrowhead took its column from the box’s own centre while the stem was drawn in the middle grid column’s centre, so the head landed one column right of the line feeding it.


Before: the ▼ under G is shifted one column from the vertical line above it. After: head and stem line up.
Node text was unreadable on a dark classDef/style fill
A node with a custom fill but no explicit color always got the theme’s text colour, so dark text could land on a dark fill (and light text on a light one).
Before: both labels use the same theme text colour, so one of them is hard to read. After: each label is black or white, picked from its own fill. An explicit color still wins.
SVG coordinates carried float noise like 142.38427299999998
Layout and curve maths wrote full floating-point precision into every coordinate, bloating the SVG for no visible benefit. Numbers are now rounded to 2 decimal places and a non-finite value is written as 0, not NaN.
<polyline class="edge" data-from="A" data-to="B" data-style="solid" data-arrow-start="false" data-arrow-end="true" points="154.91878,76.9 154.91878,124.9" fill="none" stroke="var(--_line)" stroke-width="1" marker-end="url(#arrowhead)" />
<polyline class="edge" data-from="A" data-to="B" data-style="solid" data-arrow-start="false" data-arrow-end="true" points="154.92,76.9 154.92,124.9" fill="none" stroke="var(--_line)" stroke-width="1" marker-end="url(#arrowhead)" />
The picture is the same, so this shows the first edge’s markup. Before: points carries long decimal tails. After: at most 2 decimal places.
ASCII put a node in the row of the parent that placed it first
A node took the level of whichever parent placed it first and was never revisited, so with A --> D and B --> D it landed beside B and B --> D ran sideways. Nodes are now ranked by longest path, with back edges ignored, as mermaid does.
┌────────────────────────┐ ab ┌─────────┐
│ │ │ │
│ A ├◄long─label───┐ └──┬────┐
│ │ │ │ │
└────────────┬┬──────────┘ │ │ │
▲││ │ │ │
long label │││ │ │ │
│└┼─────────────────┬───────┼────┤ │
│ │ x │ │ │ │
│ ▼ │ ▼ mid │
┌───────────┴────────────┐ │ ┌───┐ │ ┌─┴─┐
│ │ │ │ │ │ │ │
│ B ├──────┤ │ C ├──┼─►│ D │
│ │ │ │ │ │ │ │
└────────────────────────┘ │ └───┘ │ └───┘
│ │ ab
└────────────┘┌────────────────────────┐
│ ├──long label───┐
│ A │ │
│ │◄────ab──────┬─┼─────┐
└─────────────┬─┬────────┘ │ │ │
▲ │ │ │ │ │
long label │ │ │ │ │ │
│ │ └───────────────┐ │ │ │
│ │ │ │ │ x
│ ▼ x │ │ ▼ │
┌───────────┴────────────┐ │ ┌┴──┐ │
│ │ │ │ │ │
│ B │ │ │ C │ │
│ │ │ │ │ │
└───────────┬────────────┘ ab └───┘ │
│ │ │
mid │ │
│ │ │
│ ┌─────────────────┘ │
▼ ▼ │
┌────────────────────────┐ │
│ │ │
│ D ├─────────────────────┘
│ │
└────────────────────────┘Before: D sits in the same row as B. After: D is below B and B --> D drops straight down.
Leaves of labelled edges drifted a layer past their siblings
With inline edge labels, ELK’s post-compaction slid a sink into the gap the label layer leaves, one layer past its siblings. mermaid keeps the sink level with them.
Before: Go outside hangs below Have umbrella?. After: it sits level with it, as in mermaid 12.1.0 and 12.2.0.
A class diagram’s parent was not centred over its children
Class layouts did not set ELK’s balanced node placement, which flowcharts and ER diagrams already did, so a parent sat off-centre from its children.
Before: Animal is off to one side. After: it is centred above Dog and Cat.
Five or more parallel ASCII edges merged their arrowheads
Each parallel edge gets its own cell on a node’s face, but a default box face has three cells, so past that the extra edges shared one arrowhead. The face is now lengthened to one cell per edge, and cluster exits handle six siblings.
┌───────┐
│ ├──────────────────────────┐
│ A ├───────────┬───────┐ │
│ ├────┐ │ │ │
└───┬───┘ │ │ │ │
│ │ │ │ │
│ │ │ │ │
first second third fourth fifth
│ │ │ │ │
▼ │ │ │ │
┌───────┐ │ │ │ │
│ │◄───┘ │ │ │
│ B │◄──────────┴───────┘ │
│ │◄─────────────────────────┘
└───────┘┌───────┐
│ ├──────────────────────────┐
│ A ├───────────────────┐ │
│ ├───────────┐ │ │
│ ├────┐ │ │ │
└───┬───┘ │ │ │ │
│ │ │ │ │
│ │ │ │ │
first second third fourth fifth
│ │ │ │ │
▼ │ │ │ │
┌───────┐ │ │ │ │
│ │◄───┘ │ │ │
│ B │◄──────────┘ │ │
│ │◄──────────────────┘ │
│ │◄─────────────────────────┘
└───────┘Before: fewer arrowheads than edges. After: one arrowhead per labelled edge.
ASCII forward and back edge groups shared faces and merged heads
In TD/BT, the forward and back edge groups between one pair use the same side faces but were sized separately, so with more than three edges in all two arrowheads merged. A lone cluster exit group and eight or more cluster siblings had the same problem.
┌────────┐
│ ├────────────────────┬───────┐
│ A │◄───────────┐ │ │
│ ├────┐ │ │ │
└─────┬──┘ │ │ │ │
▲ │ │ b2 │ │
b1 │ │ │ │ │
│ │ │ │ │ │
│ │ │ │ │ │
│ │ │ │ │ │
│f1 f2 │ │ │
│ ▼ │ │ f3 f4
┌───┴────┐ │ │ │ │
│ │◄───┘ │ │ │
│ B ├────────────┘ │ │
│ │◄───────────────────┴───────┘
└────────┘┌────────┐
│ ├────────────────────────────┐
│ A ├────────────────────┐ │
│ │◄───────────┐ │ │
│ ├────┐ │ │ │
└─────┬──┘ │ │ │ │
▲ │ │ b2 │ │
b1 │ │ │ │ │
│ │ │ │ │ │
│ │ │ │ │ │
│ │ │ │ │ │
│f1 f2 │ │ │
│ ▼ │ │ f3 │
┌───┴────┐ │ │ │ f4
│ │◄───┘ │ │ │
│ B ├────────────┘ │ │
│ │◄───────────────────┘ │
│ │◄───────────────────────────┘
└────────┘Before: two arrowheads merge on a shared face. After: each of the six edges ends in its own head.
ASCII back-edges looped round the side faces, unlike mermaid
A TD back-edge left and re-entered a node’s right face (LR: the bottom face). mermaid 12.1.0 draws it on the flow-axis faces: out of the source’s top, up a lane beside the column, into the target’s bottom, with its own port and arrowhead.


Before: the back-edge hooks round the right side into a’s right face. After: it leaves c’s top and enters a’s bottom.
ASCII subgraph order and feedback edges followed mermaid 11, not 12
In the CI/CD pipeline the ASCII layout put Fix & Retry at the very bottom, below QA Approved?; mermaid 12.1.0 and 12.2.0 draw it beside Build Image. Sibling subgraphs tied by a shared parent came out in reversed order (West left of East); mermaid 12 keeps declaration order.


Before: Fix & Retry hangs below QA Approved?. After: it sits beside Build Image, and the QA No edge rises into it.
An ER attribute with two keys lost one of them
An attribute written PK, FK showed only FK, and PK,FK showed no key at all. Mermaid shows PK,FK for both spellings.
Before: row a shows only FK and row b shows no key. After: both show PK,FK.
ER style, classDef and class were ignored
An ER entity given style, classDef plus class, or the ::: shorthand rendered unstyled, although mermaid paints it.
Before: both entities use the theme colours. After: CUSTOMER is blue and ORDER is red.
ER ASCII edges crossed without a junction and labels were ambiguous
In ASCII, crossing ER edges drew no junction, a label could sit between two verticals or against another edge’s corner, and an edge could run along its box’s border.


Before: a plain │ where two edges cross and labels beside two lines. After: a ┼ at the crossing and each label on or beside its own edge.
ASCII dense duplicate edges and self-loops shared one cell
A node’s side face was sized for reciprocal and duplicate edges only. Self-loop ends and back-edges also take a cell, so the face came out short and two edge ends shared a cell: merged arrowheads and ├/┤ tick marks.


Before: arrowheads merge and tick marks are lost where edges share a cell. After: each edge end has its own cell.
Sequence `rect <color>` highlights were drawn as labelled frames
The colour after rect was parsed as a block label, so it was never applied and its text was printed in a frame header (rect [rgb(255,0,0)]) instead of painting the background.
Before: a grey frame with the colour text in its header tab. After: a purple background behind the two messages, with no frame.
ASCII edge labels lost the spaces between their words
An edge label drawn over a stroke treated its spaces as transparent, so the stroke showed through the gap between words: long label printed as long─label, altering the user’s text.


Before: the label on A’s right side reads long─label, with a stroke through the gap. After: it reads long label.
Mermaid entity codes in labels printed literally
Labels written with Mermaid entity codes (#quot;, #lt;, #gt;, #35;) showed the raw code instead of the character, so a label meant to read “Say” <hi> came out as #quot;Say#quot; #lt;hi#gt;. Ported from upstream beautiful-mermaid #158 (thiccyoda).
Before: the literal text #quot;Say#quot; #lt;hi#gt; and x #35;1. After: "Say" <hi> and x #1.
Nested sequence blocks were drawn stacked, not nested, in ASCII
A block inside another block (alt containing loop, opt) was laid out as if it were a sibling: its header and walls landed on the container’s walls, so the nesting was unreadable.


Before: the inner loop and opt headers collide with the alt frame. After: each sits inside the alt, one column clear of its walls.
XY chart bar series were drawn side by side, not overlapping
Two bar series on the same categories were split into narrow adjacent bars, while mermaid 12.2.0 draws each series centred on the category with later series on top.
Before: each quarter shows two thin bars next to each other. After: the two bars share one centred, full-width slot and overlap.
ASCII opposing edges merged into one route at a diamond
Two edges between the same pair (B -->|no| D and D --> B) in a left-to-right chart drew a single merged route when B was a diamond or D a parallelogram, so the loop-back looked like one two-way line.
╱────────────╱ ╱╲ ╲──────────────╲
╱ ╱ ╱ ╲ ╲ ╲
╱ Read Input ╱──────►< Valid? >─yes──►╲ Write Output ╲
╱ ╱ ╲ ╱ ╲ ╲
╱────────────╱ ╲╱ ╲──────────────╲
▲
│
│
│
│ ╱──────────────╱
│ no ╱ ╱
└────────►──╱ Ask Again ╱
╱ ╱
╱──────────────╱ ╲──────────────╲
╲ ╲
┌───►╲ Write Output ╲
│ ╲ ╲
╱────────────╱ ╱╲ │ ╲──────────────╲
╱ ╱ ╱ ╲ yes
╱ Read Input ╱──────►< Valid? >──┤
╱ ╱ ╲ ╱ ▲ │
╱────────────╱ ╲╱ │ no ╱──────────────╱
│ └────►╱ ╱
│ ╱ Ask Again ╱
└──────╱ ╱
╱──────────────╱Before: one stroke leaves the diamond’s bottom vertex. After: no leaves the right corner and drops into the parallelogram; the return runs under it into the diamond’s lower-right face, without crossing.