Skip to content

fix(c4): break rows on c4ShapeInRow instead of the display width - #8056

Open
filipsajdak wants to merge 2 commits into
mermaid-js:developfrom
filipsajdak:fix/c4-row-break-determinism
Open

fix(c4): break rows on c4ShapeInRow instead of the display width#8056
filipsajdak wants to merge 2 commits into
mermaid-js:developfrom
filipsajdak:fix/c4-row-break-determinism

Conversation

@filipsajdak

@filipsajdak filipsajdak commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

C4 row breaking depends on the size of the monitor the diagram is rendered on. This removes that dependency, so a C4 diagram lays out the same way everywhere.

Found while answering @pbrolin47's question on #7842 about a C4 layout difference in the 11.17.0 release-candidate image tests that he could not reproduce on a local dev server (diagnosis, measured numbers). This is the cause of the "cannot reproduce locally" half of that report. The width change he was looking at is separate and expected - it is #7842's reviewed change.

The defect

c4Renderer.ts seeded the row budget from the display:

screenBounds.data.widthLimit = screen.availWidth;

That was the only place in packages/mermaid/src reading the physical display, so C4 was the only diagram type in mermaid whose geometry depended on the machine. A row ended when the next element would cross that budget, so with default config (width 216, c4ShapeMargin 50, diagramMarginX 50) the fourth element of a row lands at 450 + 4w and the row breaks once w >= (availWidth - 450) / 4:

screen.availWidth where it comes from elements per row at the default width
800 headless Chrome (the e2e job) - Chrome's Linux new-headless hard-codes an 800x600 virtual screen 2
1280 Electron + Cypress's Xvfb (-screen 0 1280x1024x24, the Applitools job) 3
1512-2560 a developer's monitor 4
0 jsdom (Screen-impl.js hard-codes availWidth = 0) 1 - a single column

Nesting made it worse: the budget was divided by min(c4BoundaryInRow, siblings) at every level, so a boundary two levels deep got a quarter of it and its elements stacked one per row. And because the test compared an absolute x coordinate against a width budget, the effective threshold varied with nesting depth.

One user-visible consequence: UpdateLayoutConfig($c4ShapeInRow="6") was silently capped, because the 5th element already crossed the budget on most displays.

The change

A row ends after c4ShapeInRow elements, however wide they measure:

