Skip to content

Sync ako/mxcli: flow-graph geometry — note sides, loop/handler/parameter pitch, merges, per-outcome CASE @anchor - #1620

Merged
ako merged 14 commits into
mendixlabs:mainfrom
ako:main
Oct 21, 2026
Merged

ako merged 14 commits into
mendixlabs:mainfrom
ako:main

Conversation

@ako

@ako ako commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator

Syncs ako/mxcli:main into upstream: 12 commits from fork PR ako#1528. It fixes the flow-graph-geometry cluster: canvas positions, sides and merges that mxcli wrote differently from what a Studio Pro author would draw, and the describe and lint warnings around them.

Each defect has its own commit with a failing-first test, a control run, a mdl-examples/bug-tests/flow-geometry-*.mdl script and a finding.

Fixes #884, #923, #990 and #1587. Fixes cases 1–5 of #1598; case 6 is open, see "Not done here".

Fixes

Issue Defect Fix
#1598 case 2 Every note line was stored 2 → 0, so a note below its activity drew its line out of its own bottom The sides now face each other, vertical first. Notes use the activity numbering, as Studio Pro's own documents do
#1598 case 4 The merge after a loop inside an IF landed inside the loop box The layout now counts the loop's 100px iterator column
#923 while true with early returns gave three "NOT equivalent" warnings and three MDL-FLOW01 hits for a lossless flow A branch walk stops at its own split
#990 The @anchor help said "each end of the outgoing flow", but to: is the flow arriving Help, example, AST comments and skill are corrected, with the two-statement recipe for a vertical edge
#923 describe walked through a one-input merge (join finish; merge finish;) and warned that re-exec deletes it, which is no longer true On the main line it is described as join junctionN; merge junctionN;. Elsewhere the warning now says what is true
#884 The ON ERROR rejoin merge was stored on the End event (560;200 over 560;200), or right of the next activity The merge sits on the edge it splits. @merge still wins
#1598 case 3 The handler row sat 90px under its activity, inside the output label, and a THEN handler overlapped the ELSE row ErrorHandlerPitch is now 130, Studio Pro's median, and the layout counts the handler row
#1598 case 5 Parameters were 100px apart whatever their names The pitch follows the widest name, with a floor of 100, so names under 15 characters are unchanged. The old row still reads as derived
#1587 CASE outcome sides followed WHEN order only. @anchor(A: (from: right)) was refused (MDL092), and from: on a CASE was ignored New syntax @anchor(A: (from: right), (empty): (…)) sets sides per outcome. CASE from: is the merge exit, describe round-trips only non-default sides, and MDL092 reports bad keys
#884 An IF/ELSE ending a loop body got a merge with no way out No merge at a loop or while tail. @merge keeps one, and describe states a stored one
#1598 case 1 A note-only splice stored the note at x 3390 and shifted the row by +1180px Notes take no room in a splice, and a stated note is pinned

The @anchor per-outcome form is new MDL syntax. It covers grammar, visitor, builder, describe, mxcli syntax microflow.layout, MDL_QUICK_REFERENCE.md, the docs site, the skill, and the doctype example (02c, pattern 7). The generated parser is not committed.

Evidence

The conventions were measured, not assumed, from Studio Pro-authored documents:

  • Note lines: in 523 distinct note lines, the element side follows "vertical then horizontal" 98% of the time. Notes level with their activity store 1 → 3 / 3 → 1, which only the activity numbering explains.
  • Handler rows: across 160 of them, the median is 130px below the activity (quartiles 125–150).
  • Parameter rows: across 95 of them, the pitch grows with name length (160px median for 20–24 characters).
  • Enum outcomes: across 1,374 of them, sides follow geometry. That isn't the default for three or fewer cases, because describe reads WHEN order from those sides.

Controls. Every test fails with the reported symptom when its fix is reverted. End-to-end on an 11.13 app, comparing main and this branch:

