Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
174060f
fix(widgets): read switch arms and statement-head guards in editorCon…
claude Oct 28, 2026
e80aa61
fix(describe): leave out a pluggable widget property it hides at its …
claude Oct 28, 2026
9629790
fix(microflows): edit an activity replaced by one of the same kind in…
claude Oct 28, 2026
492728d
docs(microflows): say what a same-kind replace keeps
claude Oct 28, 2026
1fb340c
fix(canon): leave the end-of-path marker out of a parallel split belo…
claude Oct 28, 2026
0bb29a6
fix(modelsdk): give WidgetReturnType.assignableTo and GroupBox.header…
claude Oct 28, 2026
1d0fc4f
Merge pull request #1770 from ako/claude/loving-clarke-qun7w5
ako Oct 29, 2026
28463ff
fix(gitignore): ignore the unit-type index under testdata fixtures too
claude Oct 29, 2026
5ce460d
fix(layout): an error handler's row reserves the lane under its activity
claude Oct 29, 2026
559924d
fix(layout): a later @position no longer moves the statements before it
claude Oct 29, 2026
afb73a4
fix(lint): MPR008 compares events and type splits
claude Oct 29, 2026
0f56971
fix(executor): every nested flow body keeps the splice's member guard
claude Oct 29, 2026
fbf362e
fix(executor): type a stored retrieve over an association for a fragment
claude Oct 29, 2026
57e4454
fix(canon): store a user task's targeting as UserSource below Mendix …
claude Oct 29, 2026
7f288f3
fix(canon): write a user task as a Workflows$UserTask below Mendix 10.12
claude Oct 29, 2026
0a5b027
fix(wfmutator): set a user task's targeting whichever key stores it
claude Oct 29, 2026
aa39ffc
fix(executor): gate the user-task features older Mendix cannot store
claude Oct 29, 2026
4162b98
test(doctype): write user tasks in the Mendix 10 workflow script
claude Oct 29, 2026
70cf06b
test(bug-tests): measure the 1771 user-task shapes on Mendix 10.11
claude Oct 29, 2026
f4183ac
ci: add Mendix 11.16.0 to the nightly test matrix
claude Oct 29, 2026
ecf1c1d
Merge pull request #1772 from ako/claude/loving-clarke-qun7w5
ako Oct 29, 2026
b880586
Merge branch 'mendixlabs:main' into main
ako Oct 29, 2026
28f132d
Merge pull request #1779 from ako/claude/vibrant-knuth-3lhlkd
ako Oct 29, 2026
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-28","symptom":"Re-running `create or modify microflow|nanoflow` with only a filter's expression changed gave the activity, its ListOperationsAction and its FilterByExpression new $IDs, while a decision whose condition changed the same way kept its own; exec printed `spliced: 1 replaced` for both, and check, mx check, diff and describe were clean, so only a BSON dump or Studio Pro's version control (one activity removed, another added) showed it","cause":"The statement diff turns a changed activity into a Replace, and Mutator.Replace at top level appended the fragment's freshly built activity and removed the stored one. The in-place swaps that existed (swapInPlace: loop bodies #1106, handled activities #1489) kept the stored slot and geometry but still wrote the fragment's $IDs. The patch write (UpdateRawUnitPatch) deliberately skips canon.TransplantIDs, so nothing downstream re-paired them. Separately, two adjacent changed activities form one run, which became 'replace the first by both, drop the second' and could never be an edit","file":"mdl/backend/mfmutator/splice_edit.go (sameKind, swapKeeping, editInPlace, carryPairs, pairEntries, applyCarried), mdl/backend/mfmutator/splice.go (Replace, replaceInLoop, replaceKeepingHandler, Bytes), mdl/executor/cmd_alter_flow.go (noteEdited), mdl/executor/cmd_flow_modify.go (patchSummary, pairwiseSameKind)","fix":"A replace by one activity of the same activity and action $Type is written into the stored slot and paired structurally with the stored activity: same $Type at the same key, list entries by what they name (Attribute/Association plus Type, Parameter, LanguageCode, Name, Key), unnamed entries by position only for equal $Type sequences. The pairs are applied at Bytes over every binary in the unit, so the executor can keep naming the fragment's $ID (LinkNote of a stored note) until then. MicroflowMutator.EditedInPlace feeds the report: `edited in place` vs `replaced with new element IDs`. A run of N changed statements pairwise of one plain kind is split into N one-for-one gaps","test":"mdl/backend/mfmutator/splice_edit_in_place_test.go (six kinds byte-exact, members by name, another kind/operation is new, notes, loop body, error handler); mdl/executor/flow_splice_edit_in_place_1761_pedapp_test.go (six kinds x microflow/nanoflow on the real codec, a control that a log for a filter is new, and PedApp's Studio Pro-authored PopulateUserAttributes edited in one run). With the carry stubbed out the engine and executor tests fail with new $IDs; measured on 10.24.24: unfixed 1ccaa1aa->1c24b792 for the activity, fixed all kept, mx check 0 errors","insight":"The identity carry has to happen where the pairing is known, and that is the splice, not canon: the patch writer is told the splice owns its $IDs (ContentsOwnElementIDs), so a fix that hopes Reconcile will re-pair finds it switched off on purpose. Pair by name before position — a member list paired by index hands a removed member's $ID to its neighbour, which is the GUID-class mistake one level down. And apply the carry late: the executor still names the fragment's $IDs after Replace returns (linkStoredNotes draws a stored note's line to the fragment's activity), so the stored $IDs go on at Bytes, where one pass rewrites every reference too. The report half matters as much: a count that prints 'replaced' for both outcomes is why the bug read as harmless; the test that a decision 'kept its IDs' and a filter did not was only visible in a BSON dump","refs":["ako/mxcli#1761","mendixlabs/mxcli#1667","ako/mxcli#1106","mendixlabs/mxcli#1489","ako/mxcli#888"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/backend", "date": "2026-10-29", "symptom": "On Mendix 9.x to 11.1, `alter workflow M.W { set (Targeting: xpath […]) on Task; }`, and the deprecated `set activity 'Task' targeting …` form, printed 'Altered workflow' and changed nothing. describe showed the old targeting, and mx check stayed clean, because the old targeting was still valid", "cause": "Mutator.SetActivityProperty set the targeting with bsonnav.DSet(actDoc, \"UserTargeting\", …), and DSet only replaces a key that already exists. Below 11.2 a stored user task holds its targeting as UserSource, so no key matched, nothing changed, and the op returned nil", "file": "`mdl/backend/wfmutator/mutator.go` (setUserTargeting: replaces UserTargeting or UserSource in place, else appends; the write lowers it per version in canon.LowerToProjectShape)", "insight": "**A setter that only replaces an existing key is a silent no-op on a document in another version's shape.** bsonnav.DSet reports false, and every caller here ignored it. When a storage key is renamed at a version (ako/mxcli#1771: UserSource → UserTargeting at 11.2), grep the raw mutators for the new key: each one that DSets it is broken on the older versions, and the success message hides it. Have the mutator write the CURRENT shape in place of whichever key holds the fact, and let canon.LowerToProjectShape convert it. A second copy of the old shape in the mutator would be a second mapping to keep in step. **The control needs a pre-fix binary for the exact bug-test**: rebuilding with the committed file swapped in (go build -o scratch) showed 'Altered workflow' x2 with `[Name = 'admin']` kept on 9.24.30 and 10.24.24. Measured after the fix: 9.24.30 and 10.24.24 describe the new targeting, with 0 errors and the version's key set (Workflows$UserTask with UserSource on 9.24). 11.13.0 is unchanged. Tests: TestWorkflowMutator_SetTargetingReplacesAStoredUserSource and …AddsAMissingTargeting (both failed before the fix), TestNormalize_EditedMendix9UserTaskTargeting; mdl-examples/bug-tests/1771-alter-user-task-targeting.mdl", "refs": ["#1771"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area":"mdl/executor","date":"2026-10-28","symptom":"A Timeline enumeration the widget hides, set to a non-default value — `customVisualization: true, groupByDayOptions: 'dayMonth'`, or `groupByKey: 'day', groupByMonthOptions: 'monthYear'` — passes `mxcli check` and `exec`, and mx check reports \"[CE0463] The definition of this widget has changed\" at Timeline 'tlD' / 'tlG'. `mxcli widget describe timeline` says \"2 of 6 editor hide-rules recognized\"; `groupEvents: false` plus a hidden value was already an MDL-WIDGET10 error (ako/mxcli#1762, mendixlabs/mxcli#1668)","cause":"Two editorConfig shapes went unread. terser folds the statements before a `switch` into its head — `switch(e.customVisualization ? hide([…]) : hide([…]), e.groupEvents || hide([…]), e.groupByKey)` — so the THEN branch's guard is bounded by the `switch(` paren, which parseGuard rejected as a conditional group (the else branch was lifted, through trailingExpr). And a hide in a `case`/`default` arm had no reading at all: its `case \"day\":` colon reached the ternary-else path and found no `?`. The template warnings the reporter saw in custom mode came from the hand-written fallback table in widget_defs.go, which listed only the TextTemplate keys of that same hide, on the stated premise that enumerations cannot cause CE0463","file":"mdl/executor/editorconfig_switch.go (switchArmCondition, switchArms, statementHeadParen, atFunctionStatementLevel), editorconfig_extract.go (the switch-arm branch before parseGuard; parseGuard's `(` boundary), validate_widgets.go (visibilityCondWord in/notin), cmd_pages_describe_pluggable.go (withoutHiddenDefaults), widget_engine.go (WidgetDefGeneratorVersion 20)","insight":"**A known rule elsewhere is not the extractor knowing it.** The custom-mode warning made rule (1) look recognised, and it was the reason to ask why the enumerations in the same list were missed: `widget describe` (extractor only) showed no `title` rule, while the generated .def.json had one, merged from the hand table. Compare the two sources first when a rule half-works. **The reporter's framing (`the true branch of a ternary`) was not the cause**: the existing TimelinePattern test already lifted a statement-level THEN branch, and only the minified file showed that terser had moved it into the `switch(` head. Read the shipped JS, not the de-minified source. **Measure the matrix, then make it the oracle**: one widget per variant through `mx check --no-update-widgets` on 11.13.0 gave 6 CE0463 and 3 clean, and the extractor test, the check test and the bug-test all encode those nine. **Policy follows the existing split, not a second one**: hidden + non-default is refused (MDL-WIDGET10 error, exec preflight) and hidden + left out is written at the default, as rule (3) and the column-SET reset (mendixlabs/mxcli#1519) already do. **Knowing more rules made describe noisy**: every Timeline hides one of its two formats, and the generic describe printed every stored primitive, so its own output drew MDL-WIDGET10 warnings. describe now drops a hidden property at its default, using the same registry rules the check reads. **Bound the blast radius by dumping lifted rules for every installed .mpk before and after**: only Timeline and HeatMap changed. HeatMap folds into an `if(` head the same way, and its new rule is correct. **The scratchpad can be shared between agents**: work in a private subdirectory","refs":["ako/mxcli#1762","mendixlabs/mxcli#1668","ako/mxcli#258","mendixlabs/mxcli#1583"],"ce":["CE0463"],"rules":["MDL-WIDGET10"],"test":"mdl/executor/editorconfig_switch_1762_test.go; validate_timeline_hidden_1762_test.go; cmd_pages_describe_timeline_hidden_1762_test.go; cmd/mxcli/exec_preflight_timeline_1762_test.go; mdl-examples/bug-tests/1762-timeline-hidden-enumeration.mdl"}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-29", "symptom": "ako/mxcli#1769 (mendixlabs/mxcli#1673, 11.12.4): `alter microflow … { replace loop $Lookup in $Lookups with begin loop … begin $Line = find $Lines by Code = $Lookup/Code; … end loop; end; }`, $Lines retrieved over an association before the loop, stored the find as Microflows$FindByExpression `Code = $Lookup/Code`. check --references and exec reported nothing (\"Altered microflow\"), describe printed `find $Lines where Code = …`, mx check failed CE0117. create microflow writes Microflows$Find on MyModule.OrderLine.Code; the same find inserted on the main line was refused (#891's guard). `replace body of loop` had the same hole.", "cause": "Two. (1) #891's flowBuilder.qualifiedMembersOnly is set on the fragment's builder, but the loop, while and error-handler bodies are built by fresh flowBuilders whose fields each site listed by hand, and none listed the flag, so every member a nested body names (find/filter by member, sort, aggregate, change) at any depth was written bare. (2) alterFlowContext.storedVariables, the variables a fragment starts from, typed parameters, declares, creates, database retrieves and calls but left a retrieve over an association 'Unknown' (direction was the stated reason), so the member could not be qualified at all. Stored loop iterators were not typed either.", "file": "mdl/executor/cmd_microflows_builder.go, mdl/executor/cmd_microflows_builder_control.go, mdl/executor/cmd_microflows_builder_flows.go, mdl/executor/cmd_alter_flow.go, mdl/executor/cmd_microflows_builder_actions.go", "fix": "flowBuilder.nestedBuilder is the one constructor for loop, while and error-handler bodies and inherits qualifiedMembersOnly with the rest of the shared state (the handler still adds returnType and labelReg). storedVariables types a retrieve over an association from its start variable through flowBuilder.associationRetrieveType (lookupAssociation + entityIsSubtypeOf + associationRetrieveYieldsList, the answer the full build and check take: from the TO end the list of the FROM entity, from the FROM end the TO object, a reference set a list either way, owner Both one object) and a loop's iterator from its list, to a fixed point so candidate order does not matter. A self-reference, an unknown association, or a name two loops or retrieves give different origins stays untyped and its member is refused.", "test": "mdl/executor/cmd_microflows_builder_nested_test.go TestNestedBuilder_InheritsSharedState (every flowBuilder field classified inherited or own, checked against nestedBuilder by reflection); flow_splice_nested_guard_test.go TestSpliceFragment_NestedBodiesRefuseUnqualifiedMembers (find/filter/sort/aggregate/change in loop, while, handler, loop in loop, handler in loop, while in handler, if in loop; typed control); flow_splice_assoc_retrieve_test.go (both directions, reference set, one-to-one, chain out of order, iterator, self-reference, ambiguous iterator); flow_splice_nested_guard_1769_pedapp_test.go (PedApp, real codec: replace loop, replace body, insert in body, insert on main line all write Microflows$Find; over an untyped list all four refused with nothing written; describe->exec Unchanged). Controls: flag left out of nestedBuilder -> every nested shape written and the structural test names the field; storedVariables reverted -> the reported shape refused; ambiguity guard off -> $ItOrder typed. Platform 11.13.0: unfixed CE0117 (1 error); fix (1) alone refused, exit 1, stored flow byte-identical; fixed Microflows$Find on MyModule.OrderLine.Code, mx check 0 errors, describe->exec Unchanged.", "insight": "A guard set on a builder is only as good as the places that construct builders: grep `&flowBuilder{` before trusting a flowBuilder flag, and where a field is copied by hand at several sites, replace the copies with one constructor and a test that makes every field be classified. The reported statement was not the blast radius: every member-writing statement in every nested body, and `replace body of`, had it. Separately, an unqualifiable member is the symptom of an untyped variable; read what the fragment's starting scope types (storedVariables) before adding another refusal. Check reports a fragment-builder refusal as 'not predicted' with the reason, not MDL090, as it does #891's main-line refusal.", "ce": ["CE0117"], "refs": ["ako/mxcli#1769", "mendixlabs/mxcli#1673", "ako/mxcli#891", "ako/mxcli#885"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area":"mdl/executor","date":"2026-10-29","symptom":"Two consecutive activities with custom error handlers and no @position drew their handler rows on top of each other: the first handler's end event and the second handler's first activity both at 680;330 ($First 360;200, $Body 520;200, $Second 680;200). layout flows --dry-run said 'already laid out' (same builder), MPR008 said nothing (end events not compared), mx check 0 errors. A @position on a later statement also moved every unplaced statement before it onto that statement.","cause":"addErrorHandlerFlow built the row 130px under the activity, running right, but never reserved the lower lane, while clearLowerLane already made the next activity with a handler wait for a reserved lane (the #1154 guard design); measureStatements likewise reserved only for guards and left handler rows out of the extent that fitContainerSize counts, so a loop with a long trailing row was built 110px wider than measured. Case 2: buildFlowGraph's start pre-scan took the first statement WITH an @position instead of the first statement.","file":"mdl/executor/layout_lanes.go (handlerRowWidth, reserveHandlerRow), mdl/executor/layout.go (measureStatements), mdl/executor/cmd_microflows_builder_flows.go (addErrorHandlerFlow), mdl/executor/cmd_microflows_builder_graph.go (start pre-scan), mdl/linter/rules/mpr008_overlapping_activities.go","insight":"The lane model already stated the rule ('an activity with an error handler waits for the lane'); one of the lane's two occupants never registered itself. Look for the occupant that does not reserve before inventing a mechanism. Reserve from the measurer, in builder and measurer alike: the builder-only half (control) left 12 shapes with a merge landing on an activity. Studio Pro reference: all 8 multi-object handlers in PedApp and ako/TestApp are COLUMNS straight under the activity (80-130px per step), none is a row and none has a consecutive pair; a column layout would make the collision impossible but moves every flow with a 2+ statement handler and needs a vertical layout for compound handler bodies, so the lane was kept (2 of 182 example flows move). Two older measurer gaps surfaced by the new invariant test and were left alone: an IF is measured 45px short (the split's centre is counted as its left edge in guardBranchInset and measureIfStatement), and a `join` is measured as an activity plus an end event although it draws nothing, so a handler row ending in joins reserves about twice its drawn width. Check overlaps with the whole node set: MPR008 skipped events, which hid exactly this overlap; on 292 Studio Pro flows the wider set adds 0 findings.","refs":["ako/mxcli#1768","mendixlabs/mxcli#1672"],"test":"TestSecondErrorHandlerRowClearsTheFirst, TestErrorHandlerRowsNeverOverlap, TestLoopAroundHandlerRowsIsMeasuredAsBuilt, TestStatementsBeforeAPlacedOneKeepTheirDerivedPlaces, TestOverlapComparesEvents"}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-29", "symptom": "`mxcli check -p` passed scripts with user-task features the project's version cannot store, and exec wrote them: group targeting below 11.2 (CE1859 on 10.24, the targeting emptied by mx convert), and multi-user task rules and `await all users` below the version that introduced them. Once the user task was lowered to its pre-10.12 shape, these were refused only at write time, part-way through a script", "cause": "statementVersionGates had no gate for anything inside a workflow user task. The generated version data floors properties, never types, and the rule types (MajorityCompletionCriteria, ThresholdCompletionCriteria 10.0; MicroflowCompletionCriteria 10.3), the group targetings (11.2) and the multi-user task itself (UserTaskCompletion 9.22) are type-level introductions", "file": "`mdl/executor/version_gates.go` (userTaskGates, called from the CreateWorkflowStmt and AlterWorkflowStmt cases); `sdk/versions/mendix-9|10|11.yaml` (workflows.multi_user_task 9.22.0, multi_user_task_majority_threshold 10.0.0, multi_user_task_await_all 10.2.0, multi_user_task_microflow_rule 10.3.0, group_targeting 11.2.0)", "insight": "**A write-time refusal is the last line, not the check.** canon.LowerToProjectShape refuses what the older shape cannot hold, but by then the statements before it are written. Each such refusal wants a matching MDL-VERSION01 gate, so `check -p` reports it and exec refuses the script before writing anything ('Refusing to execute: 4 error(s) above. Nothing was written.' on 9.24.30). **Gate the clause, not the statement**: a workflow with a consensus or veto multi-user task is fine from 9.22, so the gates walk the activities (create, and every activity alter workflow inserts) and fire only for a rule, a targeting kind or `await all users` the version lacks. **Read the type floors from mendixmodelsdk's StructureVersionInfo** (gen/workflows.js; text-extract it, never execute it). The generated version.go has no row for a type. Measured with the bug-test: check -p on 9.24.30 reports WF_Majority (10.0), WF_Await (10.2), WF_Microflow (10.3) and WF_Groups (11.2). 10.24.24 reports WF_Groups only. 11.13.0 reports none, exec writes all five, and mx check gives 0 errors. Control: TestCheckVersionGates_UserTaskFeatures failed for every case before the gates. Tests: TestCheckVersionGates_UserTaskFeatures, …AtTheirFloors; mdl-examples/bug-tests/1771-user-task-features-by-version.mdl", "refs": ["#1771"]}
Loading
Loading