Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
6bd5208
feat: attach an alter fragment's statement to a stored note by its te…
ako Oct 25, 2026
d9ea0b8
feat: splice plain activities inside a loop body, and add alter … rep…
ako Oct 25, 2026
7e56331
docs(skills): keep write-microflows under its 700-line body cap
ako Oct 25, 2026
01985ec
fix: take out a decision of the shape alter … insert produces (ako/mx…
ako Oct 25, 2026
50aa437
docs(skills): mark the refused @annotation(id:) example as wrong insi…
ako Oct 26, 2026
71ed649
feat(pages): spell a tab page's Badge in MDL, so a rewrite keeps it (…
claude Oct 26, 2026
dfb3a89
docs: pass the conformance gate with the new alter-microflow docs
ako Oct 26, 2026
8b9a194
fix: carry ReadOnlyStyle on text box, text area, date picker and radi…
claude Oct 26, 2026
e72464e
test: strike WorkflowEndedUserTask from the TestApp allowlist
claude Oct 26, 2026
b6fdaa3
Merge pull request #1707 from ako/claude/cool-keller-jtch0r
ako Oct 26, 2026
2e34fe9
Merge pull request #1706 from ako/claude/mxcli-bug-verification-24oro5
ako Oct 26, 2026
84e7585
fix: keep the stored language order when carrying translations (#1704)
claude Oct 26, 2026
3e9e809
fix: build layout arguments in the order the placeholder blocks give …
claude Oct 26, 2026
9b92a1e
test: strike six documents the language-order fix made round-trip
claude Oct 26, 2026
029db80
Merge pull request #1696 from ako/fix/1695-alter-note-by-text
ako Oct 26, 2026
6393f35
Merge remote-tracking branch 'origin/main' into fix/1693-splice-loop-…
ako Oct 26, 2026
a9a7997
test: strike ShareFeedback from the Studio Pro round-trip lossy list
claude Oct 26, 2026
43b5632
Merge pull request #1720 from ako/claude/mxcli-bug-verification-24oro5
ako Oct 26, 2026
1efe4f2
Merge pull request #1705 from ako/fix/1693-splice-loop-body-and-decis…
ako Oct 26, 2026
460c782
fix: unescape and re-escape a dynamic-text customDateFormat (#977 ite…
claude Oct 26, 2026
7dfb709
fix: refuse a SET that leaves a date picker Custom with no pattern (#…
claude Oct 26, 2026
fc6e3f7
fix: round-trip a text area's NumberOfLines and Autocomplete (#977 it…
claude Oct 26, 2026
5d33950
fix(workflow): check user task targeting XPath against the user entity
claude Oct 26, 2026
24c7c51
fix(workflow): end an interrupting boundary path with an End, as Stud…
claude Oct 26, 2026
9f06acb
docs(skills): task pages get a claim button and one complete-task but…
claude Oct 26, 2026
31979b3
feat(check): warn when a user task's page cannot complete one of its …
claude Oct 26, 2026
94e67b7
test: strike WithdrawConfirmation from the TestApp allowlist
claude Oct 26, 2026
31ffc4d
Merge pull request #1721 from ako/claude/serene-hopper-zxbr4u
ako Oct 26, 2026
313f4d9
fix: bound a text area's NumberOfLines to the stored 32-bit range
claude Oct 26, 2026
4de9d96
fix: parse a text area's NumberOfLines at the stored 32-bit width
claude Oct 26, 2026
c37366c
Merge pull request #1724 from ako/claude/mxcli-bug-verification-24oro5
ako Oct 26, 2026
89404ac
fix: carry a flow call's output mappings through a rewrite (#977 item 3)
claude Oct 26, 2026
8a55960
fix(workflows): read group XPath targeting back instead of dropping it
claude Oct 26, 2026
0863a2c
Merge pull request #1725 from ako/claude/serene-hopper-zxbr4u
ako Oct 26, 2026
49ea8ca
fix(check): refuse an XPath [%UserRole_X%] token naming no user role
claude Oct 26, 2026
2d77559
Merge pull request #1726 from ako/claude/mxcli-bug-verification-24oro5
ako Oct 26, 2026
aa97a0a
Merge pull request #1727 from ako/claude/serene-hopper-zxbr4u
ako Oct 26, 2026
dbf80aa
fix(workflows): refuse a rewrite that would reset settings MDL cannot…
claude Oct 26, 2026
ceb7118
feat: returns ( … ) syntax for flow-call output mappings (#977 item 3)
claude Oct 26, 2026
e0e4d0e
Merge pull request #1730 from ako/claude/serene-hopper-zxbr4u
ako Oct 26, 2026
e9fb55c
feat(workflows): give a timer boundary event a caption
claude Oct 26, 2026
eef0be0
style: gofmt the flow-call returns test
claude Oct 26, 2026
d748422
fix: describe fragment prints arguments and the whole action
claude Oct 26, 2026
8a90fbd
Merge pull request #1732 from ako/claude/serene-hopper-zxbr4u
ako Oct 26, 2026
22da642
feat(workflows): fill workflow and task texts from expressions, and n…
claude Oct 26, 2026
4e4afec
fix: check misses CE1406 for a retrieve or create of a Hidden entity …
ako Oct 26, 2026
b3b339e
Merge pull request #1731 from ako/claude/mxcli-bug-verification-24oro5
ako Oct 26, 2026
97ccc02
fix: check does not type a call microflow result, so a Hidden attribu…
ako Oct 26, 2026
fc69a5b
fix: log and send email store an expression with `with (…)` as its so…
ako Oct 26, 2026
e7cae7d
Merge pull request #1733 from ako/claude/serene-hopper-zxbr4u
ako Oct 26, 2026
d8f461b
fix: create java action writes the .java file without the class javad…
ako Oct 26, 2026
8ed83a7
feat(workflows): repeat a non-interrupting timer
claude Oct 26, 2026
cc14ec2
fix: describe an action with no target selected as a note (#1728)
claude Oct 26, 2026
78034d1
Merge pull request #1735 from ako/claude/serene-hopper-zxbr4u
ako Oct 26, 2026
9a10cee
feat: extra code clause for create java action, and describe prints i…
ako Oct 26, 2026
933717a
fix: keep a microflow data source's FormValidations (#1729)
claude Oct 26, 2026
bdbd9f6
Merge pull request #1737 from ako/claude/mxcli-bug-verification-24oro5
ako Oct 26, 2026
68a0360
Merge pull request #1734 from ako/fix/1699-hidden-member-access
ako Oct 27, 2026
9bdf723
Merge pull request #1736 from ako/fix/1683-templates-java-action-roun…
ako Oct 27, 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-26", "symptom": "A user task created with `targeting groups xpath [Name = 'Reviewers']` was stored correctly (`UserTargeting: Workflows$XPathGroupTargeting`), but `describe workflow` printed it with no targeting clause, so describe -> create or modify silently removed the targeting. Users xpath, users microflow and groups microflow on the same workflow described fine", "cause": "userSourceFromGen, the reader's switch from the polymorphic UserSource/UserTargeting part to sdk/workflows, had a case for every targeting $Type the writer emits except Workflows$XPathGroupTargeting, which fell through to NoUserSource. The writer (userTargetingToGen) and the describe formatter (the XPathGroupSource case in cmd_workflows.go) both handled it, so reading the formatter and the writer showed nothing wrong", "file": "`mdl/backend/modelsdk/workflow_read.go` (userSourceFromGen)", "insight": "When a describe clause is missing but the formatter has a case for it, the reader is the suspect: a missing case in a type switch on decoded gen types defaults silently. A table test that round-trips EVERY value the writer can emit through encode -> decode -> read catches the whole class, not just the reported kind; the per-kind tests that existed covered only the kinds someone had happened to describe. The executor's `TestRoundtripWorkflow_XPathTargeting` (integration) replays the describe output and compares, with the users-xpath task as the control", "fix": "Add the `*genWf.XPathGroupTargeting` -> `&workflows.XPathGroupSource{XPath: s.XPathConstraint()}` case; table test over all five targeting kinds, single and multi user task", "refs": ["ako/mxcli#1722"], "ce": []}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-25", "symptom": "`alter microflow … insert` with `@annotation(id: n1)` meant to attach the new activity to a note already in the flow was refused at exec — '@annotation(id: n1) refers to an annotation that has not been declared — the first mention must carry the text' — while check passed it as 'not predicted'; there was no other way to attach a fragment statement to a stored note", "cause": "A fragment is built by a fresh flowBuilder whose annotationsByLabel starts empty, and the `id:` labels describe prints (n1, n2, …) are re-derived on every describe and stored nowhere, so there is nothing for a fragment to resolve them against. check only learned of the builder's error in alterFlowViolations' pre-build loop, which files builder errors as dependencies ('not predicted') rather than refusals", "file": "mdl/executor/cmd_alter_flow_stored_notes.go (resolveStoredNotes, attachStoredNote, takeStoredNoteLines); mdl/backend/mfmutator/note_link.go (LinkNote); mdl/visitor/visitor_microflow_statements.go (parseNoteAnnotation `existing:`)", "fix": "New key `@annotation(existing: 'text')`: resolveStoredNotes (called by plan for exec and before the pre-build loop for check) resolves each text to exactly one stored note or refuses, listing the candidates with position and attachments; the builder links the stored note's ID; the lines are taken out of the fragment and drawn by MicroflowMutator.LinkNote after the splice, oriented with microflows.FacingSides (moved there from the executor so builder and splice share one formula) on the post-splice geometry. ValidateAlterFlow now also runs checkAnnotationLabels per fragment, so an undeclared `id:` is refused by check (MDL079) with a hint at `existing:`", "insight": "Two traps beyond the obvious. (1) Any refusal that lives in the fragment BUILDER is downgraded by check to 'not predicted — exec decides' (alterFlowViolations treats builder errors as dependencies on earlier statements), so a deterministic refusal must be decided before the build, by a function both plan and alterFlowViolations call. (2) A line from a stored note cannot ride in the fragment: the builder never knew where the stored note is relative to what it builds (sides come out as the bottom→top default), withoutNotes/singleActivity treat a line to a non-fragment object as a non-note so the error-handler and loop-body in-place replaces refuse, and Replace carries the target's own line to the entry so the same note gets two lines. Drawing it after the splice via a mutator op that dedupes and orients on current geometry fixes all three; each was shown by a revert (lines left in the fragment: origin 2/destination 0 instead of 0/2, and 2 lines on replace). Address by text, not by the describe-time id: the id is a derived name that moves (docs-wiki/bug-patterns/mutator-addressing.md); text is what the canvas shows, and the miss/ambiguity message lists the notes so a mismatch is visible", "refs": ["ako/mxcli#1695", "mendixlabs/mxcli#1517", "#1077", "mendixlabs/mxcli#1598", "mendixlabs/mxcli#1505"], "test": "TestAlterMicroflow_AttachesAStoredNoteByItsText, TestAlterMicroflow_StatedNoteTextIsANewNote (control), TestAlterMicroflow_StoredNoteLineFacesTheNote, TestAlterMicroflow_ReplaceAttachedToTheReplacedNote, TestAlterMicroflow_StoredNoteTextMustNameOneNote, TestAlterMicroflow_ExistingNoteMisuseRefused, TestAlterMicroflow_FragmentNoteLabelsUnchanged (mdl/executor/cmd_alter_flow_stored_notes_1695_pedapp_test.go); TestLinkNote_* (mdl/backend/mfmutator/note_link_test.go); TestExistingNoteAnnotation (mdl/visitor/visitor_existing_note_test.go)"}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-25", "symptom": "After `alter microflow … insert after $X begin if … end if; end` spliced a decision in, re-running the original `create or modify` (mdl 1) was refused — 'cannot drop the ExclusiveSplit … only an activity or a loop can be, because a decision or an end event changes the shape of the flow' — and so were `alter … drop '<caption>'` and `alter … replace '<caption>' with …`, so the script that created the flow could not be re-run", "cause": "The mutator's Drop/Replace went through removable(), which accepts an activity or a loop only: a decision is several nodes (split, path activities, merge) and the splice had no notion of taking a subgraph out; the statement diff already produced the right Drop/Replace op for the stored `if`", "file": "mdl/backend/mfmutator/splice_decision.go (insertedDecision, dropDecision, replaceDecision, pathNotes, DecisionNodes); mdl/executor/cmd_alter_flow.go (takenOut: path activities' outputs judged like the target's)", "fix": "Drop/Replace of a split validate it has exactly the shape `alter … insert` produces — two paths of plain activities (or none), each entered only by its predecessor, no error handler, rejoining at one merge entered by those two paths alone and leading on to one node — then rewire the split's incoming flows to the merge's successor (drop) or the fragment (replace) and take out the split, path activities, merge and the flows between them; notes attached only to path nodes go too. The shape check is the one verdict exec and check -p share (both run the mutator), and every out-of-shape decision is refused with the part that is out of reach ('its true path ends in a return', '… holds a decision', '… has an error handler', '3 flows join at its merge', 'it has 3 paths')", "insight": "No executor diff change was needed: the LCS already emitted Drop of the stored `if` (and Replace for a run starting at it), so the whole refusal lived in one mutator guard — check the verb the diff emits before designing a new op. Two traps for tests: the path activities' outputs must be judged like the target's (a variable a path declares and the flow reads after the merge), or the drop leaves a dangling reader; and a 'rebuild re-mints every $ID' control does not work inside one executor session — a flow dropped and created again there gets its stored identities back — so the churn control is a replaced activity (1 gone, 1 added)", "refs": ["ako/mxcli#1694", "ako/mxcli#1399", "mendixlabs/mxcli#1517"], "test": "TestSpliceDecision1694_* (mdl/executor/flow_splice_decision_1694_pedapp_test.go); TestSpliceDecision_* (mdl/backend/mfmutator/splice_decision_test.go)"}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-25", "symptom": "`create or modify microflow` (mdl 1) and `alter microflow` refused any change of a loop body's shape — an activity inserted, dropped or moved inside `loop … end loop` — with 'the Loop at (x, y) changes inside its body; the splice does not edit inside a loop', and named `alter … replace loop`, which re-minted every element ID in the loop", "cause": "A loop body's nodes are drawn in the loop box's coordinates, so the splice had no layout for them: graph.node() refused every body node and only an in-place one-for-one replace (#1106) was allowed; the diff paired a loop only when its body had the same shape (editableLoopBody)", "file": "mdl/backend/mfmutator/splice.go (spliceOnFlow/makeRoom scoped to a loop, appendInLoop, prependInLoop, dropInLoop, replaceInLoop, fitLoopTo, growLoop, ReplaceLoopBody); mdl/executor/cmd_flow_modify.go (inLoopBody, bodyGap, bodyRefusal); mdl/executor/cmd_alter_flow.go (inLoopRefusal, inBodyTarget, notPlainFragment); mdl/executor/flow_splice_loop_body.go (replaceBody)", "fix": "Shift/room/pin are scoped to a collection (the top level or one loop's body); an edited body is fitted back into its box, which grows on its far side along the line the loop sits on (from the side its incoming flow enters) with what is past it moving along, and symmetrically across it so the flows in and out stay level, recursing into an enclosing loop. The diff runs inLoopBody as an LCS over the body, plain activities only; a decision/loop/return/break/continue/raise/handler is refused naming the new `alter … replace body of <loop>`, which keeps the loop's $ID, position, iterator and list and rebuilds only its contents (the loop restated with its stored header is built, and its body taken)", "insight": "Three facts settled the design and are cheap to re-measure: body flows live in the unit's top-level Flows list, not the loop; an empty loop body passes mx check (0 errors on 11.13), so dropping a body's only activity needs no refusal — but mxcli stores an empty body without its Objects key, so a writer into it must add the list (marker 3); and `create` itself draws a tall nested loop over the parameter row, so a room check that counts parameters refuses layouts the builder makes. A body restated from describe states every @position, so body positions have to be pinned (PinInLoop) like top-level ones or the second run of a spliced script reads its own shifted nodes as 'moved'", "refs": ["ako/mxcli#1693", "ako/mxcli#1399", "mendixlabs/mxcli#1517", "ako/mxcli#1106", "mendixlabs/mxcli#1598"], "test": "TestSpliceLoopBody1693_* , TestAlterReplaceBody1693 (mdl/executor/flow_splice_loop_body_1693_pedapp_test.go); TestSpliceLoop_* (mdl/backend/mfmutator/splice_loop_test.go)"}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-26", "symptom": "`alter page … set (CustomDateFormat: '') on dp` on a date picker already DateFormat Custom reported Altered page and wrote Custom with no pattern; mx check: CE0493 \"Date format is custom but no format string is specified.\" (measured on 11.13.0 with the pre-fix binary)", "cause": "The only guard was inside the DateFormat setter (refuse Custom when the stored pattern is blank), relying on properties being applied in name order. A statement that only cleared the pattern never ran it, and a per-property check on CustomDateFormat would wrongly refuse `set (CustomDateFormat: '', DateFormat: Date)`, whose result is valid", "file": "mdl/executor/cmd_alter_page.go (checkDateFormatAfterSet), mdl/backend/pagemutator/mutator.go (CheckDateFormat)", "insight": "A rule that relates two properties is a rule about the widget the statement leaves behind, so check it after the whole SET, not inside either setter — the per-property guard is order-dependent in one direction and over-strict in the other. applySetPropertyMutator already had the post-statement seam (resetHiddenColumnProperties); an optional mutator capability keeps the MCP backend untouched. Scope the check to statements that name one of the two keys, so an unrelated edit to a widget already in the bad state is not blocked. The control that matters is a valid two-property statement in the same test", "refs": ["ako/mxcli#977"], "ce": ["CE0493"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area":"mdl/executor","date":"2026-10-26","symptom":"`check -p --references` passed `$r = call microflow AddOn.Request_GetFirst(); declare $s String = $r/Secret;` in an app module while Secret was a Hidden attribute of a Protected (add-on) module; exec wrote it and mxbuild refused CE1406 \"Create variable activity 'Create String variable' is not allowed to use attribute 'AddOn.Request.Secret' because app modules cannot reference hidden elements in add-on modules\". The same read through a parameter was reported (ako/mxcli#1701, mendixlabs/mxcli#1654).","cause":"walkTypedFlow — the one variable-typing walk the --references member checks share — typed parameters, create, retrieve and loop iterators but not the output variable of `call microflow`, so `$r/Secret` had no entity and pathMembers(\"\") returned nothing; the member-existence check (checkFlowMembers in validate_member_refs.go) was blind on call results the same way.","fix":"walkTypedFlow takes the scriptContext and types a call's output from the callee's object/list return type via scriptContext.microflowReturnEntity: the script's own definition first (sc.microflows + sc.flowParams), else the stored microflow read by name (microflowsNamed + hierarchy module match), cached per validation pass. validateProtectedModuleRefs now receives the sc validateProgram already builds.","file":"mdl/executor/validate_member_refs.go","insight":"Typing a variable is where a member check's reach is decided — a member check is only as wide as the walk that types its variables, and it fails silent, so widening the walk is the fix, not another check. But a type is a claim: typing from the STORED callee when the script rewrites it with another return type would judge members against the old entity and refuse a correct script (proved by stubbing the script-first branch: the existence check then reported 'Shop.Order has no member Name' for a callee rewritten to return Shop.Customer). So the script's signature must win, and an unresolvable callee must leave the variable untyped. Measured on 11.13.0 (module set Protected via UpdateModuleSettings, one microflow per shape): declare, end event, change on a call result and change on a loop over a returned list all CE1406; the Usable attribute clean.","refs":["ako/mxcli#1701","mendixlabs/mxcli#1654","mendixlabs/mxcli#1498"],"ce":["CE1406","CE1613"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-26", "symptom": "A data view's microflow data source stored with FormValidations None (ako/TestApp Pages.OutputMappingButton dataView1, Forms$MicroflowSource.MicroflowSettings) was rewritten as All on describe → exec: Replaced page, the only difference on the page", "cause": "The data-source writer called microflowSettingsToGen with nil settings, which writes Mendix's defaults, and neither the grammar nor describe had a place for a data source's call settings", "file": "mdl/grammar/domains/MDLPage.g4 (dataSourceExprV3 MICROFLOW … actionSettingsV3?), mdl/visitor/visitor_page_v3.go + visitor_action_settings.go (data source takes FormValidations only), sdk/pages/pages_datasources.go (MicroflowSource.FormValidations), mdl/executor/cmd_pages_builder_v3.go + cmd_alter_page.go, mdl/backend/modelsdk/widget_write.go (3 microflow-source sites), mdl/executor/cmd_pages_describe_datasource.go, cmd_fragments.go; test mdl/executor/datasource_formvalidations_1729_test.go", "insight": "Tally every field of the shared element under each parent before choosing a syntax: Forms$MicroflowSettings under a data source held non-default values in FormValidations only (44 TestApp + 4 PedApp sources, every other setting default), so the data source reuses the action's `with ( … )` rule with one key allowed rather than a new property. Before the grammar change `DataSource: microflow M.F with (…)` parsed without error as the deprecated `microflow M.F` ACTION form and left the widget with no source — an alternative that fails in one rule can succeed in a fallback rule, so a parse test with no errors proves nothing about which rule took it", "refs": ["ako/mxcli#1729"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-26", "symptom": "describe fragment printed `call microflow M.F` for `call microflow M.F(Order = $currentObject)` — flow and page arguments dropped, as were a microflow/nanoflow data source's arguments, `create object E then show page P` (printed `create`), `open link …` (printed `openLink`), a fragment action parameter (printed `param`), the fragment's own `($p: datasource, $q: action)` header, and a doubled quote in a string property; the output was not the fragment `use fragment` expands, and with a quote in it did not parse", "cause": "formatActionV3 / formatDataSourceV3 / formatASTPropertyValue in mdl/executor/cmd_fragments.go were written for the bare action kinds and fell through to the AST type name for the rest; describeFragment never printed Params", "file": "mdl/executor/cmd_fragments.go (describeFragment header, formatFlowArgsV3, formatActionV3, formatDataSourceV3, string quoting via quoteExpressionLiteral); test mdl/executor/cmd_fragments_describe_args_test.go", "insight": "For an AST printer the test that finds every gap at once is 'parse the output and compare the AST with what was parsed in', not a list of expected substrings: one round-trip test over a fragment using each action kind exposed seven drops in one run, where the original report named only the arguments. A `default: return a.Type` in a printer is a silent drop waiting for the next kind", "refs": ["ako/mxcli#1731"]}
Loading
Loading