Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
228fcf0
test(flowchart): add regression coverage for numeric subgraph ids
bayaCh05 Apr 13, 2026
9bae628
fix(tidy-tree): keep mindmap edges connected to a non-circular root
knsv-bot Apr 20, 2026
0087740
fix(tidy-tree): type PositionedEdge.points to unblock CI type build
knsv Apr 20, 2026
a91ee87
feat: add nested namespace support for class diagrams
M-a-c Apr 11, 2026
1574f2d
docs: add nested namespace documentation and changeset
M-a-c Apr 11, 2026
967d1fb
fix: use short name as label for nested namespaces
M-a-c Apr 11, 2026
e141467
feat: support custom labels on namespaces
M-a-c Apr 11, 2026
cbc9641
test: add Cypress E2E snapshot tests for nested namespaces
M-a-c Apr 13, 2026
0d8a613
chore: add comment clarifying namespaceStack push ordering
M-a-c Apr 13, 2026
bb7afc7
feat: add hierarchicalNamespaces config option for compact rendering
M-a-c Apr 17, 2026
bed5f37
flowchart: add datastore shape
aruncveli Mar 21, 2026
aa56d9e
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 21, 2026
efdb408
add docs and changeset
aruncveli Mar 22, 2026
15b16ec
fix handDrawn look
aruncveli Mar 28, 2026
81441b9
address suggestions from comments
aruncveli Apr 13, 2026
3ebdbf9
fix(class): Self-referential class multiplicity labels rendered multi…
Gaston202 Apr 4, 2026
fb9405b
fix(class): Keep relationship title on self-referential edges
Gaston202 Apr 4, 2026
9f252c3
fix(class): avoid duplicate labels on self-referential edges
Gaston202 Apr 7, 2026
3253388
fix(stateDiagram): prevent end note detection inside text
biiab Mar 16, 2026
475d303
add changeset
biiab Mar 23, 2026
f6971e3
test(stateDiagram): improve note parsing edge case tests
biiab Apr 10, 2026
1b083cd
feat(eventmodeling): enforce Event Modeling connection invariants via…
yordis Apr 7, 2026
87923a3
chore(deps): update dependency dompurify to v3.4.0 [security]
renovate[bot] Apr 16, 2026
4ff8cf0
fix(eventmodeling): avoid shipping pre-release screen terminology
yordis Apr 16, 2026
9122dae
7604: Fix for the default config
knsv Apr 20, 2026
b0bfa95
[autofix.ci] apply automated fixes
autofix-ci[bot] Apr 20, 2026
d088b87
fix(stateDiagram): enforce strict comment syntax
RodrigojndSantos Mar 23, 2026
0c9f43b
fix(stateDiagram): enforce strict comment syntax
RodrigojndSantos Apr 9, 2026
50bd924
fix(stateDiagram): allow inline comments and fix single % parsing
RodrigojndSantos Apr 19, 2026
248d784
First version of scoped e2e tests
pbrolin47 Apr 9, 2026
a53ae38
Added info about scoped tests in doc
pbrolin47 Apr 10, 2026
c6ec19d
refactor(e2e): organise spec files into diagram subfolders
pbrolin47 Apr 15, 2026
93f2517
refactor(e2e): replace CROSS_CUTTING_SPECS list with positional conve…
pbrolin47 Apr 15, 2026
1d3f143
Fix lint errors
pbrolin47 Apr 15, 2026
0df4e00
Corrected import paths
pbrolin47 Apr 15, 2026
4c4a88c
Test feature
pbrolin47 Apr 17, 2026
5916cec
Changed to correct branch for test
pbrolin47 Apr 17, 2026
959fbd2
Corrected error
pbrolin47 Apr 17, 2026
caa2847
Lint
pbrolin47 Apr 17, 2026
6f41334
New test
pbrolin47 Apr 17, 2026
de21f52
Remove comment
pbrolin47 Apr 21, 2026
2b9bbf4
Remove hard coding for test no longer in use
pbrolin47 Apr 21, 2026
f43b011
test(flowchart): add exact repro test for numeric subgraph bug (#7609)
bayaCh05 Apr 21, 2026
cb5ae96
fix(flowchart): handle nested numeric subgraph ids in dagre (#7609)
bayaCh05 Apr 21, 2026
3973356
Merge branch 'develop' into fix/numeric-subgraph-ids
bayaCh05 May 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cold-mails-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@mermaid-js/parser': major
---

enforce eventmodeling connection invariants via Langium validator
5 changes: 5 additions & 0 deletions .changeset/cold-snakes-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mermaid': patch
---

fix: updated the parser to require a new line boundary before the "end note" keyword to ensure it is only matched when used as an actual note terminator.
7 changes: 7 additions & 0 deletions .changeset/eager-cobras-slide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'mermaid': minor
---

feat: add datastore shape for flowchart

In Data flow diagrams, a datastore/warehouse/file/database is used to represent data persistence. It is denoted by a rectangle with top and bottom border. This change adds that as a shape in flowcharts.
5 changes: 5 additions & 0 deletions .changeset/feat-nested-namespaces.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mermaid': minor
---

feat: add nested namespace support for class diagrams via dot notation and syntactic nesting
7 changes: 7 additions & 0 deletions .changeset/fix-7560-self-referential-multiplicity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'mermaid': patch
---

fix: self-referential class multiplicity labels no longer rendered multiple times

Fixes #7560. Resolves an issue where cardinality labels on self-referential class relationships were rendered three times due to edge splitting in the dagre layout. The fix ensures that each sub-edge only carries its relevant label positions.
6 changes: 6 additions & 0 deletions .changeset/fix-mindmap-tidy-tree-root-edges.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@mermaid-js/layout-tidy-tree': patch
'mermaid': patch
---

fix: keep mindmap edges connected to a non-circular root when using the tidy-tree layout
6 changes: 6 additions & 0 deletions .changeset/three-melons-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'mermaid': major
'@mermaid-js/parser': major
---

fix: replace Event Modeling `screen` / `scn` terminology with `ui`
5 changes: 5 additions & 0 deletions .changeset/yummy-moose-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'mermaid': patch
---

fix: updated the parser to only treat comments starting with %%.
43 changes: 9 additions & 34 deletions cypress/integration/rendering/architecture/architecture.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,6 @@ describe('architecture diagram', () => {
service disk1(disk)[Storage] in api
service disk2(disk)[Storage] in api
service server(server)[Server] in api
service gateway(internet)[Gateway]

db:L -- R:server
disk1:T -- B:server
disk2:T -- B:db
server:T -- B:gateway
`
);
});
it('should render a simple architecture diagram with titleAndAccessibilities', () => {
imgSnapshotTest(
`architecture-beta
title Simple Architecture Diagram
accTitle: Accessibility Title
accDescr: Accessibility Description
group api(cloud)[API]

service db(database)[Database] in api
service disk1(disk)[Storage] in api
service disk2(disk)[Storage] in api
service server(server)[Server] in api

db:L -- R:server
disk1:T -- B:server
disk2:T -- B:db
`
);
});
it('should render an architecture diagram with groups within groups', () => {
imgSnapshotTest(
`architecture-beta
group api[API]
group public[Public API] in api
group private[Private API] in api

service serv1(server)[Server] in public

Expand Down Expand Up @@ -73,6 +39,7 @@ describe('architecture diagram', () => {
service serv1(server)[Server 1]
service serv2(server)[Server 2]
service disk(disk)[Disk]


db:L -- R:s3
serv1:L -- T:s3
Expand All @@ -89,11 +56,13 @@ describe('architecture diagram', () => {
service servR(server)[Server 3]
service servT(server)[Server 4]
service servB(server)[Server 5]


servC:L --> R:servL
servC:R --> L:servR
servC:T --> B:servT
servC:B --> T:servB


servL:T --> L:servT
servL:B --> L:servB
Expand All @@ -110,12 +79,14 @@ describe('architecture diagram', () => {
group top_group(cloud)[Top]
group bottom_group(cloud)[Bottom]
group center_group(cloud)[Center]


service left_disk(disk)[Disk] in left_group
service right_disk(disk)[Disk] in right_group
service top_disk(disk)[Disk] in top_group
service bottom_disk(disk)[Disk] in bottom_group
service center_disk(disk)[Disk] in center_group


left_disk{group}:R --> L:center_disk{group}
right_disk{group}:L --> R:center_disk{group}
Expand All @@ -132,11 +103,13 @@ describe('architecture diagram', () => {
service servR(server)[Server 3]
service servT(server)[Server 4]
service servB(server)[Server 5]


servC:L -[Label]- R:servL
servC:R -[Label]- L:servR
servC:T -[Label]- B:servT
servC:B -[Label]- T:servB


servL:T -[Label]- L:servT
servL:B -[Label]- L:servB
Expand All @@ -155,6 +128,7 @@ describe('architecture diagram', () => {
service bottom_gateway(internet)[Gateway]
junction juncC
junction juncR


left_disk:R -- L:juncC
top_disk:B -- T:juncC
Expand Down Expand Up @@ -280,6 +254,7 @@ describe('architecture diagram', () => {
web:B --> T:pe2
pe2:R --> L:bus
vm1:R --> L:pe2
`
`,
{ architecture: { randomize: false } }
);
Expand Down
100 changes: 100 additions & 0 deletions packages/mermaid/src/mermaidAPI.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1250,4 +1250,104 @@ flowchart TD
}
);
});

describe('flowchart numeric subgraph ids', () => {
beforeEach(() => {
mermaidAPI.globalReset();
mermaid.initialize({
startOnLoad: false,
deterministicIds: true,
deterministicIDSeed: '',
flowchart: { htmlLabels: false },
});
});

jsdomIt('renders a flowchart with a numeric subgraph id', async () => {
const diagramText = `flowchart LR
subgraph 1 ["inner"]
A
end`;

await expect(mermaidAPI.render('numeric-subgraph-id', diagramText)).resolves.toMatchObject({
svg: expect.any(String),
});
});

jsdomIt('renders nested flowcharts with numeric subgraph ids', async () => {
const diagramText = `flowchart LR
subgraph 1 ["outer"]
subgraph 2 ["inner"]
A --> B
end
end
B --> C`;

await expect(
mermaidAPI.render('nested-numeric-subgraph-id', diagramText)
).resolves.toMatchObject({
svg: expect.any(String),
});
});

jsdomIt('renders a flowchart when a numeric subgraph id has an outgoing edge', async () => {
const diagramText = `flowchart LR
subgraph 1 ["outer"]
subgraph 2 ["inner"]
A --> B
end
end
1 --> C`;

await expect(mermaidAPI.render('numeric-subgraph-edge', diagramText)).resolves.toMatchObject({
svg: expect.any(String),
});
});

jsdomIt('renders the alphabetic equivalent of the numeric subgraph edge case', async () => {
const diagramText = `flowchart LR
subgraph a ["outer"]
subgraph b ["inner"]
A --> B
end
end
a --> C`;

await expect(
mermaidAPI.render('alphabetic-subgraph-edge', diagramText)
).resolves.toMatchObject({
svg: expect.any(String),
});
});

jsdomIt(
'reproduces issue #7609 with the exact graph LR repro through the Dagre render path',
async () => {
mermaid.initialize({
startOnLoad: false,
deterministicIds: true,
deterministicIDSeed: '',
flowchart: { htmlLabels: false, defaultRenderer: 'dagre-wrapper' },
});

// Regression coverage only: this preserves the exact repro from #7609 and documents
// the current Dagre failure instead of claiming the numeric subgraph bug is fixed.
const diagramText = `graph LR
subgraph outer
subgraph 1 ["inner"]
external
subgraph sub
internal
end
sub-->external
end
end`;

const { svg } = await mermaidAPI.render('numeric-subgraph-issue-7609', diagramText);

expect(svg).toContain('class="cluster');
expect(svg).toContain('>inner<');
expect(svg).toContain('>external<');
}
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,21 @@ const edgeInCluster = (edge, clusterId) => {
);
};

const edgeLeavesCluster = (edge, clusterId) => {
const fromDescendant = isDescendant(edge.v, clusterId);
const toDescendant = isDescendant(edge.w, clusterId);

if (edge.v === clusterId) {
return !toDescendant;
}

if (edge.w === clusterId) {
return !fromDescendant;
}

return fromDescendant !== toDescendant;
};

const copy = (clusterId, graph, newGraph, rootId) => {
log.warn(
'Copying children of ',
Expand Down Expand Up @@ -247,10 +262,7 @@ export const adjustClustersAndEdges = (graph, depth) => {
if (children.length > 0) {
log.debug('Cluster identified', id, descendants);
edges.forEach((edge) => {
const d1 = isDescendant(edge.v, id);
const d2 = isDescendant(edge.w, id);

if (d1 ^ d2) {
if (edgeLeavesCluster(edge, id)) {
log.warn('Edge: ', edge, ' leaves cluster ', id);
log.warn('Descendants of XXX ', id, ': ', descendants.get(id));
clusterDb.get(id).externalConnections = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,38 @@ describe('Graphlib decorations', () => {
expect(cGraph.nodes().length).toBe(1);
expect(bGraph.edges().length).toBe(0);
});

it('adjustClustersAndEdges should rewrite edges from nested cluster nodes to descendant anchors GLB78', function () {
/*
subgraph outer
subgraph 1 [inner]
external
subgraph sub
internal
end
sub --> external
end
end
*/
g.setNode('outer', { data: 1 });
g.setNode('1', { data: 2 });
g.setNode('sub', { data: 3 });
g.setNode('external', { data: 4 });
g.setNode('internal', { data: 5 });
g.setParent('1', 'outer');
g.setParent('external', '1');
g.setParent('sub', '1');
g.setParent('internal', 'sub');
g.setEdge('sub', 'external', { data: 'link1' }, '1');

adjustClustersAndEdges(g);

const outerGraph = g.node('outer').graph;
const innerGraph = outerGraph.node('1').graph;

expect(innerGraph.edges()).toEqual([{ v: 'internal', w: 'external', name: '1' }]);
expect(validate(innerGraph)).toBe(true);
});
});
it('adjustClustersAndEdges should handle nesting GLB77', function () {
/*
Expand Down
11 changes: 11 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions scripts/e2e-diagram-scope.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ export function detectScope(files, options = {}) {
continue;
}

// Anything else (root config, CI YAML, docs, cypress/other, etc.) → full suite
// Ignorable files (docs, changesets, AI config, etc.) → skip silently.
// Guard: .md files inside a diagram source folder are NOT ignorable — they
// may be samples or signal intent, and their diagram folder was already
Expand Down
Loading