Skip to content

fix: place a merge, a lane and the next statement where the measurer says - #1159

Closed
tgolembiewski wants to merge 1 commit into
mendixlabs:mainfrom
tgolembiewski:fix/1158-layout-measure-mismatch
Closed

tgolembiewski wants to merge 1 commit into
mendixlabs:mainfrom
tgolembiewski:fix/1158-layout-measure-mismatch

Conversation

@tgolembiewski

Copy link
Copy Markdown
Contributor

Closes #1158

What

Three fixed-size disagreements between what measureStatements predicts and where the builder places things. Every merge, every following statement and every branch lane is positioned from the first and drawn by the second, so a few pixels of disagreement is an overlap. Geometry only — no MDL syntax, no execution semantics, @position still never moved.

Measured on mdl-examples/doctype-tests/02c-complex-layout-examples.mdl (exec, then read the coordinates back with describe):

before after
overlapping pairs 15, in 3 of the 6 flows 0
after merge rejoin2 in CX_SYNC_FactoryTelemetry merge (850,200), next activity (930,200) — edges on the same line 40 px, the gap two activities have
end of a case branch in CX_ACT_HandleAuthCode set $Success (2855,200) covering the merge at (2885,200) merge clear of the branch
nested decisions in CX_SYNC_FactoryTelemetry lanes 50 px apart where an activity is 60 tall lane below what the branch above occupies

mx check 0 errors before and after.

The three fixes:

  1. A split is measured to the merge that closes it, not to its branch — splitWidthWithMerge, from the split's left edge to the merge's right edge. The old measure was 25 px short of what a following element has to clear.
  2. The advance past a merge clears the merge first. Half a pitch from a merge's centre is what an activity's half width needs; a merge is MergeSize = 40 against an activity's 120. The case statement's own merge and merge <label> now use mergeX + MergeSize + HorizontalSpacing/2, which is what addIfStatement has always used.
  3. A branch lane goes below what the branch above actually occupies. Half the measured height is only right for content centred on its line; a branch holding a nested if hangs entirely below its own — measured 160 tall, it occupies 30 above and 130 below. The branch above is already built when the next lane is placed, so lowestBetween measures it, the same move separateRows already makes for wrapped rows. Its range is bounded at both ends because a split's shared merge is appended among its branches.

All three predate #1155; they were covered by slack in the spacing that #1155 removed.

Tests

  • TestMeasuredWidthMatchesWhatIsBuilt — asks the measurer and the builder the same question about the same run and compares. A short measure overlaps the next element; a generous one wastes canvas.
  • TestBuiltElementsDoNotOverlap — the invariant itself, over eight shapes taken from the example file.
  • Each fix was reverted in turn and both tests were confirmed to fail with the reported symptom. Three of them passed on the first attempt with their fix removed — two elements can be vertically clear simply by being in different columns — so the shapes were widened until they did not.
  • TestEnumSplitGeometryIsUnchanged pinned the advance that fix 2 changes. It is now TestEnumSplitAdvanceClearsItsMerge and pins the gap instead, with the reason in its comment; its other half, that the merge does not drift with branch count, is unchanged.

make build, make test, make lint-go, make check-mdl, make check-findings, make check-wiki-pages, scripts/check-tunnel-deps.sh, scripts/check-skill-mdl.sh (skills and docs-site), govulncheck and make test-integration against a real mx 11.12.1 all pass locally.

Note, not part of this PR

describeexec of CX_SYNC_FactoryTelemetry does not round-trip: the shared tail after merge shared4 is printed twice, labels are renumbered, and the re-executed model fails mx check with [error] [CE0773] "Value must be of type Boolean." at Sequence flow. Identical on the v0.23.0 release binary, so it is not from the layout work — the three remaining describe overlap reports on that flow are this duplicate print, one activity at one position, not two elements.

…says

Three fixed-size disagreements between what measureStatements predicts and where
the builder puts things. Every merge, every following statement and every branch
lane is positioned from the first and drawn by the second, so a few pixels of
disagreement is an overlap rather than a cosmetic difference. Measured on
mdl-examples/doctype-tests/02c-complex-layout-examples.mdl: 15 overlapping pairs
in 3 of its 6 flows, down to none. mx check reports 0 errors either way, which is
why none of this was visible to anything automatic.

- A split was measured to its branch rather than to the merge that closes it -
  25px short of what a following element has to clear. splitWidthWithMerge now
  measures from the split's left edge to the merge's right edge.
- The advance past a merge came from its centre by what an activity's half width
  needs: half a pitch is 80px and a merge is 40 wide against an activity's 120, so
  the next element's left edge landed on the merge's right edge. The enum split's
  own merge and `merge <label>` now clear the merge first and then leave the
  ordinary gap, which is what addIfStatement has always done.
- A branch lane was placed half the branch's measured HEIGHT below the line, which
  is only right for content centred on that line. A branch holding a nested IF
  hangs entirely below its own: measured 160 tall it occupies 30 above and 130
  below, so the lane landed 50px inside it. The branch above is already built when
  the next lane is placed, so lowestBetween measures it instead - the same move
  separateRows already makes for wrapped rows. Its range is bounded at both ends,
  because a split's shared merge is appended among its branches.

Two tests hold the invariant rather than the pixel values: one asks the measurer
and the builder the same question about the same run and compares them, the other
asserts directly that no two elements of a built flow overlap, over eight shapes.
Each fix was reverted in turn to confirm they fail with the reported symptom.

TestEnumSplitGeometryIsUnchanged pinned the advance these fixes change; it is now
TestEnumSplitAdvanceClearsItsMerge and pins the gap, with the reason in its
comment. Its other half - that the merge does not drift with branch count - is
unchanged.

Closes mendixlabs#1158

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

AI Code Review

Critical Issues

  • None found

Moderate Issues

  • None found

Minor Issues

  • None found

What Looks Good

  • The PR addresses a clear bug (layout overlaps in complex microflows) with three specific, well-explained fixes.
  • Comprehensive test coverage added: new test file layout_agreement_test.go and modifications to existing tests verify the fixes.
  • Changes are localized to the layout calculation logic in the executor, with no MDL syntax changes (appropriate since it's a geometry-only fix).
  • All tests pass locally as stated, including mx check on the example file showing zero overlaps after fixes.
  • The fix is proven by reverting each change and confirming test failures with the reported symptoms.
  • CHANGELOG updated appropriately.
  • Code follows existing patterns and comments explain the rationale clearly.
  • No violations of the full-stack consistency checklist (not applicable as no MDL syntax changed).
  • Scope is atomic: focused solely on fixing layout overlaps without unrelated changes.

Recommendation

Approve the PR. The changes are correct, well-tested, and ready for merge.


Automated review via OpenRouter (Nemotron Super 120B) — workflow source

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Microflow layout: elements overlap where the measurer and the builder disagree — nested decisions, named merges, wide CASE

1 participant