Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area":"mdl/backend","date":"2026-10-21","symptom":"Adding one `@annotation(text: 'note D', position: (1500, 600), size: (200, 80))` to a described flow and re-running it as create or modify stored the note at x 3390 instead of 1500 and shifted the three later activities by +1180px; with size-only notes every activity after the first noted one shifted by +140..+280px. mx check: 0 errors. With @position on every statement nothing moved.","cause":"Plain describe omits positions, so the re-noted statement is not declaredMatches and becomes a 1:1 Replace whose fragment is unplaced. fragmentGeometry boxed every fragment object, notes included: activity at 300..420 and note at 1400..1600 gave a 1300px box, Replace's `extra = length - size` shifted everything past the cut by 1180, and placeCentred translated the note with the box (1500 + 1890 = 3390). A size-only note is centred on its activity, so a 260/400px note made extra 140/280.","file":"mdl/backend/mfmutator/splice.go (fragmentGeometry, placeCentred), mdl/backend/microflow_mutation.go (MicroflowFragment.Pinned), mdl/executor/cmd_microflows_builder_annotations.go (recordStatedNote), cmd_alter_flow.go (buildFragment), cmd_microflows_builder_flows.go (handler merge-back)","insight":"The agent's arithmetic from source reproduced every reported number (1180, 3390), and the unit test then reproduced them in the mutator (b 420 -> 1600) before the fix — so trust the arithmetic, but run it. A note sits off its line and takes no room on it: leave notes out of the box the splice makes room for, translate an unplaced note with its activity, and leave a note with a stated (absolute) position where the script put it. Loop-body notes need no pin: they are children of the loop and move with its box. Still open: a note-only change is a Replace at all, so the activity gets a new $ID; an AddNotes op would keep it.","refs":["ako/mxcli#1508","mendixlabs/mxcli#1598"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area":"mdl/executor","date":"2026-10-21","symptom":"A CASE stored each outcome flow's OriginConnectionIndex by WHEN order only (1st 0/top, 2nd 1/right, 3rd 2/bottom; 4+ grouped thirds). `@anchor(A: (from: right))` was refused with MDL092, `@anchor(from: right)` on the CASE and `true:`/`false:` were accepted and silently ignored, and describe never emitted a CASE's outcome sides, so a Studio Pro-drawn CASE lost them on describe -> exec.","cause":"parseAnchorAnnotation knew only from/to/true/false/iterator/tail keys and annotationParamName could not spell `(empty)` or a keyword value; addEnumSplit consumed the CASE's annotations without reading any anchor and never set nextFlowAnchor; emitSplitAnchorAnnotation only looked for true/false flows and mergeExitAnchor returned \"\" for a non-Boolean split","file":"mdl/grammar/domains/MDLSettings.g4 (annotationParamName), mdl/visitor/visitor_microflow_statements.go (parseAnchorAnnotation), mdl/ast/ast_microflow.go (CaseAnchors), mdl/executor/cmd_microflows_builder_actions.go (splitCaseSlot.applyCaseAnchor, addEnumSplit), validate_microflow.go (checkCaseAnchors), cmd_microflows_show_helpers.go (enumCaseAnchorFragments, mergeExitAnchor)","insight":"Do NOT make the default follow geometry for up to three cases, tempting as the data is (Studio Pro: straight below -> bottom 467/467, level right -> right 269/286): describe reads the WHEN order back from those sides (splitCaseOrder), and TestEnumSplitRoundtripPreservesEmptyCaseBeforeNamedCases fails the moment a branch placed out of order changes side. The per-outcome form is the escape hatch, and describe writes an entry only where the stored side differs from what the builder computes for that branch (same slot.apply, same position in describe order), so an ordinary CASE stays annotation-free. `to:` goes in the CASE entry only for a flow into a merge; into an activity it is that activity's own @anchor(to:). A bare `name: (…)` on an IF now parses as an outcome key and is refused by validation instead of the visitor. Over --mcp the create path sends no connection sides for ANY flow, so Studio Pro shows its right->left default there; verify sides on the modelsdk engine.","refs":["ako/mxcli#1492","mendixlabs/mxcli#1587"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area":"mdl/executor","date":"2026-10-21","symptom":"An error handler's row was centred 90px under its activity (retrieve at 360;200, handler activity at 360;290): a 30px gap, inside the output variable and type Studio Pro prints under the box. A handler in a THEN branch also overlapped the ELSE row (+90 against +100). mx check: 0 errors.","cause":"addErrorHandlerFlow (and the web-service call and the pending-handler terminal end event) used VerticalSpacing, a constant shared with type-split branches and RowGap; measureStatement gave an activity with a handler body the height of a bare activity, so the IF placed its ELSE row as if the handler row did not exist","file":"mdl/executor/layout.go (ErrorHandlerPitch, measureStatement), cmd_microflows_builder_flows.go (addErrorHandlerFlow), cmd_microflows_builder_calls.go, cmd_microflows_builder_graph.go","insight":"The issue's ~140px was an estimate; Studio Pro's own documents settle it: 160 distinct handler rows straight under their activity (ako/TestApp, PedApp, seven .mpk apps) have a median offset of 130px, quartiles 125..150, minimum 90. IF branch rows measured the same way sit a median 134px under the split, but mxcli's +100 else row already leaves 40px, clear of the ~35px label, so only the handler pitch changed; give it its own constant rather than raising VerticalSpacing, which also drives RowGap and type-split branches. Write the measured number into the test literally: a test comparing against the builder's own constant passed at 90 and proved nothing.","refs":["ako/mxcli#1508","mendixlabs/mxcli#1598"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area":"mdl/executor","date":"2026-10-21","symptom":"An IF/ELSE as the last statement of a loop or while body got an ExclusiveMerge with no outgoing flow: both branches end the iteration, so it joined nothing. Studio Pro authors delete it by hand (the reporter's hand-polished flow had 2 merges, mxcli's rebuild of the same MDL 4), and since describe printed the same MDL with and without it, every re-exec drew it again.","cause":"addIfStatement decided needMerge from returns alone; the loop-body loops in addLoopStatement/addWhileStatement never told a statement it was last, so nothing knew the IF's continuation was the end of the iteration","file":"mdl/executor/cmd_microflows_builder_control.go (atBodyTail, addIfStatement tailWithoutMerge, both loop-body loops), cmd_microflows_builder.go (flowBuilder.atBodyTail), cmd_microflows_show_helpers.go (emitMergeAnnotation)","insight":"The claim the fix rests on — Mendix accepts two dead-end branches at a loop tail — was run, not assumed: PedApp 11.13 with the shape built gives the identical mx check error set to the pristine copy, and Studio Pro 11.15's ped_check_errors reports none. Keep the merge when either branch is empty (a split needs a flow per case: CE0079), when a branch ends in a continuing custom error handler, and when @merge is written; pass the tail on to the last statement of each branch so a nested IF/ELSE at the tail loses its merge too. The read side is the trap: a stored tail merge (an earlier mxcli's) sits at the OLD derived position, so the canonical describe treated its @merge as derivable and dropped it, and the rebuild then removed the node — emitMergeAnnotation now always states @merge for a split merge with no way out. assertDescriptionRebuildsGraph compares graphs modulo merges and cannot see any of this; count merges.","refs":["ako/mxcli#1273","mendixlabs/mxcli#884"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area":"mdl/executor","date":"2026-10-21","symptom":"The merge after an IF whose branch holds a foreach LOOP landed inside the loop's box: LoopedActivity 950;200 320x160 spans x 790..1110, ExclusiveMerge 1070;200 spans 1050..1090, so the loop's outgoing line ran right to left into it. mx check: 0 errors.","cause":"measureLoopStatement sized a loop as body + 2*LoopPadding while addLoopStatement builds body + 2*LoopPadding + iteratorSpace (a local 100); the IF's mergeX comes from the measured branch width, so it fell 100px short for every foreach loop at every nesting level. WHILE has no iterator column and no mismatch.","file":"mdl/executor/layout.go (LoopIteratorSpace, measureLoopStatement), mdl/executor/cmd_microflows_builder_control.go (addLoopStatement)","insight":"The issue's cause was right in effect but not in mechanism: the builder's `+iteratorSpace` width is overwritten by fitContainerSize after the body is built, so the column reaches the final width through innerStartX instead. The measurer still has to include it, because the IF reads the measured width before the loop exists. A centre-to-centre layout invariant (merge.X >= loop.X) passed throughout, so the test compares box EDGES (merge.X - MergeSize/2 >= loop.X + Width/2); TestLayoutInvariants_NoBackwardMergeAndEndRightmost treats a LoopedActivity as ActivityWidth wide and could not see it.","refs":["ako/mxcli#1508","mendixlabs/mxcli#1598"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area":"mdl/executor","date":"2026-10-21","symptom":"Every Microflows$AnnotationFlow was written OriginConnectionIndex 2 -> DestinationConnectionIndex 0 (note bottom -> element top) wherever the note sat, so a note placed BELOW its activity drew its line out of its own bottom edge and round both boxes. The reader dropped the stored sides too, so a rewrite of a Studio Pro-drawn note below its activity flipped it to 2 -> 0.","cause":"annotationFlowToGen hardcoded SetOriginConnectionIndex(2)/SetDestinationConnectionIndex(0), and microflows.AnnotationFlow had no field for the sides, so neither the builder nor the reader could say otherwise","file":"mdl/executor/cmd_microflows_builder_annotations.go (orientAnnotationFlows, facingSides), mdl/backend/modelsdk/microflow_write.go (annotationFlowToGen), mdl/backend/modelsdk/microflow.go (annotationFlowsFromGen), sdk/microflows/microflows.go (AnnotationFlow.Sides)","insight":"The issue proposed a separate note numbering (top 0, bottom 1, left 2, right 3); measuring Studio Pro's own documents refuted it in one pass: 891 AnnotationFlows from ako/TestApp, PedApp and the mx-test-projects .mpk apps (523 distinct). A note level with its activity on the left stores 1 -> 3 and on the right 3 -> 1, which only the activity numbering (0 top, 1 right, 2 bottom, 3 left) explains; a note below stores 0 -> 2 (58 of 67). The element side follows 'vertically clear -> top/bottom, else left/right' in 98%; the NOTE side is not predictable from geometry (Studio Pro often stores 1 -> 0 for a note above, presumably from where the author dragged), so do not chase the origin past 'the facing side'. Sides are chosen after separateRows, because notes are placed before rows and containers settle, and loop-body positions are relative to the loop's top-left, so the comparison is done in absolute coordinates.","refs":["ako/mxcli#1508","mendixlabs/mxcli#1598"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area":"mdl/executor","date":"2026-10-21","symptom":"The merge an ON ERROR handler falls through to was stored on top of the next node: `@position(240,200) call … on error without rollback {…}; @position(560,200) return;` gave ExclusiveMerge 560;200 40x40 and EndEvent 560;200 20x20. With an activity after the guarded one, the merge landed RIGHT of that activity and the edge into it ran backwards. Moving the RETURN moved both.","cause":"addErrorHandlerRejoinFlowForState (and the empty-handler variant, and mergeOverConnectedEndEvents) placed the merge at fb.posX - HorizontalSpacing/2, Y fb.baseY. The rejoin is wired after addStatement(next), when the cursor has already moved past the successor (an end event advances it by spacing/2, an activity by a full spacing), and baseY is never updated inside an IF branch","file":"mdl/executor/cmd_microflows_builder_flows.go (mergeOnEdge, objectByID, the two rejoin sites, mergeOverConnectedEndEvents)","insight":"The issue's 'make @merge work on ON ERROR' half was already done (#750: finishCustomErrorHandler reads @merge into rejoinMergeAt); the real defect was the default. Place the merge from the two nodes it sits between — midway between the origin's right edge and the destination's left edge, on the destination's line — and keep the cursor formula only for an edge that does not run left to right (a row wrap). With default spacing the 40px merge exactly fills the 40px gap between two activities: no overlap, no daylight; widening the gap would shift every downstream node and needs the measurer to agree. CALL WEB SERVICE still bypasses finishCustomErrorHandler, so @merge is ignored there. Verified on PedApp 11.13.0: main 560;200 (on the end event), fixed 425;200.","refs":["ako/mxcli#1273","mendixlabs/mxcli#884"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area":"mdl/executor","date":"2026-10-21","symptom":"`join finish; merge finish;` (a merge only one path enters) was written by exec and then described as nothing, with '-- WARNING: the merge at (1330, 350) is not represented in this description ... re-executing this MDL DELETES it' (mendixlabs/mxcli#923 MF_RT_Chain). The warning had also stopped being true: `create or modify` from the description reports Unchanged and keeps the merge.","cause":"traverseFlow walks through any unlabelled merge that no split pairs with, so a one-input merge left no trace in the text; droppedMergeWarnings then flagged it with wording from before modify-in-place compared descriptions instead of rebuilding","file":"mdl/executor/cmd_microflows_show_helpers.go (traverseFlow pass-through), mdl/executor/cmd_microflows_show_merge.go (inlineMerges, droppedMergeWarningsIn), mdl/executor/cmd_microflows_derived_layout.go (memo mergeLabels)","insight":"Only the MAIN LINE (indent 0) can spell it back: there a `merge` after a `join` has no open path, so the builder draws exactly one edge into it. In a branch body the branch loop would add its own fall-through edge (no pathOpen guard), and MDL-FLOW04 bans labels in loop bodies, so those keep the (reworded) warning. Labels are minted lazily in walk order (junction1, …) so unused candidates leave no gaps. Two traps: assertDescriptionRebuildsGraph decided 'is this a whole statement?' with strings.Contains(src, \"create \"), which a warning mentioning `create or modify` defeats (fixed to a line check); and the graph comparison is merge-blind, so a merge-count assertion is needed to see the fix at all. Verified on PedApp 11.13.0: main binary 1 warning, fixed 0; describe -> exec Unchanged as modify AND as replace; 3 merges before and after.","refs":["ako/mxcli#1277","mendixlabs/mxcli#923"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area":"mdl/microflowgraph","date":"2026-10-21","symptom":"`while true begin if $C = 'a' then return 'A'; end if; … set $S = 'x'; end while;` was described with one '-- WARNING: … NOT equivalent to the microflow and must not be re-executed over it (mxcli #923, recombinable)' line per decision, and lint raised MDL-FLOW01 as often ('overlap 1 node(s)'), although describe -> exec reported Unchanged and mx check gave 0 errors.","cause":"classify measured each branch body with reachableBefore(branch, join), stopping only at the join and the virtual exit. Each early return is its own end event, so the join is the exit; the false branch walked set -> header merge -> the decision itself -> its true branch's end event, which then counted as overlap.","file":"mdl/microflowgraph/structure.go (classify, reachableBefore)","insight":"The reporter's diagnosis (back edge, structure.go) was exact; the minimal fix is to stop each branch walk at its own split — a branch that re-enters its split has gone round a loop. Stripping back edges via dominators would also work but changes the post-dominator input for every consumer (describe warnings, lint, labelCrossedMerges, normalizeCollection). Keep the crossing-diamond-inside-a-loop control: it must stay Interleaved, and it is the case a cruder 'ignore nodes that reach the split' filter would hide, since inside a loop every body node reaches the split.","refs":["ako/mxcli#1277","mendixlabs/mxcli#923"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area":"sdk/microflows","date":"2026-10-21","symptom":"Parameters without @position were written at 200;53, 300;53, 400;53 whatever their names, so LongParameterNameOne/Two/Three (20-22 characters) overlapped on the canvas. describe prints no parameter positions, so nothing headless showed it.","cause":"DerivedParameterPosition(idx) returned 200 + idx*100; the MCP backend repeated the same arithmetic inline","file":"sdk/microflows/microflows.go (ParameterPitch, DerivedParameterPosition, AuthoredParameterPosition, legacyParameterPosition), mdl/backend/modelsdk/microflow.go (splitFlowObjects, now two-pass), microflow_write.go, nanoflow_write.go, rule_write.go, mdl/backend/mcp/microflow.go, mdl/executor parameter sites","insight":"The pitch now depends on every name, so the reader must read all parameters before deciding which positions are authored (splitFlowObjects is two-pass). The trap the reporter flagged is real: AuthoredParameterPosition compares against the derived point, so without also accepting the OLD 100px row every long-named parameter an earlier mxcli wrote would read as hand-placed and stay overlapped. Fitted to 95 Studio Pro-authored parameter rows (median pitch 95/106/125/160px for names of 5-9/10-14/15-19/20-24 characters): ~7px a character, rounded up to 10, floor 100 — names under 15 characters keep the old row, so ordinary flows do not move. 73% of 1,109 multi-parameter Studio Pro flows stack parameters in a COLUMN (median pitch 95px) instead; switching to that would move every generated flow's parameters and was not done.","refs":["ako/mxcli#1508","mendixlabs/mxcli#1598"]}
Loading
Loading