Skip to content

fix(c4): keep every relationship that repeats a from/to pair - #8143

Open
filipsajdak wants to merge 4 commits into
mermaid-js:developfrom
filipsajdak:bug/7183_c4-dynamic-multi-rel
Open

filipsajdak wants to merge 4 commits into
mermaid-js:developfrom
filipsajdak:bug/7183_c4-dynamic-multi-rel

Conversation

@filipsajdak

@filipsajdak filipsajdak commented Aug 27, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #7183.

C4Dynamic numbers its interactions, so several relationships between the same pair are the normal case rather than an edge case. Only the last of them survived parsing.

The defect

c4Db.addRel looked for an existing relationship with the same from and to and wrote into it when it found one:

let rel = {} as C4Rel;
const old = rels.find((rel) => rel.from === from && rel.to === to);
if (old) {
  rel = old;
} else {
  rels.push(rel);
}

So the second Rel(a, b, "Interaction 2") overwrote the first. Rendering @aristotelos's diagram on develop produces a single line reading 1: Interaction 2 - the first statement's label is gone, and because the number is a positional index over rels, the numbering silently closes over the gap. The relationship never reaches the renderer, which is why no amount of styling brings it back.

The change

Each statement creates its own relationship. That is the whole fix; nothing downstream needed touching, because the C4Dynamic counter in c4Renderer.ts is a plain index over rels and starts numbering 1:, 2: correctly once the entries exist.

UpdateRelStyle(a, b, ...) finds its target the same way, by from/to, so once a pair can name several relationships that lookup is ambiguous. It now applies to every relationship between the named pair. @pbrolin47 chose those semantics over first-match-only on the issue:

I think the variant with UpdateRelStyle(a, b, ...) applied on every relation between the named pair is better that the first-match-variant. Existing diagrams will look the same, and having the same styling of every relation between a named pair is a good for now.

Direction still matters: UpdateRelStyle(a, b, ...) does not reach a Rel(b, a, ...). That is unchanged, and covered by a test.

Evidence

Four unit tests in the new parser/c4Rel.spec.ts cover the repeated pair, the two directions of a pair staying separate, and UpdateRelStyle reaching both relationships.

The report is about what reaches the SVG, so there is a browser-level assertion too - CHAR.dynamic-repeated-pair in e2e/rendering/c4/c4-characterization.spec.js asserts both 1: Interaction 1 and 2: Interaction 2 are present. It fails on develop with exactly the symptom above:

Expected substring: "1: Interaction 1"
Received string:    "... A[Software System] B[Software System] 1: Interaction 2 ..."

131 C4 unit tests pass.

What this does not fix

The issue asks for two things, and this delivers one of them. All the relationships are now rendered, but they do not yet avoid each other: the legacy grid places both lines by the same rule, so they land on top of one another - correctly numbered 1: and 2:, at identical coordinates.

They separate under #8042, which routes C4 edges through the unified pipeline. Measured, rather than assumed: applying this change on that branch and rendering the same diagram gives two distinct paths,

M61.071,98 L57.5,123 L60.506,144.04
M73.929,98 L77.5,123 L74.494,144.04

about 13px apart at the endpoints, with both labels drawn. So the complete answer to the report is this change plus that one, and this half is independent of it and worth having on develop now.

What's working well

  • 🎉 [praise] Repeated C4Dynamic relationships remain distinct.
  • 🎉 [praise] UpdateRelStyle applies to all matching directed relationships.
  • 🎉 [praise] Parser and end-to-end tests cover repeated pairs, directionality, styling, and rendered labels.
  • 🎉 [praise] The changeset documents the patch release.

Security

No XSS or injection issues identified. The changes do not alter SVG sanitization or introduce new output sinks.

Things to address

No blocking or important issues found. Visual path separation remains out of scope and is tracked by #8042.

Verdict

APPROVE

Severity tally: 0 blocking / 0 important / 0 nit / 0 suggestion / 4 praise

@changeset-bot

changeset-bot Bot commented Aug 27, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fa8c1a2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
mermaid Patch

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

@netlify

netlify Bot commented Aug 27, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for mermaid-js ready!

Name Link
🔨 Latest commit fa8c1a2
🔍 Latest deploy log https://app.netlify.com/projects/mermaid-js/deploys/6ac97ea89d241900079d709c
😎 Deploy Preview https://deploy-preview-8143--mermaid-js.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot added the Type: Bug / Error Something isn't working or is incorrect label Aug 27, 2026
@pkg-pr-new

