[pull] develop from mermaid-js:develop - #234
Merged
Merged
Conversation
ELK previously shipped as a separate `@mermaid-js/layout-elk` package that sites had to install and register. Its source now lives in core at `rendering-util/layout-algorithms/elk`, is registered automatically, and is the default `layout`. Flowchart, state, class, ER, requirement, use-case and agentflow diagrams that do not specify a `layout` are now laid out by ELK instead of dagre. Mindmap keeps dagre unless a layout is explicitly requested, which needed a way to tell "the user asked for this" from "this is just the default" — `isConfigKeySet` provides it on top of the existing `getUserDefinedConfig`. Bundling: - ELK registers behind `injected.includeLargeFeatures` as a lazy import, so the ESM builds fetch it only when a diagram uses it. The single-file IIFE build inlines it and grows by ~516 kB gzipped. - The tiny build omits ELK and falls back to dagre; its size is unchanged. - `@mermaid-js/layout-elk` remains published and is now built from core's ELK source, so there is one implementation. Because it no longer imports the whole `mermaid` entry point, its payload drops from ~1.58 MB to ~728 kB gzipped. It is how the tiny build gets ELK. Also: - Drops the cross-bundle `mermaid.mermaidAPI.setConfig` hack, which only existed because the separate bundle carried a second mermaid instance. - Routes the state renderer through `getRegisteredLayoutAlgorithm`, which it previously bypassed, so an unavailable layout falls back rather than failing to render. - Pins the e2e suite to dagre in `mermaidUrl` so existing visual baselines are preserved; diagrams that select ELK by syntax opt out, since the detector's choice ranks below a user-supplied layout. - Guards `Object.keys(dependencies)` in the esbuild config, which crashed for a package with no dependencies. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two CI failures from bundling ELK. build-docs: the docs site aliases `mermaid` to `dist/mermaid.esm.min.mjs`, and Vite runs `es-module-lexer` over every module for import analysis. The minified ELK chunk is the one module in the graph that lexer rejects, which surfaced as "content contains invalid JS syntax". The unminified ESM build lexes clean (verified across all 136 modules of its graph), and VitePress minifies its own output anyway, so the pre-minified input bought nothing. lint: `layout`'s longer schema description shifted line numbers in the generated config types, so four typedoc pages were stale — the source links in `MermaidConfig`, `LayoutLoaderDefinition`, `RenderOptions` and `getEffectiveHtmlLabels`. Regenerated; the diff is line numbers only. Note for anyone regenerating docs from a git worktree: typedoc requires `.git` to be a directory to discover the repository, and in a worktree it is a file, so it silently emits every "Defined in:" without its source link. The links here were restored to match what CI produces. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…layout
The baseline pin set `layout: dagre` for any spec that did not ask for one,
which outranks a diagram type's own choice — so the whole swimlanes suite
rendered with dagre and lost its lanes (33 failures on `g.cluster.swimlane`
resolving to 0 elements).
`swimlanes` is `createFlowDiagram({ defaultLayout: 'swimlane' })`, and
`flowDiagram.init` ranks `defaultLayout` below a user-supplied layout —
exactly the precedence that `flowchart-elk` already needed an opt-out for. I
only special-cased the ELK half of it.
Detect this from the diagram source in `mermaidUrl` instead of relying on
each spec to pass `useDiagramLayout`, because a missed case does not fail
loudly: the swimlanes fixtures under `e2e/diagrams` render through the
screenshot-only mmd runner, where a dagre-laid-out swimlanes diagram would
have been silently baselined as correct.
`useDiagramLayout` stays for `flowchart.defaultRenderer: 'elk'`, which is
not detectable from the source — only `flowDiagram.init` promotes it, so it
is inert on the class/er/mindmap fixtures that carry it and those must stay
pinned.
Extends the pin regression spec to cover self-selecting diagrams, multi-graph
renders, and a flowchart that merely mentions the keyword in a node label.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The suite was pinned to dagre in `mermaidUrl` to keep existing Argos baselines comparable. That hides the layout users will actually get, so remove it and let every diagram render with ELK — the baselines are meant to move. Removes the pin, the `useDiagramLayout` opt-out and the self-selecting-layout detection it needed, plus the regression spec guarding all of it. `e2e/` is now byte-identical to before this branch. Also drops `elk-default.spec.ts`. It existed only to cover the shipped default that the pin was hiding; with no pin, every spec exercises it. Argos will show a large diff across the whole suite. That is the intent: it is the review surface for how diagrams look under ELK. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`usecase.docs.spec.ts` renders all 20 mermaid-example fences from the canonical page through a real layout engine in jsdom. ELK, now the default, does considerably more work than dagre and pays a one-off cost to load elkjs — roughly 55ms per example locally, which on a slower CI runner pushes the whole spec past vitest's 5s default. It timed out in CI while passing locally in 1.1s. Adds an optional timeout passthrough to `jsdomIt` and uses 30s here. Left as a real render rather than pinned to dagre: the point of the spec is that the documented examples render under the layout users actually get. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-flowchart-layout-4d2342 # Conflicts: # docs/config/setup/mermaid/interfaces/MermaidConfig.md
Prettier collapses the two-symbol import onto one line; my hand-edit after moving the file into core left it expanded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mindmap defaults to `cose-bilkent`, and `mindmapDb.getData()` already decides that: when `getUserDefinedConfig().layout` is undefined it stamps `cose-bilkent` onto the config it hands the renderer. I missed that when making ELK the global default, read only the renderer, and concluded mindmap was effectively on dagre. The override I added then forced dagre unconditionally, so mindmaps silently changed layout engine. Reverting `mindmapRenderer.ts` to its original form restores the correct behaviour and needs nothing new: the db's existing check is unaffected by the global default changing, because an unset `layout` is still unset. Also drops `isConfigKeySet`. It was added solely for that override and duplicated the `getUserDefinedConfig()` idiom `mindmapDb` was already using, so it is now unused public API. Replaces the spec that codified the wrong behaviour with one that guards the right one — mindmap resolves to `cose-bilkent` while the global default is `elk`, and still honours an explicit layout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…efault Two failures from the e2e run, both real. **arrowMarkerAbsolute ignored under ELK.** The e2e viewer registered the standalone `@mermaid-js/layout-elk` bundle, which overrides mermaid's own registration with a second, self-contained copy whose config module never sees `initialize()` — so edges painted with schema defaults. That is exactly what `syncElkPackageConfig` existed for; I removed it believing `setConfig(getConfig())` was an identity round-trip. It is not: the local `setConfig` and the host's `getConfig` are different module instances in the plugin build, so it copies host config into the plugin's copy. Restored as `syncHostConfig`, using a relative `setConfig` rather than the package's default export, so it repairs the plugin build and is a genuine no-op in core. The e2e viewer no longer registers the package either, so the suite exercises the ELK users actually get instead of the plugin build. **State diagrams with notes failed to render.** `note right of <composite>` produced a note node carrying `type`/`isGroup` inherited from the state it annotates, marking the note as a container. The shared paint path then looked up a `note` *cluster* shape, which does not exist, and threw `shapes[shape] is not a function`, blanking the whole diagram. A note is always a leaf inside the note group, so it is now `isGroup: false`. Pre-existing: dagre only reads `isGroup` for edge hints, so it rendered regardless. It surfaces now that state defaults to ELK, and would already reproduce on `layout: elk`. Covered by a unit test that fails without the fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`syncHostConfig` looks like a no-op from inside mermaid — the host's config module and the layout's are the same module there, so removing the call changes nothing that any existing test observes. It only matters in the standalone `@mermaid-js/layout-elk` bundle, which carries its own copy of the config module that never sees the host's `initialize()`. That asymmetry is why the call was removed in the first place when ELK moved into mermaid. The spec asserts the CALL rather than the resulting config value, because a value assertion would pass either way in-process and would give false confidence. Both cases fail if the call is removed: that the host's config is handed over, and that it happens before the layout data is read, since `measureLayoutFn` builds the labels after `prepareLayout` returns.
…2e-switch-layout-elk-dagre
…2e-switch-layout-elk-dagre
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…2e-switch-layout-elk-dagre
…e edge The #8194 fix repointed a degenerately-anchored edge at the small node's centre. But ELK, with nodeFlexibility PORT_POSITION, had already placed the node so its clamped port sat on a straight route — the node was what stood off-centre, not the edge — so the repointing bent straight verticals visibly diagonal in every fixture with an edge between a state dot and a wider node. Replace the drop-the-anchor logic with an alignment pre-pass: before any edge points are built, a node whose terminal anchor sits on a degenerate side (shorter than twice the ports-surrounding margin) is shifted along that side's axis until its centre lands on the anchor line. The edge stays exactly as routed, the appended centre point is collinear with it, and running as a pre-pass keeps every edge's view of the node position consistent regardless of processing order. First anchor wins; the nodeDb entry and the layout node move together so painting and clipping agree.
fix(elk): move small nodes onto the routed line instead of bending the edge
…agre Review verdict approved, and fixed some of the comments. Update of baseline will occur later
Develop added specs assuming dagre is the default layout and that elk is the canonical unregistered algorithm; both premises change on this branch. Expectations follow the elk default, the fallback specs use a genuinely unregistered name, and the stale ships-separately comments are updated.
…comment - ER docs: Dagre example actually uses dagre; drop the obsolete 9.4+/lazy-load note - syntax-reference: list every diagram type that supports layout/look selection, with the mindmap cose-bilkent exception - layouts.md: mindmaps default to cose-bilkent, not dagre - render.spec: literal-key form for the dotted flat key; evenGroupFrames suite moved out of clearContainerAlgorithmOptions - hostConfigSync.spec: instrument the property the code actually reads (edges) and assert the sync-before-read ordering both ways - elkOptionCatalogue: the catalogue objects are merged too, so stop calling them inert; all four blocks must ship empty
…, lexer note - @mermaid-js/layout-elk declares dompurify, katex, dayjs and dagre-d3-es, so the npm core build externalizes them (verified in the built chunks: bare imports instead of inlined copies) and they dedupe against the host and appear in audits. The self-contained CDN build is unchanged. - syncHostConfig documents that setConfig sanitizes away the secure keys, and the hostConfigSync fixture no longer asserts a securityLevel propagation that sanitize() prevents. - The elk-default-layout changeset records the mermaid.esm.min.mjs es-module-lexer incompatibility and corrects the mindmap default to cose-bilkent; the layout-elk changeset documents the new dependencies.
The generic measure path (used by ELK) passed only node.dir to insertNode, which is set for groups alone, so fork/join bars in LR diagrams were drawn horizontal and edges hit the short side off-center. Resolve the effective direction per node — its own dir, the nearest ancestor group's dir, or the diagram direction — mirroring what dagre's recursive render provides via rankdir. Also treat RL like LR in forkJoin so the bar stays perpendicular to a horizontal flow.
…cluster backgrounds
The ER renderer carried an ELK-specific workaround from the old external ELK
renderer: svg.select('.edges').lower(), added because that renderer painted
edges above nodes and the crow's-foot markers showed on top of entity boxes.
The unified ELK render path already inserts groups in the order
clusters < edges < edgeLabels < nodes, so nodes cover marker overlap without
any reordering — and the .lower() call now moved the edge group below the
clusters group, hiding relationship edges behind subgraph backgrounds
(fully hidden with a solid background, peeking through the hachure gaps in
the hand-drawn look).
Remove the call and leave a comment explaining why it must not return.
Verified on the tmp-v12.0.0-faulty-diags ER fixtures (solid + handDrawn),
plain ER diagrams under ELK (markers still tucked under entity boxes),
nested flowchart subgraphs, edge-to-subgraph, and composite states under ELK.
An edge that terminates on a compound node ([*] --> Composite in state diagrams, node --> subgraph in flowcharts) is anchored by ELK on the frame's border. evenGroupFrames could pull the drawn frame in past that anchor, leaving it floating outside the frame; the on-border check in sanitizeElkEdgePoints then missed it and cutter2 re-clipped the edge along a ray to the group's centre, painting a long shallow diagonal that hugged the cluster border instead of entering it cleanly. Feed the terminal point of every group-attached edge into the frame pass's lane measurement so a frame is never pulled past its own edge anchors. Only the point on the group is added - the edge's other points belong to the layout outside the frame and must not hold it open. Adds e2e/platform/dev-diagrams/tmp-v12.0.0-faulty-diags/ elk-legacy-preset-regression.mmd as a manual regression fixture for the legacy-preset (Miro-matched) ELK configuration; verified pixel-identical before and after (104/738560 px).
…-routing fix(elk): edge from start node hugs the composite state border
…om:mermaid-js/mermaid into claude/elk-default-flowchart-layout-4d2342
A composite state or subgraph containing a loop opened on the wrong node. The start circle shared the first layer with an ordinary state instead of having it to itself, and dagre laid the same diagram out correctly, which is what made it look like an ELK bug rather than a configuration gap. It was a configuration gap. `createRootElkGraph` resolves `elk.layered.cycleBreaking.strategy` from the preset; `buildSubgraphLayoutOptions` never set it at all, so a container laid out on its own fell back to ELK's default, GREEDY. The root and its own containers then reversed different edges of the same cycle. Greedy reverses the edge that turns an ordinary state into a source, and sources sit on the first layer — hence the start circle sharing it. The gap was invisible from config: every value of `elk.preset` and `elk.cycleBreakingStrategy` rendered such a diagram identically, because none of them reached the container. That is also how this was diagnosed — running the root with GREEDY reproduced the container's layout byte for byte. Containers now resolve it exactly as the root does, and the preset is hoisted above the literal so the two functions read alike; they drifted apart in the first place because they did not. Only containers that contain a cycle move — an acyclic subgraph gives a cycle-breaking strategy nothing to do. Verified unchanged on a flat state machine, an acyclic subgraph and the nested-subgraph fixture. `preset: legacy` still reaches containers with GREEDY, so it keeps reproducing the earlier rendering inside frames as well as outside them. The three new unit cases pin the two sides agreeing rather than pinning each side's value separately, which is what allowed the drift.
…-layout-4d2342 feat(layout)!: bundle ELK and make it the default layout
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )