From c9ce128d39d9ece0226e0e406b86d96b321b8be1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 16:36:19 +0000 Subject: [PATCH] fix: remove dead dagre patch and fix explicitDir false-positive with inheritDir Agent-Logs-Url: https://github.com/sjackson0109/mermaid/sessions/6e0b59e7-9b3a-4532-bdc3-7d7893b2c0da Co-authored-by: sjackson0109 <38080190+sjackson0109@users.noreply.github.com> --- package.json | 3 +- .../mermaid/src/diagrams/flowchart/flowDb.ts | 7 +- .../mermaid/src/diagrams/flowchart/types.ts | 1 + patches/dagre-d3-es@7.0.14.patch | 143 ------------------ pnpm-lock.yaml | 7 +- 5 files changed, 10 insertions(+), 151 deletions(-) delete mode 100644 patches/dagre-d3-es@7.0.14.patch diff --git a/package.json b/package.json index c2699440c..840f13cab 100644 --- a/package.json +++ b/package.json @@ -147,8 +147,7 @@ }, "pnpm": { "patchedDependencies": { - "roughjs": "patches/roughjs.patch", - "dagre-d3-es@7.0.14": "patches/dagre-d3-es@7.0.14.patch" + "roughjs": "patches/roughjs.patch" }, "onlyBuiltDependencies": [ "canvas", diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.ts b/packages/mermaid/src/diagrams/flowchart/flowDb.ts index 2fd52af97..a09af4c11 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowDb.ts +++ b/packages/mermaid/src/diagrams/flowchart/flowDb.ts @@ -700,6 +700,10 @@ You have to call mermaid.initialize.` const result = uniq(list.flat()); const nodeList = result.nodeList; let dir = result.dir; + // Capture whether the user explicitly wrote a direction keyword BEFORE any + // TD→TB normalisation or inheritDir override, so that explicitDir is true + // only for user-authored direction statements. + const hasExplicitDir = dir !== undefined; // Normalize TD ("top-down" alias) to the canonical TB ("top-bottom") that dagre expects, // mirroring the same normalisation in setDirection() for the top-level graph direction. if (dir === 'TD') { @@ -729,6 +733,7 @@ You have to call mermaid.initialize.` title: title.trim(), classes: [], dir, + hasExplicitDir, labelType: this.sanitizeNodeLabelType(_title?.type), }; @@ -1123,7 +1128,7 @@ You have to call mermaid.initialize.` cssClasses: subGraph.classes.join(' '), shape: 'rect', dir: subGraph.dir, - explicitDir: !!subGraph.dir, // true only when the user wrote an explicit 'direction X' keyword + explicitDir: subGraph.hasExplicitDir, // true only when the user wrote an explicit 'direction X' keyword isGroup: true, look: config.look, }); diff --git a/packages/mermaid/src/diagrams/flowchart/types.ts b/packages/mermaid/src/diagrams/flowchart/types.ts index ac2d04015..6df7e1104 100644 --- a/packages/mermaid/src/diagrams/flowchart/types.ts +++ b/packages/mermaid/src/diagrams/flowchart/types.ts @@ -78,6 +78,7 @@ export interface FlowClass { export interface FlowSubGraph { classes: string[]; dir?: string; + hasExplicitDir: boolean; id: string; labelType: string; nodes: string[]; diff --git a/patches/dagre-d3-es@7.0.14.patch b/patches/dagre-d3-es@7.0.14.patch deleted file mode 100644 index bb9eb7c74..000000000 --- a/patches/dagre-d3-es@7.0.14.patch +++ /dev/null @@ -1,143 +0,0 @@ -diff --git a/src/dagre/layout.js b/src/dagre/layout.js -index ee10670c600a9ac6ac6ebfa575c84a3c7e3a7621..0283ef7424ece094ffd231179537b37902b1600a 100644 ---- a/src/dagre/layout.js -+++ b/src/dagre/layout.js -@@ -18,6 +18,8 @@ function layout(g, opts) { - time('layout', () => { - var layoutGraph = time(' buildLayoutGraph', () => buildLayoutGraph(g)); - time(' runLayout', () => runLayout(layoutGraph, time)); -+ // After main layout, re-layout any clusters with their own rankdir -+ time(' applyClusterDirections', () => applyClusterDirections(layoutGraph, time)); - time(' updateInputGraph', () => updateInputGraph(g, layoutGraph)); - }); - } -@@ -121,7 +123,12 @@ function buildLayoutGraph(inputGraph) { - - _.forEach(inputGraph.nodes(), function (v) { - var node = canonicalize(inputGraph.node(v)); -- g.setNode(v, _.defaults(selectNumberAttrs(node, nodeNumAttrs), nodeDefaults)); -+ var nodeAttrs = _.defaults(selectNumberAttrs(node, nodeNumAttrs), nodeDefaults); -+ // Preserve per-cluster rankdir so applyClusterDirections can use it -+ if (node.rankdir) { -+ nodeAttrs.rankdir = node.rankdir; -+ } -+ g.setNode(v, nodeAttrs); - g.setParent(v, inputGraph.parent(v)); - }); - -@@ -393,6 +400,115 @@ function positionSelfEdges(g) { - }); - } - -+/* -+ * Returns all descendant node IDs of a given cluster node (all depths). -+ */ -+function getAllDescendants(g, v) { -+ var result = []; -+ function collect(node) { -+ g.children(node).forEach(function (child) { -+ result.push(child); -+ if (g.children(child).length) { -+ collect(child); -+ } -+ }); -+ } -+ collect(v); -+ return result; -+} -+ -+/* -+ * After the main layout has assigned positions to all nodes, re-layout any -+ * clusters (compound nodes) that carry a `rankdir` different from the -+ * top-level graph. Each such cluster's descendants are laid out in a fresh -+ * sub-graph with the cluster's own direction, then the resulting positions are -+ * translated so the sub-layout is centred at the cluster's main-graph position. -+ */ -+function applyClusterDirections(g, time) { -+ var graphRankdir = ((g.graph().rankdir) || 'TB').toUpperCase(); -+ -+ g.nodes().forEach(function (v) { -+ if (!g.children(v).length) return; -+ var node = g.node(v); -+ if (!node || !node.rankdir) return; -+ var clusterRankdir = node.rankdir.toUpperCase(); -+ if (clusterRankdir === graphRankdir) return; -+ -+ var descendants = getAllDescendants(g, v); -+ if (!descendants.length) return; -+ -+ // Build a fresh sub-graph — avoid filterNodes which mutates nesting state -+ var subGraph = new Graph({ multigraph: true, compound: true }); -+ subGraph.setGraph({ -+ rankdir: node.rankdir, -+ ranksep: g.graph().ranksep != null ? g.graph().ranksep : 50, -+ edgesep: g.graph().edgesep != null ? g.graph().edgesep : 20, -+ nodesep: g.graph().nodesep != null ? g.graph().nodesep : 50, -+ }); -+ subGraph.setDefaultEdgeLabel(function () { return {}; }); -+ -+ descendants.forEach(function (u) { -+ var nl = g.node(u); -+ subGraph.setNode(u, { width: (nl && nl.width != null) ? nl.width : 50, -+ height: (nl && nl.height != null) ? nl.height : 50 }); -+ var parent = g.parent(u); -+ if (parent && parent !== v && descendants.indexOf(parent) !== -1) { -+ subGraph.setParent(u, parent); -+ } -+ }); -+ -+ g.edges().forEach(function (e) { -+ if (descendants.indexOf(e.v) !== -1 && descendants.indexOf(e.w) !== -1) { -+ var el = g.edge(e); -+ subGraph.setEdge(e.v, e.w, { -+ weight: (el && el.weight != null) ? el.weight : 1, -+ minlen: (el && el.minlen != null) ? el.minlen : 1, -+ width: (el && el.width != null) ? el.width : 0, -+ height: (el && el.height != null) ? el.height : 0, -+ labeloffset: (el && el.labeloffset != null) ? el.labeloffset : 10, -+ labelpos: (el && el.labelpos) ? el.labelpos : 'r', -+ }); -+ } -+ }); -+ -+ // Layout the sub-graph with the cluster's own direction -+ runLayout(subGraph, time); -+ -+ // Compute the sub-graph bounding box -+ var minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity; -+ descendants.forEach(function (u) { -+ var n = subGraph.node(u); -+ if (n && n.x != null && n.y != null) { -+ var hw = (n.width || 0) / 2; -+ var hh = (n.height || 0) / 2; -+ minX = Math.min(minX, n.x - hw); -+ minY = Math.min(minY, n.y - hh); -+ maxX = Math.max(maxX, n.x + hw); -+ maxY = Math.max(maxY, n.y + hh); -+ } -+ }); -+ -+ // Translate sub-layout to be centred at the cluster's main-graph position -+ var clusterNode = g.node(v); -+ if (!clusterNode) return; -+ var offsetX = (clusterNode.x || 0) - (maxX + minX) / 2; -+ var offsetY = (clusterNode.y || 0) - (maxY + minY) / 2; -+ -+ descendants.forEach(function (u) { -+ var subNode = subGraph.node(u); -+ var mainNode = g.node(u); -+ if (mainNode && subNode && subNode.x != null && subNode.y != null) { -+ mainNode.x = subNode.x + offsetX; -+ mainNode.y = subNode.y + offsetY; -+ } -+ }); -+ -+ // Update cluster bounding box to match sub-layout -+ clusterNode.width = Math.max(clusterNode.width || 0, maxX - minX); -+ clusterNode.height = Math.max(clusterNode.height || 0, maxY - minY); -+ }); -+} -+ - function selectNumberAttrs(obj, attrs) { - return _.mapValues(_.pick(obj, attrs), Number); - } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 331c15af3..c08cdd811 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,9 +5,6 @@ settings: excludeLinksFromLockfile: false patchedDependencies: - dagre-d3-es@7.0.14: - hash: 9da670a68ec3d17e16b620a8bf8f9ea88df6e8122e728e68caec8eb0cdd9b9d0 - path: patches/dagre-d3-es@7.0.14.patch roughjs: hash: 3543d47108cb41b68ec6a671c0e1f9d0cfe2ce524fea5b0992511ae84c3c6b64 path: patches/roughjs.patch @@ -267,7 +264,7 @@ importers: version: 0.12.3 dagre-d3-es: specifier: 7.0.14 - version: 7.0.14(patch_hash=9da670a68ec3d17e16b620a8bf8f9ea88df6e8122e728e68caec8eb0cdd9b9d0) + version: 7.0.14 dayjs: specifier: ^1.11.19 version: 1.11.19 @@ -15687,7 +15684,7 @@ snapshots: d3-transition: 3.0.1(d3-selection@3.0.0) d3-zoom: 3.0.0 - dagre-d3-es@7.0.14(patch_hash=9da670a68ec3d17e16b620a8bf8f9ea88df6e8122e728e68caec8eb0cdd9b9d0): + dagre-d3-es@7.0.14: dependencies: d3: 7.9.0 lodash-es: 4.18.1