pkg-pr-new Bot commented Aug 27, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@mermaid-js/examples

npm i https://pkg.pr.new/@mermaid-js/examples@8143

mermaid

npm i https://pkg.pr.new/mermaid@8143

@mermaid-js/layout-elk

npm i https://pkg.pr.new/@mermaid-js/layout-elk@8143

@mermaid-js/layout-tidy-tree

npm i https://pkg.pr.new/@mermaid-js/layout-tidy-tree@8143

@mermaid-js/mermaid-zenuml

npm i https://pkg.pr.new/@mermaid-js/mermaid-zenuml@8143

@mermaid-js/parser

npm i https://pkg.pr.new/@mermaid-js/parser@8143

@mermaid-js/tiny

npm i https://pkg.pr.new/@mermaid-js/tiny@8143

commit: fa8c1a2

@coderabbitai

coderabbitai Bot commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

We couldn't safely recover the incremental review. No full review was started, and the last reviewed checkpoint was preserved. Retry later, or explicitly request a full review by commenting @coderabbitai full review.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 8baffdd2-46c4-4b25-9960-28122cef40b3


📥 Commits

Reviewing files that changed from the base of the PR and between 9731d80 and 4dcc911.


📒 Files selected for processing (1)
  • packages/mermaid/src/diagrams/c4/c4Db.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.



📝 Walkthrough

Walkthrough

C4 relationship storage now preserves repeated source-target interactions. Relationship styling updates all matching relationships. Parser and end-to-end tests verify rendering, directionality, styling, and relationship metadata.

Changes

C4 relationship handling

Layer / File(s) Summary
Preserve and style repeated relationships
packages/mermaid/src/diagrams/c4/c4Db.ts
addRel stores each relationship separately. updateRelStyle applies colors and offsets to every matching relationship. Text attributes retain their expected shape.
Validate repeated relationship behavior
packages/mermaid/src/diagrams/c4/parser/c4Rel.spec.ts, e2e/rendering/c4/c4-characterization.spec.js, .changeset/c4-dynamic-multi-rel.md
Parser and rendering tests verify repeated relationships, directionality, styling, metadata, and sequence numbers. The changeset documents the patch release.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant C4Parser
  participant c4Db
  participant C4Renderer
  C4Parser->>c4Db: Add repeated Rel entries
  c4Db-->>C4Parser: Return all relationships
  C4Parser->>c4Db: Apply UpdateRelStyle
  c4Db->>c4Db: Style every matching relationship
  c4Db-->>C4Renderer: Provide retained interactions
  C4Renderer-->>C4Renderer: Render labels and sequence numbers
Loading

Merge Risk: ⚪ Minimal · up to 4dcc9

Repeated C4 relationships are now preserved and styled consistently without changing external access, privileges, services, or deployment behavior. No actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR satisfies the requirement to retain and render all repeated relationships and preserves directional matching. It does not satisfy the linked issue's separate requirement that repeated relations… Add the visual-separation changes required by issue #7183, or link and include the implementation that routes C4 edges through the unified pipeline so repeated relationships render on distinct paths without overlap.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main fix: preserving repeated C4 relationships for the same directed pair.
Description check ✅ Passed The description provides a detailed summary, references issue #7183, explains the design, documents tests, and clearly states the remaining visual-separation limitation. It omits the template checklis…
Out of Scope Changes check ✅ Passed The changes remain within scope. They update C4 relationship storage and styling, add focused parser and end-to-end regression tests, and add the related patch changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 3…

Full details: Description check

Explanation

The description provides a detailed summary, references issue #7183, explains the design, documents tests, and clearly states the remaining visual-separation limitation. It omits the template checklist and explicit documentation status, but the required technical information is mostly complete.


Full details: Linked Issues check

Explanation

The PR satisfies the requirement to retain and render all repeated relationships and preserves directional matching. It does not satisfy the linked issue's separate requirement that repeated relationships must not collide visually; the description explicitly defers that requirement to #8042.


Full details: Docstring Coverage

Explanation

No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 3 files.



✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests



Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 27, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 50.00000% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.50%. Comparing base (33d0a49) to head (fa8c1a2).

Files with missing lines Patch % Lines
packages/mermaid/src/diagrams/c4/c4Db.ts 50.00% 16 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #8143      +/-   ##
===========================================
- Coverage    81.15%   79.50%   -1.66%     
===========================================
  Files          622      600      -22     
  Lines        86016    77210    -8806     
  Branches     16421    15710     -711     