main this branch
loop-in-branch merge 1070;200 (inside the box 790..1110) 1170;200
ON ERROR rejoin merge 560;200, the End event 425;200
note-only splice $E 2410→3590, note 3390;600 nothing moves, note 1500;600
describe warnings for the two while true test microflows 3 / 1 0 / 0

mx check: app copies with every new bug-test script and the full 02c doctype file give the same error set as the pristine copy.

Studio Pro 11.15.0-rc.4, via MCP: ped_check_errors reports nothing for the loop merge, the parameter rows, the note lines, the CASE flows or the loop-tail flow. Studio Pro stores exactly the positions this branch writes.

make build, make test, make lint, make check-mdl and make check-findings pass, and docs-wiki/bug-patterns/flow-graph-geometry.md is synced.

Not done here

  • Microflow canvas layout: six cases where mxcli writes positions, sides or splices that Studio Pro would draw differently or overlap #1598 case 6: an if/else inserted in a splice shifts the decision, merge and return by +339, but not the replaced activities. A re-run is then refused, and the no-else variant stacks two decisions. This needs a behaviour choice, so it is proposed as its own task:
    • pin stated positions and refuse a shift past them, or
    • carry the shift into later fragments.
  • The MCP backend's create path sends no connection sides and no notes, so @anchor has no effect over --mcp. This is a separate follow-up.
  • Known gaps: a CASE at a loop tail still gets a merge with no way out, and @merge on CALL WEB SERVICE is still ignored

ako and others added 14 commits October 21, 2026 00:51
Every AnnotationFlow was written 2 -> 0 (note bottom -> element top), so a
note below its activity drew its line out of its own bottom edge and round
both boxes. The builder now picks the facing sides once every object has
its final position, the writer writes them, and the reader carries stored
sides so a Studio Pro-drawn line survives a rewrite.

The rule is Studio Pro's, measured on 523 distinct Studio Pro-drawn note
lines: the element side follows vertical-then-horizontal separation in 98%,
and notes use the activity numbering (a note left of its activity stores
1 -> 3), not the separate numbering the issue proposed.

Refs #1508 (case 2), mendixlabs#1598

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
measureLoopStatement left out the 100px iterator column addLoopStatement
reserves on a foreach loop's left, so an IF holding a loop measured its
branch 100px short and put its merge inside the loop box (loop 790..1110,
merge 1050..1090), with the loop's outgoing line running right to left.
Both now use one LoopIteratorSpace constant.

Refs #1508 (case 4), mendixlabs#1598

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each decision's false branch walked round the loop's back edge into the
decision itself and on into its true branch, so its return counted as
shared: describe printed one 'NOT equivalent ... must not be re-executed'
warning per decision and lint raised MDL-FLOW01 as often, for a flow whose
describe -> exec writes nothing. A branch walk now stops at its own split.
A crossing diamond inside a loop stays Interleaved.

Refs #1277, mendixlabs#923

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The layout syntax topic said @anchor names 'which SIDE each end of the
outgoing flow attaches to'. The builder has always applied from: to the
flow leaving the statement and to: to the flow arriving at it, so an
author following the help got the edge into the next statement entering
on the left (an S-curve in Studio Pro) while describe echoed the authored
sides. The help, its example, the AST comments and the microflow skill now
say what is implemented, and give the two-statement recipe for a straight
vertical edge.

Refs #1279, mendixlabs#990

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
'join finish; merge finish;' builds a merge only one path enters, and
describe walked through it and warned that re-executing the description
deleted it. On the main line it is now described as written
(join junction1; merge junction1;), so the description rebuilds it and
there is nothing to warn about. In a branch or loop body it is still
walked through, and the warning now says what is true: create or modify
keeps the merge, a rebuild removes it.

Verified on PedApp 11.13.0: main binary 1 warning, fixed 0; describe ->
exec reports Unchanged as modify and as replace; 3 merges before and after.

