Repository navigation
Common Render pipeline 5: DAGRE shared paint - #7812
Conversation
🦋 Changeset detectedLatest commit: 89f2ebf The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## codex/common-render-pipeline-4-dagre-layoutdata #7812 +/- ##
==================================================================================
- Coverage 2.95% 2.95% -0.01%
==================================================================================
Files 652 652
Lines 69348 69417 +69
Branches 973 973
==================================================================================
+ Hits 2047 2048 +1
- Misses 67301 67369 +68
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
The latest updates on your projects. Learn more about Argos notifications ↗︎
|
…odex/common-render-pipeline-5-dagre-shared-paint
… codex/common-render-pipeline-6-dagre-shared-nodes
… codex/common-render-pipeline-6-dagre-shared-nodes
… codex/common-render-pipeline-6-dagre-shared-nodes
…agre-shared-nodes' into codex/common-render-pipeline-6-dagre-shared-nodes
…odex/common-render-pipeline-5-dagre-shared-paint
… codex/common-render-pipeline-6-dagre-shared-nodes
… codex/common-render-pipeline-6-dagre-shared-nodes
knsv-bot
left a comment
There was a problem hiding this comment.
[sisyphus-bot]
⚠️ Intended verdict: REQUEST_CHANGES (one 🔴 blocking item below). Posting as aCOMMENTbecause GitHub won't let this account request changes on its own PR and the bot review token is currently unavailable — please treat the blocking item as gating.
Thanks for this — routing Dagre through the shared common-paint path is a genuinely nice step in the pipeline series, and the abstraction here is clean. I read the diff at head (c7308a3) against the base (codex/common-render-pipeline-4-dagre-layoutdata), traced the new paint flow end-to-end, and ran an XSS pass. One thing needs sorting before this is ready to flow toward develop, plus a couple of coverage items. Details below.
Note: this targets
codex/common-render-pipeline-4-..., notdevelop, so some of the gates below (changeset, final visual sign-off) may legitimately be planned for the integration PR at the bottom of the stack. If that's the case, just point me at it and I'll happily drop the blocker.
What's working well
- 🎉 The
paintOptionshooks are a tidy abstraction. Letting Dagre drive the sharedpaintLayoutDataviagetNodes/getEdgeNode/skipNode/isCluster/clusterDb(dagre/index.js:764-776) keeps dagre-specific knowledge out ofcommon/index.tswhile still reusing the shared edge/label/cluster machinery. Much better than special-casing dagre inside the common code. - 🎉 Behavior preservation is careful. Keeping
paintDagreLayoutCoresolely for the recursive subgraph measurement path, and the deliberate choice to leaveclass.paddingundefined so the v2 renderer keeps its own node sizing (config.spec.ts— "without forcing class padding"), both show real attention to not perturbing existing output. I confirmedpositionRenderedEdgeLabelincommon/index.tsis functionally equivalent to the oldpositionEdgeLabel(the{ flowchart: ... }arg is fine sincegetSubGraphTitleMarginsonly readsflowchart.subGraphTitleMargin). - 🎉 Strong unit coverage. The new specs are excellent — class self-loop multiplicity preservation in
getEdgesToRender, the ER recursive self-loop, "subgraph through shared paint", reverse-order nested subgraphs, and the hand-drawn nested-namespace cases. ThegetEdgesToRendermerge test in particular nails the regression-prone bit.
Things to address
🔴 [blocking] The class default-renderer flip changes default output and needs a changeset + explicit visual sign-off
defaultConfig.ts:60 adds class: { defaultRenderer: 'dagre-wrapper', ... }. Via classDetector-V2.ts, this routes plain classDiagram text (without -v2) to the v2 unified renderer instead of the legacy class renderer. I confirmed this is a new change in this PR: both develop and the base branch leave class.defaultRenderer unset (→ legacy renderer), and this PR is what updates mermaidAPI.spec.ts from expectedType: 'class' → 'classDiagram'.
That's a default-rendering-output change for every existing classDiagram document (GitHub/GitLab/Obsidian render these server-side), which per our conventions is treated as potentially breaking. It's also not mentioned anywhere in the PR summary — the four bullets are all about the paint pipeline, so a maintainer skimming the description would miss the most consumer-visible change in the diff. Could we:
- Add a changeset (
pnpm changeset) describing the class-default switch — somewhere in the stack if not here. - Call it out in the PR description so it's not buried.
- Confirm the class Cypress baselines are intentionally re-pointed (
cypress/integration/rendering/class/classDiagram.spec.jsetc.) and that Argos diffs on those have been eyeballed — this is the only reliable way to verify the new default looks right.
I believe this is intentional (the commit "Fix class renderer default for docs runtime" and the pipeline goal of getting class diagrams onto the shared dagre path both point that way), so this isn't "revert it" — it's "let's make the gate explicit before it lands."
🟡 [important] Shared rendering-util + new self-loop geometry needs visual-regression coverage
shouldMergeSelfLoopSegments now includes er and classDiagram (dagre/index.js:35-40), so self-referential ER/class edges render through the compact merged-loop path with the new label merge (dagre/index.js:387-399). The unit tests assert the merged path exists and that terminal labels '1'/'0..1' are present, but not their geometry. Since this is shared rendering-util/ code and the whole point (re #6049) is that these loops look good, it'd be great to add imgSnapshotTest() cases in cypress/integration/rendering/ for (a) a class diagram with a self-referential multiplicity edge and (b) a recursive ER relationship, so Argos locks the visual output. Per CLAUDE.md, shared-rendering changes really do want full e2e rather than JSDOM assertions alone.
🟡 [important] "Resolves #6049" looks like it over-claims for this PR
#6049 is specifically about flowchart self-loops, and flowchart/flowchart-v2/stateDiagram were already in shouldMergeSelfLoopSegments on the base branch — so the flowchart fix landed earlier in the series. This PR extends the merge to er/classDiagram. If #6049 is meant to be closed by the stack, consider moving Resolves #6049 to the integration PR and describing this one as "extend the self-loop merge to ER and class diagrams", so the issue isn't auto-closed before the flowchart-facing change reaches develop.
Nits
- 🟢 [nit]
EMPTY_CLUSTER_DB(common/index.ts:39) is a shared module-levelMapused as the defaultclusterDb. It's safe today becauseinsertEdgeonly reads it (.get(...)), but a shared mutable singleton is a latent footgun if a future consumer ever writes to the passed clusterDb. A frozen map or a freshnew Map()per call would be cheaper to reason about. - 🟢 [nit] Worth a one-line comment next to the
classblock indefaultConfig.tsexplaining whypaddingis intentionally omitted (to avoid forcing v2 node sizing) — otherwise a well-meaning future contributor may "complete" it to match the schema default of5and silently change class node sizes.
Security
Ran a dedicated XSS/injection pass over the changed source. No XSS or injection issues identified. The self-loop labelEdge merge feeds the same insertEdgeLabel → createText/createLabel sanitizing sink as the prior code, labelStyle is still re-derived through the CSS-property whitelist in styles2String, and the terminal DOMPurify.sanitize() in mermaidAPI.ts is untouched. The refactor changes which sanitized string/coordinate is used, not whether sanitization runs.
Really solid work overall — the architecture is the right shape and the unit tests are thorough. Once the class-default switch has a changeset + a visual confirmation it's good to go. Happy to re-review quickly. 🙏
Severity tally: 🔴 1 · 🟡 2 · 🟢 2 · 🎉 3 — reviewed by reading the diff at head c7308a3; did not run the suite locally (working tree is on an unrelated branch).
…odex/common-render-pipeline-5-dagre-shared-paint
… codex/common-render-pipeline-6-dagre-shared-nodes
…cleanups - Add changeset documenting the class.defaultRenderer switch to the unified (dagre-wrapper) renderer - Drop the shared EMPTY_CLUSTER_DB singleton in favor of a fresh Map per paint call to avoid a mutable module-level default - Document why class.padding is intentionally left undefined in defaultConfig - Add hand-drawn self-referential class diagram e2e snapshot test
|
Thanks for the thorough review! Addressed in cdfc0ea and via a PR-description update: 🔴 Class default-renderer flip
🟡 Visual coverage for the new self-loop geometry
Rather than duplicating those, I added the one missing variant: a hand-drawn class self-loop test in 🟡 "Resolves #6049" over-claim 🟢 Nits
|
knsv-bot
left a comment
There was a problem hiding this comment.
[sisyphus-bot]
✅ Intended verdict: APPROVE. Posting as a
COMMENTbecause GitHub won't let this account approve its own PR — a human maintainer should treat this as a green light pending CI.
Re-review of the diff at head (cdfc0ea65) against codex/common-render-pipeline-4-dagre-layoutdata, following up on the previous round. All previously raised items are resolved, the security pass is clean, and I found nothing new that blocks.
Previous round — all items resolved
- 🔴→✅ Class default-renderer flip:
.changeset/class-default-unified-renderer.md(minor) documents the switch with the correct opt-out (class: { defaultRenderer: 'dagre-d3' }— verified against the schema enum). The PR description now leads with the change instead of burying it. - 🟡→✅ Visual coverage: both requested cases already existed (
classDiagram-v3.spec.jsself-referential multiplicity test — which exercises the new default path on this branch — and the recursive ER tests inerDiagram-unified.spec.jsacross default/ELK/handDrawn). A hand-drawn class self-loop snapshot test was added inclassDiagram-handDrawn-v3.spec.jsto close the one real gap. - 🟡→✅ #6049 wording: now "Part of #6049", with closure deferred to the integration PR.
- 🟢→✅
EMPTY_CLUSTER_DB: shared singleton removed in favor of a freshnew Map()per paint call (common/index.ts:255) — the right call, sinceObject.freezewouldn't actually preventMap.set. - 🟢→✅
class.paddingomission: now documented indefaultConfig.ts:62-64.
What's working well
- 🎉 Behavior-preservation verified at the seams. I traced the two subtle invariants:
paintOptions.clusterDbcaptures the module-level Map frommermaid-graphlib.js, which is safe becauseclear()mutates in place and never reassigns; and the subgraph-title margin adjustments that used to live inpaintDagreLayoutCorenow flow throughapplyDagreLayoutResult, which writes normalized positions back onto the dagre graph payloads thatgetDagrePaintNodesfeeds the shared painter. Both check out. - 🎉 The new unit tests are exactly the right shape. "Graph-backed paint nodes and edge endpoints", "skip nodes already painted elsewhere", and "lets layout algorithms decide which group nodes are clusters" each pin one hook's contract, and the class self-loop terminal-label merge test locks the regression-prone bit of
getEdgesToRender. 144 tests pass locally at head. - 🎉 The
DAGRE_NODE_LAYOUT_PROPERTIESallowlist discipline held — extended only with the fixed literal'clusterNode', keeping the defense against copying user-controlled graph keys ontoLayoutData.
Nits (non-blocking)
- 🟢 [nit]
dagre/index.js:781— theskipNode: (node, { measure }) => !measure.graph.hasNode(node.id)hook is effectively redundant for dagre, sincegetDagrePaintNodesalready derives its list frommeasure.graphand filters falsy payloads. Harmless as defense-in-depth, but a one-line comment saying it only matters ifgetNodesis ever removed would save a future reader the puzzle. - 🟢 [nit]
dagre/index.js:776—paintOptionscapturesclusterDbonce at module load. This works becausemermaid-graphlib.jsonly ever mutates the Map in place, but that invariant lives in a different file; a short comment noting the dependency would make it harder to break accidentally (e.g., by someone changingclear()toclusterDb = new Map()).
Security
Dedicated XSS/injection pass over all changed source: no XSS or injection issues identified. The self-loop label merge feeds the same sanitizing insertEdgeLabel → createText/createLabel sink as every other edge (sanitization via sanitizeText/renderKatexSanitized before any .html() call); labelStyle lands only in a style attribute via setAttribute; the new paint hooks route node objects exclusively to geometry-only sinks (transform/d/id attrs); and the class-default switch routes to the already-shipping, sanitized v2 pipeline. The terminal DOMPurify pass is untouched.
Remaining gate (mechanical, not a review finding)
The e2e jobs and Argos build for cdfc0ea65 were still running at review time. The previous Argos build (13 changed snapshots, including the class diagrams moving to the unified renderer) was reviewed and approved; the new hand-drawn self-loop test will produce one new baseline that wants a quick eyeball when the build lands. Nothing in the diff since then changes rendering output, so I expect green.
Severity tally: 🔴 0 · 🟡 0 · 🟢 2 · 🎉 3. Nice work closing out the round — from my side this is ready once CI confirms. 🚀
Reviewed by reading the full diff at head cdfc0ea65, running the affected unit suites locally (144 passing), and a dedicated XSS sub-review.
…-6-dagre-shared-nodes Common Render pipeline 6: DAGRE shared node measurement
9841d26
into
codex/common-render-pipeline-4-dagre-layoutdata
Upstream release: https://github.com/mermaid-js/mermaid/releases/tag/mermaid%4011.17.0 Release notes: ### Minor Changes - [#7842](mermaid-js/mermaid#7842) [`3670b4e`](mermaid-js/mermaid@3670b4e) Thanks [@filipsajdak](https://github.com/filipsajdak)! - feat(c4): render C4 elements through the unified shape system, using the new person shape - [#7812](mermaid-js/mermaid#7812) [`cdfc0ea`](mermaid-js/mermaid@cdfc0ea) Thanks [@knsv-bot](https://github.com/knsv-bot)! - feat(class): route `classDiagram` to the unified (v2) renderer by default Set `class: { defaultRenderer: 'dagre-d3' }` in the config to restore the legacy renderer. - [#7785](mermaid-js/mermaid#7785) [`c45cde9`](mermaid-js/mermaid@c45cde9) Thanks [@knsv-bot](https://github.com/knsv-bot)! - feat(flowchart): add collapsible flowchart subgraphs via `subgraphId@{ view: collapsed }` - [#7828](mermaid-js/mermaid#7828) [`8eb3afc`](mermaid-js/mermaid@8eb3afc) Thanks [@knsv-bot](https://github.com/knsv-bot)! - feat(elk): add `elk.keepEntryNodeOnTop` config option to keep a recursive flow's entry node on top - [#7803](mermaid-js/mermaid#7803) [`74e44eb`](mermaid-js/mermaid@74e44eb) Thanks [@knsv-bot](https://github.com/knsv-bot)! - feat(elk): add `elk.nodePlacementAlignment` config option - [#7792](mermaid-js/mermaid#7792) [`ea55b31`](mermaid-js/mermaid@ea55b31) Thanks [@RodrigojndSantos](https://github.com/RodrigojndSantos)! - feat(er): add subgraph support to ER diagrams. - [#7970](mermaid-js/mermaid#7970) [`a2c0fb6`](mermaid-js/mermaid@a2c0fb6) Thanks [@filipsajdak](https://github.com/filipsajdak)! - feat(flowchart): add `folder`, `bucket`, `console` (terminal window) and `browser` shapes - [#7842](mermaid-js/mermaid#7842) [`ae3e115`](mermaid-js/mermaid@ae3e115) Thanks [@filipsajdak](https://github.com/filipsajdak)! - feat(flowchart): add `person` shape (circular head above a rounded body), usable in flowcharts via `A@{ shape: person }` - [#7724](mermaid-js/mermaid#7724) [`0fd7a9f`](mermaid-js/mermaid@0fd7a9f) Thanks [@xdumaine](https://github.com/xdumaine)! - feat(xyChart): add legends for named line and bar series ### Patch Changes - [#7847](mermaid-js/mermaid#7847) [`215fe89`](mermaid-js/mermaid@215fe89) Thanks [@filipsajdak](https://github.com/filipsajdak)! - fix(c4): named attributes such as `$tags`, `$link` and `$sprite` are no longer clobbered to undefined when they arrive in an earlier positional slot of Person/System/Container/Component/Boundary/Rel statements. - [#7871](mermaid-js/mermaid#7871) [`8d874c4`](mermaid-js/mermaid@8d874c4) Thanks [@knsv-bot](https://github.com/knsv-bot)! - fix(flowchart): stop dagre layout from spamming `warn`-level logs on every node/edge/cluster - [#8071](mermaid-js/mermaid#8071) [`b3d1f63`](mermaid-js/mermaid@b3d1f63) Thanks [@pbrolin47](https://github.com/pbrolin47)! - fix(block): sibling blocks overlapping in block diagrams when one has a label wider than 200px - [#7870](mermaid-js/mermaid#7870) [`71b8843`](mermaid-js/mermaid@71b8843) Thanks [@knsv-bot](https://github.com/knsv-bot)! - fix: a `RangeError: Invalid array length` crash when rendering certain edges. - [#7924](mermaid-js/mermaid#7924) [`9cbef5d`](mermaid-js/mermaid@9cbef5d) Thanks [@nightt5879](https://github.com/nightt5879)! - fix(treeView): icons disappearing after strict security sanitization. - [#7850](mermaid-js/mermaid#7850) [`a34cbf0`](mermaid-js/mermaid@a34cbf0) Thanks [@aloisklink](https://github.com/aloisklink)! - fix(block): allow classdefs to update text color - [#7937](mermaid-js/mermaid#7937) [`f9cbe1e`](mermaid-js/mermaid@f9cbe1e) Thanks [@filipsajdak](https://github.com/filipsajdak)! - fix(dagre): let a diagram's own nodeSpacing/rankSpacing take effect in the unified dagre layout - [#8005](mermaid-js/mermaid#8005) [`90eeece`](mermaid-js/mermaid@90eeece) Thanks [@pbrolin47](https://github.com/pbrolin47)! - fix(flowchart): reverts the behavior change from #7672 (fix/4648-directions), since arrows between subgraphs are broken - [#7951](mermaid-js/mermaid#7951) [`afa2f80`](mermaid-js/mermaid@afa2f80) Thanks [@aloisklink](https://github.com/aloisklink)! - perf: use `fastdom` to batch DOM measurements (up to 25% speedup) - Updated dependencies \[[`e848423`](mermaid-js/mermaid@e848423)]: - @mermaid-js/parser@1.2.1 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: give me a 98K <240642031+inhuman-0@users.noreply.github.com>
Summary
class.defaultRenderernow defaults todagre-wrapper, so plainclassDiagramdocuments render through the shared dagre layout/paint pipeline instead of the legacy renderer (changeset included; restore the old behavior withclass: { defaultRenderer: 'dagre-d3' })erandclassDiagramtypes (the flowchart-facing part of Flowchart ugly self linking nodes #6049 landed earlier in this series)Part of #6049 — the issue should be closed by the integration PR once the whole stack reaches
develop.Verification
Visual regression coverage for self-loops
cypress/integration/rendering/class/classDiagram-v3.spec.js("self-referential class diagram with multiplicity labels") — uses plainclassDiagram, so it exercises the new default unified path; a hand-drawn variant was added inclassDiagram-handDrawn-v3.spec.jscypress/integration/rendering/er/erDiagram-unified.spec.js(default, ELK, and handDrawn variants) anderDiagram.spec.js