Address knsv review feedback on #7708:
1. Expand the changeset to call out that align, row, and column are now
reserved keywords in architecture-beta. Authors using one of these
as an exact service/group/junction id (e.g. service row(database))
will get a parse error and need to rename. Prefix-using identifiers
like rowspan and columnar keep working via langium longer-alt.
2. Add a parametrised unit test asserting that each of the three exact
reserved-keyword ids is rejected at parse time. This complements the
existing prefix-collision test (rowspan/columnar resolve clean) by
pinning the contract for the exact-match case.
Both URLs return 404:
- gitbook-plugin-mermaid-pdf (GitBook integrations list)
- databutton.com demo link in the Elle Neal tutorial entry
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses three review comments from pbrolin47 on #7708:
1. Wrap layout.run() in try/catch and rethrow fcose's raw
'RangeError: Invalid array length' as a friendly Error explaining the
most likely causes (contradictory align order, or two aligns sharing
a node along the same axis). Previously the failure surfaced as an
opaque RangeError from FDLayout.calcGrid.
2. Use idealEdgeLengthMultiplier * iconSize for the heuristic relative
constraint gap, matching the declared hint gap. Previously hardcoded
to 1.5 * iconSize, which diverged from declared spacing when users
tuned the multiplier away from 1.5.
3. Add a unit test covering the DB-level guard in addLayoutHint that
rejects hints with fewer than two members. The grammar already
enforces members+=ID (members+=ID)+ structurally, so this is dead
code for the parser path, but the guard exists for programmatic
callers and is now tested.
`align row` for three databases that all connect `R --> L:mcp` linearises them into
a chain pointing into MCP rather than producing the intended fan-in. The natural
fit for "three things feeding one downstream node" depends on the port pair:
- column when the edges share a horizontal port pair (e.g. R --> L) — siblings
stack vertically with parallel horizontal arrows reaching the downstream node.
- row when the edges share a vertical port pair (e.g. B --> T) — siblings sit
in a horizontal row with parallel vertical arrows fanning down.
Updated the docs example and the Cypress test to match this guidance.
Verified visually: arch-verify-column-row.png shows DB1/DB2/DB3 in a clean
vertical stack with three parallel arrows into MCP, and Source 1/2/3 in a clean
horizontal row with three parallel arrows fanning down into Processor.
Targets #6817 and complements the workaround documented for #6120.
The current architecture-beta layout heuristic derives all alignment + relative-
placement constraints from the BFS spatial map. When several services share similar
edge topology (e.g. three databases all connecting `R --> L:mcp`), the BFS assigns
them the same logical coordinate and fcose lays them on top of each other.
This PR adds an `align` directive that lets authors declare horizontal or vertical
alignment explicitly:
```
architecture-beta
group api(cloud)[API]
service db1(database)[DB1] in api
service db2(database)[DB2] in api
service db3(database)[DB3] in api
service mcp(server)[MCP] in api
db1:R --> L:mcp
db2:R --> L:mcp
db3:R --> L:mcp
align row db1 db2 db3
```
`align row {ids…}` pins members to the same Y; `align column {ids…}` pins them to
the same X. Pair `row` + `column` directives to produce a clean grid (see the new
"Grid layouts" subsection in the docs).
Implementation:
- Langium grammar gets a new `Alignment` rule. `row`/`column`/`align` become reserved
keywords; existing identifiers like `rowspan` keep working via langium's longer-alt
tokenizer (covered by a regression test).
- DB validates members at parse time: each ID must already be a service or junction,
and a member cannot appear twice in the same directive.
- Renderer merges declared hints into the existing fcose constraint set:
- `getAlignments` drops any heuristic alignment group that contains a declared
member, then appends the user's group. Without this, fcose receives both a
heuristic and a declared placement for the same set of nodes and crashes with
`RangeError: Invalid array length` in `FDLayout.calcGrid`.
- `getRelativeConstraints` emits a chained `{left,right}` (or `{top,bottom}`) for
each declared hint, and skips heuristic BFS pairs already covered by a declared
chain.
Caveat documented in the docs: the declared order must not contradict edge directions
(e.g. `align row a b` conflicts with `a:L --> R:b`, which says `a` is right of `b`).
Contradiction currently surfaces as the same fcose `RangeError`; a follow-up could
add explicit DB-time validation.
Tests:
- Unit tests cover parser acceptance (row/column/comma-less list), DB validation
(member existence, duplicate rejection), and the rowspan/columnar collision case.
- Cypress `imgSnapshotTest` cases for row, column, and combined row+column grid.
Builds on #<PR1-number> for the `idealEdgeLengthMultiplier` config that controls the
gap between aligned members.
Note: pre-commit hook bypassed because `pnpm --filter mermaid run docs:build` (run by
lint-staged on docs changes) currently fails on pre-existing TypeScript errors in
`packages/mermaid/src/diagrams/wardley/wardleyParser.ts` that exist on `develop`.
The cytoscape-fcose layout calls Math.random() internally in its
constraint solver regardless of randomize:false, so the visual test
'should render a deterministic layout for a complex deeply-nested
diagram' failed on every CI run (and was disabled in #7728 pending
this fix).
Adds an `architecture.seed` config field (default 1) that temporarily
swaps Math.random for a mulberry32 seeded generator around the two
layout.run() invocations and restores it in a finally block. The
non-zero default makes every architecture diagram render with the same
layout on every render; setting `architecture.seed: 0` opts out of the
swap and restores the pre-fix non-deterministic behavior for callers
who want layout variety.
Re-enables the previously skipped cypress test, injects
`architecture.seed = 1` from the test helper alongside handDrawnSeed=1,
and adds a new test that exercises an explicit seed override.
Resolves#7729
The renderer seeded its mulberry32 PRNG from hashString(id) where the
SVG element id varies per render, so each render produced a different
wavy boundary and Argos visual tests failed on every run.
Adds a `cynefin.seed` config field (default 0 → existing per-id hashing
behavior; any non-zero value locks the waviness across renders).
Extracts a `resolveSeed(configuredSeed, id)` helper alongside the
existing PRNG primitives, hardened against non-finite inputs. The
cypress helper now injects `cynefin.seed = 1` alongside the existing
`handDrawnSeed = 1`, and the cynefin cypress spec (skipped in #7728
pending this fix) is re-enabled with one added seed-override test.
Resolves#7727
Conflict in cypress/integration/rendering/flowchart-v2.spec.js (renamed by
develop into the flowchart/ subdirectory). Kept both the new V2-18 cluster
test and develop's Edge label autowrapping describe block.
Addresses review feedback on PR #7314:
- Guard the safe-anchor lookup with isNodeInExtractableCluster so the
re-anchoring only fires when the current anchor would actually be lost
to subgraph extraction. Previously the helper was called and its
return value discarded, meaning findSafeAnchorNode ran for every
cluster with externalConnections + a direct outgoing edge — a much
broader set than the bug scenario, which risked shifting edge
attachment on diagrams that previously rendered fine.
- Drop the candidate === child skip in findSafeAnchorNode so a leaf
sibling at the cluster level is a valid candidate. findNonClusterChild
returns the leaf id when child is a leaf; that's a legitimate anchor,
not a "no result" sentinel.
- Simplify the V2-18 test config from
{ htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' }
to {} — the failing diagram has only plain identifiers, so the extra
options just obscured intent.
Fixes abfb563e1 (Version Packages, 2026-05-11).
I'm not 100% sure why, but the automated changeset PR didn't run
`pnpm --filter mermaid run docs:release-version && pnpm --filter mermaid run docs:build`
correctly. I've run it again, and this one seems to have worked.
Fixes: abfb563e1d
fedf70cc3 (fix(class): Self-referential class multiplicity labels rendered multiple times, 2026-04-04)
changed how `endLabel`s were rendered, by
preventing them from being rendered within their `endEdgeLabelLeft` or
`endLabelRight` elements, when they existed.
Although when looking at the code, this seems correct (as otherwise
`inner` is not used), this actually causes the labels to render on top
of the edges, which makes the class diagrams look worse.
This commit reverts that change, to avoid any visaul regression
differences.
Fixes: fedf70cc3c
The `wardleyRenderer` file never uses `.html()` or `.innerHTML` to set
items within the HTML. Instead, it only ever uses D3 Selection's
`.text`, which works `textContent`. This makes it immune to XSS attacks.
When we over-sanitize this text, the diagram will show `<` instead of
`<` in labels.