Refs #1277, mendixlabs#923

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The merge a fall-through handler rejoins at was placed from the layout
cursor, which by then has moved past the successor: it landed on the end
event (merge 560;200 over end 560;200) or right of the next activity, with
the edge into it running backwards. It now sits midway between the guarded
activity's right edge and its successor's left edge, on the successor's
line; @merge still places it explicitly. The merge two paths into one end
event get is placed the same way.

Verified on PedApp 11.13.0: main 560;200, fixed 425;200.

Refs #1273, mendixlabs#884

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A handler row was centred 90px under its activity, inside the output
variable and type Studio Pro prints under the box, and a handler in a THEN
branch overlapped the ELSE row (+90 against +100). Handler rows now sit
130px under their activity - the median of 160 Studio Pro-authored handler
rows - and the measurer counts the row, so an ELSE row goes below it.

Refs #1508 (case 3), mendixlabs#1598

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Parameters without @position were written 100px apart whatever their
names, so 20-22 character names overlapped. The row is now spaced at about
7px a character of the widest name, rounded up to 10px and never under
100px, fitted to 95 Studio Pro-authored parameter rows. Names under 15
characters keep the old row, and the old 100px row still reads as derived,
so a flow an earlier mxcli wrote is re-spaced rather than pinned.

Refs #1508 (case 5), mendixlabs#1598

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A CASE stored each outcome's side by WHEN order alone, and MDL had no way
to say otherwise: @anchor(A: (from: right)) was refused with MDL092, and
@anchor(from: right) or true:/false: on a CASE were accepted and ignored.

  @anchor(from: bottom, Open: (from: right), (empty): (from: bottom, to: top))
  case $Order/Status ...

sets one outcome's edge per entry ((empty) and keyword values included),
and the CASE's own from: is the flow leaving its merge, as on an IF.
Describe writes an entry only where the stored side differs from the
builder's default, so an ordinary CASE stays annotation-free and a Studio
Pro-drawn one keeps its sides through describe -> exec. A key that names
no WHEN value, true:/false: on a CASE, or a value key off a CASE is MDL092.

The WHEN-order default stays for up to three outcomes: describe reads the
case order back from it.

Refs #1492, mendixlabs#1587

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both branches end the iteration, so the merge joined nothing and had no
way out; Studio Pro authors delete it by hand and every re-run drew it
again. At a loop or while tail the builder now leaves it out, also for an
IF/ELSE ending a branch of that IF. An empty branch, a continuing error
handler or an explicit @merge keeps it, and describe states @merge for a
stored merge with no way out, so a rebuild keeps one an earlier mxcli wrote.

Verified: PedApp 11.13 with the shape has the same mx check error set as
the pristine copy, and Studio Pro 11.15's ped_check_errors reports none.

Refs #1273, mendixlabs#884

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adding @annotation(…, position: (1500, 600)) to one statement of a
described flow spliced it in as a replacement whose box ran from the
activity to the note: the note was stored at x 3390 and every later
activity shifted by 1180px (a size-only note: 140..280px). A note now takes
no room on the line it documents, an unplaced note moves with its activity,
and a note at a stated position stays where the script puts it.

Verified on PedApp 11.13: main binary $E 2410 -> 3590 and the note at
3390;600; fixed, nothing moves and the note is at 1500;600.

Refs #1508 (case 1), mendixlabs#1598

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…carry meaning

Synthesised from this cluster's findings: settle a side or pitch from
Studio Pro's own documents, find every reader before changing a default
(CASE sides encode WHEN order), state a node the builder no longer draws,
and keep notes out of a splice's room.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fix: flow-graph-geometry cluster — note sides, loop/handler/parameter pitch, merges, CASE outcome anchors
@ako
ako merged commit 29dceb2 into mendixlabs:main Oct 21, 2026
15 checks passed
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 canvas: container Size, edge waypoints and merge positions are not writable — hand-quality layout impossible via MDL

1 participant