if (this.nextData.cnt > c4ShapeInRow) {

c4ShapeInRow already existed as the count-based break, so this removes the pixel test rather than replacing it. Row topology is now a function of the diagram source and its config alone. Element widths still depend on the font, but only box sizes vary now - not which row an element lands in.

widthLimit had a second, non-obvious role: it was also the wrap budget for boundary label/type/description text. That is now the widest row the grid can produce (c4ShapeInRow elements plus their margins), so boundary text wrapping stops varying by machine too, instead of silently losing wrapping.

Evidence

c4Renderer.spec.ts renders through the public API with getBBox stubbed, so every element self-sizes to the c4.width floor and the grid coordinates are exact. Four cases: a row fills to c4ShapeInRow before the next starts; the same source places identically whatever the display reports; $c4ShapeInRow="6" is honoured; a boundary's elements stay on one row.

All four are red on develop and green here. On develop jsdom reports availWidth = 0, so the five elements stack in a single column at x 258 (y 403, 643, 883, 1123, 1363) instead of laying out 4 + 1. Worth knowing on its own: no unit test could previously exercise C4 layout at all, because every element landed on its own row regardless of the diagram.

The 130 existing C4 unit tests still pass; they are parser and db tests and never asserted geometry.

eslint.config.js gains no-restricted-globals for screen under packages/mermaid/src, so this cannot regress. Verified it fires by re-adding a screen.availWidth read.

This moves screenshots, deliberately

Argos will show a large C4 diff, and it is the point of the change rather than a side effect. Diagrams get wider and shorter: where a narrow CI display previously forced 2-3 elements per row, a row now holds up to 4, and elements inside boundaries are no longer stacked one per row. The changeset says so.

Flagging the timing explicitly: 11.17.0 is being prepared right now, and this alters the C4 baseline it is cut from. @pbrolin47 - happy to hold this until the release is out if that is easier; the defect is not new and is not getting worse.

Not fixed here

All of this disappears with #8042, which replaces this grid with the unified layout pipeline and deletes c4Renderer.ts. This is deliberately a small change to a file with a limited remaining life, chosen over re-designing the row-break rule.

Summary

  • Made C4 row placement deterministic by using c4ShapeInRow instead of display width.
  • Updated boundary text wrapping to use the maximum grid row width.
  • Removed screen.availWidth from C4 layout calculations.
  • Added an ESLint rule that prevents screen-based layout calculations in Mermaid source.
  • Added renderer tests for row capacity, display independence, custom row limits, and boundary layout.
  • Added a changeset documenting the behavior change.

Testing

  • C4 parser and database tests continue to pass.
  • Screenshot changes are expected because diagrams may become wider and shorter.

@changeset-bot

changeset-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8da7777

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

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

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 12, 2026

Copy link
Copy Markdown

Deploy Preview for mermaid-js failed.

Name Link
🔨 Latest commit 8da7777
🔍 Latest deploy log https://app.netlify.com/projects/mermaid-js/deploys/6a7cad0be3d1fd00083f9b7e

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

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a2f9a083-521e-4bcc-b465-a921a9fe8f1d

📥 Commits

Reviewing files that changed from the base of the PR and between 1422198 and 8da7777.

📒 Files selected for processing (1)
  • packages/mermaid/src/diagrams/c4/c4Renderer.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/mermaid/src/diagrams/c4/c4Renderer.spec.ts

📝 Walkthrough

Walkthrough

C4 rendering now determines row breaks only from c4ShapeInRow. Boundary text uses a shared nominal row width. Tests verify deterministic placement across display sizes, custom row limits, and boundary contents. ESLint prevents new screen-based layout calculations.

Changes

C4 layout determinism

Layer / File(s) Summary
Replace display-width row limits
packages/mermaid/src/diagrams/c4/c4Renderer.ts
The renderer removes boundary and screen width limits. Row placement uses c4ShapeInRow, and boundary text measurement uses maxRowWidth.
Validate deterministic placement
packages/mermaid/src/diagrams/c4/c4Renderer.spec.ts, eslint.config.js, .changeset/c4-row-break-determinism.md
Tests cover default rows, display-width independence, custom values above four, and boundary placement. ESLint rejects screen usage for layout calculations. The changeset documents the behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: Graph: C4

Suggested reviewers: aloisklink

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the main change: C4 rows now break on c4ShapeInRow instead of display width.
Description check ✅ Passed The description clearly explains the defect, implementation, tests, expected screenshot changes, and scope, although it omits the template headings and issue-resolution line.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@pkg-pr-new

pkg-pr-new Bot commented Aug 12, 2026

Copy link
Copy Markdown

Open in StackBlitz

@mermaid-js/examples

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

mermaid

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

@mermaid-js/layout-elk

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

@mermaid-js/layout-tidy-tree

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

@mermaid-js/mermaid-zenuml

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

@mermaid-js/parser

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

@mermaid-js/tiny

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

commit: 8da7777

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
eslint.config.js (1)

235-247: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Enable checkGlobalObject for no-restricted-globals.

Set checkGlobalObject: true to report window.screen, globalThis.screen, and self.screen. Exclude **/*.spec.{ts,js} or add a narrow exception because c4Renderer.spec.ts accesses globalThis.screen.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@eslint.config.js` around lines 235 - 247, Update the no-restricted-globals
configuration for the screen entry in the ESLint config to set
checkGlobalObject: true, so qualified global references are reported. Exclude
**/*.spec.{ts,js} from this restriction or add a targeted exception for
c4Renderer.spec.ts to preserve its globalThis.screen access.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/mermaid/src/diagrams/c4/c4Renderer.spec.ts`:
- Line 33: Update the test setup around the global MutationObserver assignment
to save its original value before setting it to undefined, then restore that
saved value in the rendering test’s finally block so later tests retain the
prior global state.

---

Nitpick comments:
In `@eslint.config.js`:
- Around line 235-247: Update the no-restricted-globals configuration for the
screen entry in the ESLint config to set checkGlobalObject: true, so qualified
global references are reported. Exclude **/*.spec.{ts,js} from this restriction
or add a targeted exception for c4Renderer.spec.ts to preserve its
globalThis.screen access.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e432cc5a-7095-4145-b68e-c4927f69391a

📥 Commits

Reviewing files that changed from the base of the PR and between d93e9c8 and 1071adf.

📒 Files selected for processing (4)
  • .changeset/c4-row-break-determinism.md
  • eslint.config.js
  • packages/mermaid/src/diagrams/c4/c4Renderer.spec.ts
  • packages/mermaid/src/diagrams/c4/c4Renderer.ts

Comment thread packages/mermaid/src/diagrams/c4/c4Renderer.spec.ts
@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 77.65%. Comparing base (d93e9c8) to head (8da7777).

Files with missing lines Patch % Lines
packages/mermaid/src/diagrams/c4/c4Renderer.ts 80.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #8056      +/-   ##
===========================================
+ Coverage    77.56%   77.65%   +0.09%     
===========================================
  Files          572      572              
  Lines        75278    75273       -5     
  Branches     14685    14695      +10     
===========================================
+ Hits         58389    58454      +65     
+ Misses       15885    15825      -60     
+ Partials      1004      994      -10     
Flag Coverage Δ
e2e 70.70% <100.00%> (+0.07%) ⬆️
unit 74.95% <80.00%> (+0.15%) ⬆️

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/c4Renderer.ts 84.56% <80.00%> (+2.48%) ⬆️

... and 9 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.

@argos-ci

argos-ci Bot commented Aug 12, 2026

Copy link
Copy Markdown

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

Build Status Details Updated (UTC)
default (Inspect) ⚠️ Changes detected (Review) 4 changed Aug 12, 2026, 5:47 PM

A row ends after `c4ShapeInRow` elements, however wide those elements measure.
The row budget used to be seeded from `screen.availWidth`, so the same diagram
laid out differently depending on the monitor it was rendered on, and stacked
into a single column under jsdom, where `availWidth` is 0. Nesting divided that
budget once per level, which is why elements inside boundaries ended up one per
row on a narrow display.

Boundary label, type and description text wrap to the widest row the grid can
produce - `c4ShapeInRow` elements plus their margins - rather than to the
display width, so wrapping does not vary by machine either.
@filipsajdak
filipsajdak force-pushed the fix/c4-row-break-determinism branch from 1071adf to 1422198 Compare August 12, 2026 16:47

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/mermaid/src/diagrams/c4/c4Renderer.spec.ts`:
- Around line 117-123: Constrain the display width in both layout tests by
setting globalThis.screen.availWidth below the width needed for the tested
elements before rendering:
packages/mermaid/src/diagrams/c4/c4Renderer.spec.ts:117-123 for six elements and
packages/mermaid/src/diagrams/c4/c4Renderer.spec.ts:126-135 for three boundary
elements. Use the existing afterEach cleanup to restore the environment.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 83dcb96b-f18f-46c0-87b6-be06ee41a90a

📥 Commits

Reviewing files that changed from the base of the PR and between 1071adf and 1422198.

📒 Files selected for processing (2)
  • eslint.config.js
  • packages/mermaid/src/diagrams/c4/c4Renderer.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • eslint.config.js

Comment thread packages/mermaid/src/diagrams/c4/c4Renderer.spec.ts
`no-restricted-globals` rejects `screen` under `packages/mermaid/src`, so
rendered geometry cannot start depending on the size of the viewer's monitor
again.
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.

1 participant