Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
281cd7b
fix: class diagram markers no longer scale with edge stroke-width
ashishjain0512 Aug 19, 2026
3333ad6
Merge pull request #8079 from mermaid-js/class-diagram-redux-marker
pbrolin47 Aug 19, 2026
c66200b
fix(elk): build markers with the host instance's config
ashishjain0512 Aug 20, 2026
8265aa5
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 20, 2026
31ce60a
fix(c4): wrap element labels to c4.width again
knsv Aug 20, 2026
246de0a
Merge pull request #8088 from mermaid-js/fix/elk-neo-marker-theme-config
knsv Aug 20, 2026
9bdac28
Merge remote-tracking branch 'origin/master' into hotfix/11.17.1
knsv Aug 20, 2026
876e5dd
fix(c4): wrap element labels to c4.width again
knsv Aug 20, 2026
adba7fe
Merge branch 'hotfix/11.17.1' of github.com:mermaid-js/mermaid into h…
knsv Aug 20, 2026
1a96110
fix(dev): dedupe @codemirror/state so the dev explorer editor loads
knsv Aug 20, 2026
6bae15e
Updated tests to Playwright API
pbrolin47 Aug 20, 2026
b433a9a
Updated changeset to describe specifik diagram affected
pbrolin47 Aug 20, 2026
412c80a
Update doc and consistent handlig in c4 as for seq diags
pbrolin47 Aug 21, 2026
8a23480
Updated doc from feedback
pbrolin47 Aug 21, 2026
655211a
Reverted change of wrap-options
pbrolin47 Aug 24, 2026
5b17e0a
Merge pull request #8092 from mermaid-js/hotfix/11.17.1
pbrolin47 Aug 24, 2026
3054836
Version Packages
github-actions[bot] Aug 24, 2026
569f46e
Version Packages (#8114)
pbrolin47 Aug 24, 2026
178d7c7
fix: restore edgePaths class on the edge group (#8125)
knsv-bot Aug 25, 2026
dcb694d
Version Packages (#8130)
github-actions[bot] Aug 25, 2026
41adaee
Merge branch 'develop' into sync/merge-back-11.17.2
ashishjain0512 Aug 25, 2026
0677067
test(e2e): point swimlanes edge selector at g.edgePaths
ashishjain0512 Aug 25, 2026
b82c00c
fix(agentflow): point the edge stroke rule at .edgePaths
ashishjain0512 Aug 25, 2026
96a4e35
Potential fix for pull request finding 'CodeQL / Incomplete string es…
ashishjain0512 Aug 25, 2026
b7653bf
[autofix.ci] apply automated fixes
autofix-ci[bot] Aug 25, 2026
5f9cea7
Merge pull request #8132 from mermaid-js/sync/merge-back-11.17.2
ashishjain0512 Aug 25, 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
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

> **clearLayoutRenderState**(): `void`

Defined in: [packages/mermaid/src/rendering-util/layout-algorithms/common/index.ts:197](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/layout-algorithms/common/index.ts#L197)
Defined in: [packages/mermaid/src/rendering-util/layout-algorithms/common/index.ts:207](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/layout-algorithms/common/index.ts#L207)

## Returns

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

> **defaultMeasureLayout**(`data4Layout`, `__namedParameters`, `options?`): `Promise`<{ `graph`: `Graph`; `groups`: { `clusters`: `D3Selection`<`SVGGElement`>; `edgeLabels`: `D3Selection`<`SVGGElement`>; `edgePaths`: `D3Selection`<`SVGGElement`>; `nodes`: `D3Selection`<`SVGGElement`>; `rootGroups`: `D3Selection`<`SVGGElement`>; }; `nodeElements`: `Map`<`string`, `D3Selection`<`SVGElement` | `SVGGElement`>>; }>

Defined in: [packages/mermaid/src/rendering-util/layout-algorithms/common/index.ts:204](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/layout-algorithms/common/index.ts#L204)
Defined in: [packages/mermaid/src/rendering-util/layout-algorithms/common/index.ts:214](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/layout-algorithms/common/index.ts#L214)

## Parameters

Expand Down
2 changes: 1 addition & 1 deletion docs/config/setup/mermaid/functions/paintLayoutData.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

> **paintLayoutData**(`data4Layout`, `context`, `options`): `Promise`<`void`>

Defined in: [packages/mermaid/src/rendering-util/layout-algorithms/common/index.ts:212](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/layout-algorithms/common/index.ts#L212)
Defined in: [packages/mermaid/src/rendering-util/layout-algorithms/common/index.ts:222](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/layout-algorithms/common/index.ts#L222)

## Parameters

Expand Down
2 changes: 1 addition & 1 deletion docs/diagrams/flowchart-code-flow.mmd
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ flowchart TD
Styles --> stylesTS["styles.ts<br/>getStyles(options)"]
stylesTS --> StyleOptions["FlowChartStyleOptions<br/>- arrowheadColor, border2<br/>- clusterBkg, mainBkg<br/>- fontFamily, textColor"]

StyleOptions --> GenerateCSS["Generate CSS styles<br/>- .label, .cluster-label<br/>- .node, .edgePath<br/>- .flowchart-link, .edgeLabel"]
StyleOptions --> GenerateCSS["Generate CSS styles<br/>- .label, .cluster-label<br/>- .node, .edgePaths<br/>- .flowchart-link, .edgeLabel"]
GenerateCSS --> GetIconStyles["getIconStyles()"]

%% Type System
Expand Down
7 changes: 4 additions & 3 deletions docs/syntax/c4.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,14 +212,15 @@ UpdateRelStyle(customerA, bankA, $offsetY="60")

## Element text wrapping

Element text (name, type and description) stays on one line by default; the element sizes itself to the longest line. Set the root `wrap` config value to wrap text to the element width instead, which is set by the [`c4.width`](/config/schema-docs/config-defs-c4-diagram-config.html#width) config value:
Since v11.17.1, C4 diagrams wrap by default and can be disabled by either setting wrap or c4.wrap to false. Before v11.17.1, wrapping was disabled by default, and could be enabled by setting wrap to true (but not c4.wrap).

Example below illustrates disabling of default wrapping by use of c4.wrap.

```yaml
---
config:
wrap: true
c4:
width: 216
wrap: false
---
```

Expand Down
30 changes: 30 additions & 0 deletions e2e/rendering/class/classDiagram-neo.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,33 @@ test.describe('Class diagram — Neo look with new themes', () => {
});
});
});

// The neo/redux themes set `themeVariables.strokeWidth` to 2, which lands on `path.relation`.
// Relation markers must not scale with it, otherwise they overshoot the line-end offset and end
// up drawn behind the class box. The `look: 'neo'` cases above use the `-margin` marker variants,
// so only the default `classic` look exercises the plain markers.
test.describe('Class diagram — Classic look with new themes', () => {
themes.forEach(({ theme, label }) => {
test(`CLASSIC-1 [${label}]: should render relation markers outside the class box`, async ({
page,
}, testInfo) => {
await imgSnapshotTest(page, testInfo, diagrams.allRelationships, {
logLevel: 1,
htmlLabels: true,
theme,
});
});
});

themes.forEach(({ theme, label }) => {
test(`CLASSIC-2 [${label}]: should render cardinality with classic look`, async ({
page,
}, testInfo) => {
await imgSnapshotTest(page, testInfo, diagrams.cardinality, {
logLevel: 1,
htmlLabels: true,
theme,
});
});
});
});
5 changes: 4 additions & 1 deletion e2e/rendering/swimlanes/swimlanes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ const HANDDRAWN_FIXTURES = [
];

const shapeSelector = 'rect, polygon, ellipse, circle, path';
const edgePathSelector = 'g.edgePath path.path, g.edgePath path';
// The shared layout renderer emits the edge group as `g.edges.edgePaths`
// (see createLayoutElementGroups). Edge paths themselves carry
// `edge-thickness-* edge-pattern-* flowchart-link`, not a `.path` class.
const edgePathSelector = 'g.edgePaths path';

const asStandaloneSwimlanes = (source: string): string => {
// Every swimlanes layout-test fixture declares the standalone `swimlanes`
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
"@changesets/cli": "^2.29.8",
"@codemirror/commands": "^6.10.4",
"@codemirror/language": "^6.12.4",
"@codemirror/state": "^6.6.0",
"@codemirror/state": "^6.7.1",
"@codemirror/view": "^6.43.7",
"@cspell/eslint-plugin": "^9.3.2",
"@eslint/js": "^9.26.0",
Expand Down
18 changes: 18 additions & 0 deletions packages/mermaid/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# mermaid

## 11.17.2

### Patch Changes

- [#8125](https://github.com/mermaid-js/mermaid/pull/8125) [`178d7c7`](https://github.com/mermaid-js/mermaid/commit/178d7c79fcbafcf0662b822ec34ed989372ee5c2) Thanks [@knsv-bot](https://github.com/knsv-bot)! - fix: restore the `edgePaths` class on the edge group in rendered SVG, and point the flowchart, block and user journey stylesheets at it

## 11.17.1

### Patch Changes

- [#8092](https://github.com/mermaid-js/mermaid/pull/8092) [`31ce60a`](https://github.com/mermaid-js/mermaid/commit/31ce60a596746c76dc932ab540d910a6c7fff8be) Thanks [@pbrolin47](https://github.com/pbrolin47)! - fix(c4): wrap element labels to `c4.width` again

C4 element labels (`System`, `Container`, `Component`, `Person` and their `_Ext` variants) stopped wrapping in 11.17.0, so long descriptions rendered on one unbroken line and the shape grew sideways well past the configured `c4.width`. The unified-shapes label helper gated wrapping on the root-level `wrap` option, which has no schema default and is therefore `undefined`; it now gates on `c4.wrap` (default `true`), which is what the legacy renderer used.

- [#8088](https://github.com/mermaid-js/mermaid/pull/8088) [`c66200b`](https://github.com/mermaid-js/mermaid/commit/c66200bc2302006c908f77819c584109f50c06e7) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - fix: neo-look arrowheads and crow's-foot markers no longer fall back to default theme colours/stroke widths on the first render with `layout: elk`. State diagram arrowheads stayed dark on dark themes, and ER / requirement markers were drawn at the default stroke width, because markers were created from the layout package's own bundled copy of mermaid, whose config had not been initialized yet.

- [#8079](https://github.com/mermaid-js/mermaid/pull/8079) [`281cd7b`](https://github.com/mermaid-js/mermaid/commit/281cd7b0705a7cdf4295bfd5e3171647dc809dfb) Thanks [@ashishjain0512](https://github.com/ashishjain0512)! - fix(class): class diagram relation markers (composition, aggregation, extension, dependency, lollipop) no longer scale with the edge stroke width, so they stay outside the class box boundary in themes that set `strokeWidth: 2` (`redux`, `redux-dark`, `redux-color`, `redux-dark-color`, `neo`, `neo-dark`) with the default `classic` look.

## 11.17.0

### Minor Changes
Expand Down
2 changes: 1 addition & 1 deletion packages/mermaid/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "11.17.0",
"version": "11.17.2",
"description": "Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.",
"type": "module",
"module": "./dist/mermaid.core.mjs",
Expand Down
2 changes: 1 addition & 1 deletion packages/mermaid/src/diagrams/agentflow/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ const getStyles = (options: AgentflowStyleOptions) =>
fill: ${options.arrowheadColor};
}

.edgePath .path {
.edgePaths .path {
stroke: ${options.lineColor};
stroke-width: ${options.strokeWidth ?? 2}px;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/mermaid/src/diagrams/block/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ const getStyles = (options: BlockChartStyleOptions) =>
fill: ${options.arrowheadColor};
}

.edgePath .path {
.edgePaths .path {
stroke: ${options.lineColor};
stroke-width: 2.0px;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/mermaid/src/diagrams/flowchart/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ const getStyles = (options: FlowChartStyleOptions) =>
fill: ${options.arrowheadColor};
}

.edgePath .path {
.edgePaths .path {
stroke: ${options.lineColor};
stroke-width: ${options.strokeWidth ?? 2}px;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/mermaid/src/diagrams/user-journey/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const getStyles = (options) =>
fill: ${options.arrowheadColor};
}

.edgePath .path {
.edgePaths .path {
stroke: ${options.lineColor};
stroke-width: 1.5px;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/mermaid/src/docs/diagrams/flowchart-code-flow.mmd
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ flowchart TD
Styles --> stylesTS["styles.ts<br/>getStyles(options)"]
stylesTS --> StyleOptions["FlowChartStyleOptions<br/>- arrowheadColor, border2<br/>- clusterBkg, mainBkg<br/>- fontFamily, textColor"]

StyleOptions --> GenerateCSS["Generate CSS styles<br/>- .label, .cluster-label<br/>- .node, .edgePath<br/>- .flowchart-link, .edgeLabel"]
StyleOptions --> GenerateCSS["Generate CSS styles<br/>- .label, .cluster-label<br/>- .node, .edgePaths<br/>- .flowchart-link, .edgeLabel"]
GenerateCSS --> GetIconStyles["getIconStyles()"]

%% Type System
Expand Down
7 changes: 4 additions & 3 deletions packages/mermaid/src/docs/syntax/c4.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,14 +156,15 @@ UpdateRelStyle(customerA, bankA, $offsetY="60")

## Element text wrapping

Element text (name, type and description) stays on one line by default; the element sizes itself to the longest line. Set the root `wrap` config value to wrap text to the element width instead, which is set by the [`c4.width`](/config/schema-docs/config-defs-c4-diagram-config.html#width) config value:
Since v11.17.1, C4 diagrams wrap by default and can be disabled by either setting wrap or c4.wrap to false. Before v11.17.1, wrapping was disabled by default, and could be enabled by setting wrap to true (but not c4.wrap).

Example below illustrates disabling of default wrapping by use of c4.wrap.

```yaml
---
config:
wrap: true
c4:
width: 216
wrap: false
---
```

Expand Down
2 changes: 1 addition & 1 deletion packages/mermaid/src/rendering-util/createGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface CreateLayoutElementGroupsOptions {

export function createLayoutElementGroups(
element: D3Selection,
{ edgePathsClass = 'edges edgePath' }: CreateLayoutElementGroupsOptions = {}
{ edgePathsClass = 'edges edgePaths' }: CreateLayoutElementGroupsOptions = {}
): LayoutElementGroups {
const rootGroups = element.insert('g').attr('class', 'root');
const clusters = rootGroups.insert('g').attr('class', 'clusters');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,17 @@ export function createCommonLayoutRenderer<
options?: RenderOptions
): Promise<void> {
const element = svg.select('g') as unknown as D3Selection<SVGElement>;
insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId);
// Use the helper handed over by the host mermaid instance when there is one.
// External layout packages (elk, tidy-tree) are bundled with their own copy of
// these modules, and that copy's config module never sees `mermaid.initialize()`,
// so markers created through the statically imported `insertMarkers` read default
// theme variables instead of the diagram's.
(helpers?.insertMarkers ?? insertMarkers)(
element,
data4Layout.markers,
data4Layout.type,
data4Layout.diagramId
);
clearLayoutRenderState();

// Convenience struct containing everything you need to render
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const extension = (elem, type, id) => {
.attr('markerWidth', 20)
.attr('markerHeight', 28)
.attr('orient', 'auto')
.attr('markerUnits', 'userSpaceOnUse')
.append('path')
.attr('d', 'M 1,1 V 13 L18,7 Z'); // this is actual shape for arrowhead

Expand Down Expand Up @@ -83,6 +84,7 @@ const composition = (elem, type, id) => {
.attr('markerWidth', 190)
.attr('markerHeight', 240)
.attr('orient', 'auto')
.attr('markerUnits', 'userSpaceOnUse')
.append('path')
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');

Expand All @@ -96,6 +98,7 @@ const composition = (elem, type, id) => {
.attr('markerWidth', 20)
.attr('markerHeight', 28)
.attr('orient', 'auto')
.attr('markerUnits', 'userSpaceOnUse')
.append('path')
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');

Expand Down Expand Up @@ -141,6 +144,7 @@ const aggregation = (elem, type, id) => {
.attr('markerWidth', 190)
.attr('markerHeight', 240)
.attr('orient', 'auto')
.attr('markerUnits', 'userSpaceOnUse')
.append('path')
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');

Expand All @@ -154,6 +158,7 @@ const aggregation = (elem, type, id) => {
.attr('markerWidth', 20)
.attr('markerHeight', 28)
.attr('orient', 'auto')
.attr('markerUnits', 'userSpaceOnUse')
.append('path')
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');

Expand Down Expand Up @@ -198,6 +203,7 @@ const dependency = (elem, type, id) => {
.attr('markerWidth', 190)
.attr('markerHeight', 240)
.attr('orient', 'auto')
.attr('markerUnits', 'userSpaceOnUse')
.append('path')
.attr('d', 'M 5,7 L9,13 L1,7 L9,1 Z');

Expand All @@ -211,6 +217,7 @@ const dependency = (elem, type, id) => {
.attr('markerWidth', 20)
.attr('markerHeight', 28)
.attr('orient', 'auto')
.attr('markerUnits', 'userSpaceOnUse')
.append('path')
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
elem
Expand Down Expand Up @@ -254,6 +261,7 @@ const lollipop = (elem, type, id) => {
.attr('markerWidth', 190)
.attr('markerHeight', 240)
.attr('orient', 'auto')
.attr('markerUnits', 'userSpaceOnUse')
.append('circle')
.attr('fill', 'transparent')
.attr('cx', 7)
Expand All @@ -270,6 +278,7 @@ const lollipop = (elem, type, id) => {
.attr('markerWidth', 190)
.attr('markerHeight', 240)
.attr('orient', 'auto')
.attr('markerUnits', 'userSpaceOnUse')
.append('circle')
.attr('fill', 'transparent')
.attr('cx', 7)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { select } from 'd3';
import { describe, expect, it } from 'vitest';
import insertMarkers from './markers.js';

/**
* The class diagram relation markers must not scale with the edge's stroke-width.
*
* Themes such as `redux`/`neo` set `themeVariables.strokeWidth` to 2, which becomes the
* `stroke-width` of `path.relation`. Markers default to `markerUnits="strokeWidth"`, so any
* marker missing an explicit `markerUnits` gets drawn at twice its size and overshoots the
* line-end offset from `markerOffsets`, ending up hidden behind the class box.
*/
const classDiagramMarkers = ['aggregation', 'extension', 'composition', 'dependency', 'lollipop'];

const renderMarkers = () => {
const svg = select(document.body).append('svg');
insertMarkers(svg, classDiagramMarkers, 'classDiagram', 'test');
return svg;
};

describe('class diagram markers', () => {
it('sets markerUnits="userSpaceOnUse" on every marker so size is independent of stroke-width', () => {
const svg = renderMarkers();

const offenders = svg
.selectAll('marker')
.nodes()
.filter((marker) => (marker as Element).getAttribute('markerUnits') !== 'userSpaceOnUse')
.map((marker) => (marker as Element).id);

expect(offenders).toEqual([]);
});
});
Loading
Loading