===========================================
- Hits         69803    61382    -8421     
- Misses       15207    15828     +621     
+ Partials      1006        0    -1006     
Flag Coverage Δ
e2e ?
unit 79.50% <50.00%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
packages/mermaid/src/diagrams/c4/c4Db.ts 72.23% <50.00%> (+0.19%) ⬆️

... and 194 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@filipsajdak

Copy link
Copy Markdown
Collaborator Author

The e2e red here is not this PR's, and it is not a flake - the job aborts before a single test runs:

SyntaxError: Invalid regular expression: /e2e/rendering/c4/**/gi: Nothing to repeat

playwright test <pattern> compiles its positional arguments with new RegExp(), so the scope detector's glob e2e/rendering/c4/** is not a valid regular expression and throws. Any PR whose changes narrow the e2e scope to a single diagram folder hits this - which is every C4-only PR, including this one.

#8103 fixes it, by emitting e2e/rendering/c4/ (a substring match over the file paths) instead of a glob. Nothing in this branch needs changing; the same commit turns this job green.

The Playwright case added here passes locally against this branch, and fails against develop with the exact symptom the issue reports:

Expected substring: "1: Interaction 1"
Received string:    "... A[Software System] B[Software System] 1: Interaction 2 ..."

@filipsajdak

Copy link
Copy Markdown
Collaborator Author

Regression check, since this changes c4Db and every C4 diagram goes through it.

I rendered all 25 C4 fixtures under e2e/diagrams/c4/ through the public API and hashed the normalised SVG, on develop and on this branch. Identical - 0705da927d01dd7488040bd4b01077f8 either side, no fixture erroring, and the harness reproduces its own hashes so the match is not accidental stability.

That is the expected result: addRel only behaves differently when a diagram repeats a from/to pair, and no existing fixture does. The behaviour change is confined to the case the issue reports.

Note there is no Argos build on this PR to compare against - the e2e job dies at the scope-detector regex before any screenshot is taken, so this is the evidence available until #8103 lands.

@argos-ci

argos-ci Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Argos notifications ↗︎

Awaiting the start of a new Argos build…

`addRel` looked for an existing relationship with the same `from` and `to`
and wrote into it when one was found, so a second `Rel(a, b, ...)` replaced
the first rather than adding to it. A C4Dynamic diagram, where each
interaction is numbered and repeated pairs are the normal case, therefore
showed only the last of them (mermaid-js#7183).

Each statement now creates its own relationship. `UpdateRelStyle` names a
from/to pair rather than one line, so it applies to every relationship
between that pair; previously it could only ever reach the single merged
entry.

Note this fixes the data, not the drawing: the legacy renderer places both
lines by the same grid rule, so two relationships between one pair still
land on top of each other. They separate once the layout slice (mermaid-js#8042)
routes C4 edges through the unified pipeline, which spaces parallel edges.

127 existing C4 tests pass; 4 new ones cover the repeated pair, the two
directions of a pair, and UpdateRelStyle reaching both.
The unit tests cover the db, but the report in mermaid-js#7183 is about what reaches
the SVG, so assert that there too. On develop the same diagram renders a
single line reading `1: Interaction 2` - the first statement is gone and
the numbering has closed over the gap - so this fails without the fix.

Both lines share a grid position, so the assertion is that each is drawn
and numbered rather than where it sits.
@filipsajdak
filipsajdak force-pushed the bug/7183_c4-dynamic-multi-rel branch from 4dcc911 to 6fc4d6c Compare September 18, 2026 07:15
@filipsajdak

Copy link
Copy Markdown
Collaborator Author

@pbrolin47 this is ready for review: CI is green apart from Argos, and it is mergeable.
Argos: 1 changed. A repeated C4Dynamic from/to pair now renders both relationships (1: and 2:) instead of only the last one.
Closes #7183.

# Conflicts:
#	e2e/rendering/c4/c4-characterization.spec.js
…sed e2e layout

The case asserting that two Rels between one pair both render lived in the
c4-characterization spec that the e2e restructuring deleted.

The render becomes a pr-check fixture (a bug fix gets a reproducing fixture in
the smoke tier). A fixture cannot assert on SVG text, so a small pr-check spec
renders the same source without a screenshot and keeps the check that both
"1: Interaction 1" and "2: Interaction 2" are drawn. The parse side stays
covered by c4Rel.spec.ts.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: Bug / Error Something isn't working or is incorrect

Projects

None yet

Development

Successfully merging this pull request may close these issues.

C4 Dynamic Diagram multiple interactions

1 participant