Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c72e326
mdl: list operations and aggregates as one statement per activity; se…
ako Sep 27, 2026
48c43d5
describe: print list activities as statements under mdl 1
ako Sep 27, 2026
d80cefe
docs: write-microflows skill and syntax topic use the list statement …
ako Sep 27, 2026
4580e85
canon: a patch of the stored unit owns its element $IDs (ContentsOwnE…
ako Sep 27, 2026
85ac9cd
mfmutator: graph splice into the stored microflow (insert after/befor…
ako Sep 27, 2026
ab4e9e2
alter microflow/nanoflow: grammar, fragment building and scope checks…
ako Sep 27, 2026
e184384
describe with handles: the error-handler block's { is not part of the…
ako Sep 27, 2026
02979d9
mcp: alter microflow insert over the Studio Pro MCP backend
ako Sep 27, 2026
884f8a7
mdl: a trailing comma is allowed in every bracketed list (#732)
ako Sep 27, 2026
534a595
mfmutator: drop/replace of a loop removes its body flows
ako Sep 27, 2026
7abe76b
alter microflow: scope checks span the whole statement
ako Sep 27, 2026
bba6bd2
mdl 1: `;` terminates every statement and `/` is not a terminator (#732)
ako Sep 27, 2026
3810436
mdl 1: '' is the only string escape; a backslash is an ordinary chara…
ako Sep 27, 2026
b3516b5
refactor: move the OData property did-you-mean into mdl/suggest
ako Sep 27, 2026
7e18dbd
mdl 1: an unknown property key or a mis-shaped value is an error (#732)
ako Sep 27, 2026
0a98db1
mdl: retrieve … first is the object; limit 1 is a list of one under m…
ako Sep 27, 2026
ebe0282
docs: teach retrieve … first for an object; limit 1 changes meaning u…
ako Sep 27, 2026
7be826b
mdl 1: read a statement's terminators off its last tokens (#732)
ako Sep 27, 2026
17ba552
docs: what mdl 1 makes strict, and trailing commas (#732)
ako Sep 27, 2026
20c2af7
mdl 1: hint at the doubled apostrophe when \' ends a string literal (…
ako Sep 27, 2026
1d36f4b
Merge branch 'c2-739' into c2-737
ako Sep 27, 2026
67305ee
Merge branch 'c2-737' into c2-740
ako Sep 27, 2026
0dd61fe
describe: end every statement with `;` and never print a `/` line (#744)
ako Sep 27, 2026
458361e
diff: render statements without the `/` line, like describe (#744)
ako Sep 27, 2026
c7a32d7
Merge remote-tracking branch 'origin/feature/744-describe-terminators…
ako Sep 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
3 changes: 3 additions & 0 deletions .claude/skills/fix-issue/findings/mdl-executor.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -728,3 +728,6 @@
{"area": "mdl/executor", "date": "2026-09-26", "symptom": "`describe fragment from page M.P widget w` (and `from snippet`) fails for every container and every widget — including ones `describe page` prints — with `not found in page M.P`, a message that does not even name the widget", "cause": "Visitor stores DescribeFragmentFromStmt.ContainerType as \"PAGE\"/\"SNIPPET\"; describeFragmentFrom switched on \"page\"/\"snippet\" with no default, so neither branch ran, the widget list stayed empty, and the fall-through reported the widget missing. Third instance of this split: ALTER PAGE (#402), DESCRIBE/ALTER STYLING (#631)", "file": "`mdl/executor/cmd_fragments.go` (`describeFragmentFrom`)", "insight": "The mismatch hid behind a plausible error because a switch on the discriminator had no default: an unmatched container type looked like an empty container, and an empty container looks like a missing widget. Normalise with strings.ToLower where the discriminator is consumed (the house convention — cmd_styling, cmd_alter_page, validate_alter_* all do) AND make the default an error, so the next casing drift fails loudly instead of reporting the wrong thing. The existing mock tests hand-built the AST and so agreed with the handler; only a test that goes visitor.Build → NewRegistry().Dispatch pins the contract between the two layers (cmd_fragments_from_test.go). Verified on Evora: Administration.Account_Edit/textBox6 and AgentCommons.Snippet_Agent_Details/dataView7 now describe", "refs": ["#402", "#631"]}
{"area": "mdl/executor", "date": "2026-09-26", "symptom": "describe output that does not re-parse or loses data (ako/mxcli#707): an entity string default or validation message containing ' was emitted unescaped; so were module-role descriptions, published OData/REST Path/Version/Namespace/Summary/Folder, and REST client BaseUrl/Path/header values; an agent `mcp service` block with a Description lacked the comma after `Enabled`; workflow decision / parallel split captions came back only as `-- caption` comments (replay reset them to 'Decision' / 'Parallel split'); `describe demo user` emitted `password '***'`, which replay stored as the password; `describe settings` printed `DatabasePassword = '<plaintext>'`.", "cause": "Hand-rolled `'%s'` emit sites that put the quotes and the escaping in different places (the #1006 source-scan guard covered only cmd_workflows.go); a block emitter with no separator logic, unlike its sibling; captions treated as commentary although the grammar has `comment '…'` for both activities; secrets printed as data, with a placeholder the writer took literally.", "file": "mdl/executor/cmd_entities_describe.go, cmd_security.go, cmd_security_write.go, cmd_odata.go, cmd_published_rest.go, cmd_rest_clients.go, cmd_agenteditor_agents.go, cmd_workflows.go, cmd_settings.go", "fix": "Every emit site uses mdlQuoted; TestDescribers_HaveNoHandRolledStringLiterals now scans all seven describer files. MCP block writes the comma like the tool block. workflowCaptionClauses emits `comment '…'` for a non-default caption and computes the name clause against the caption the writer will store. DatabasePassword is omitted with a comment (create or modify is a patch, so replay keeps it). Demo users are described as `create or modify … password '***'`, and the executor treats '***' as 'keep the stored password', refusing it for a user that does not exist.", "insight": "Assert round trips by reparsing describe output with the real visitor and comparing the AST value to the stored one, not by substring. For secrets the right placeholder is one the WRITER understands: omission works where the create is a patch (configuration); where the grammar requires the value (demo user) give the placeholder a meaning (keep stored) and refuse it where that meaning is empty, so a replay can neither leak nor silently set a credential.", "test": "mdl/executor/issue707_describe_roundtrip_test.go"}
{"area": "mdl/executor", "date": "2026-09-26", "symptom": "describe microflow \u2026 with handles (ako/mxcli#713) printed no handle for an activity inside an `on error { \u2026 }` block, and the alter-target resolver counted such activities after the whole main flow: on SUB_Feedback_PostToAppInsights `return * @1` picked `return $Response`, although describe prints the handler's `return empty` first. Also `$Response` did not address a REST call whose output is on its result handling (and cast, create list, web service, workflow, XML/JSON, database-query outputs).", "cause": "Error-handler bodies are rendered by collectErrorHandlerStatements, a second describer that returned bare strings and never wrote the source map, so those nodes had no line to rank or print a handle at; unranked candidates were appended last. The output-variable switch was copied from actionOutputVariableName, which had drifted from the formatter.", "file": "mdl/executor/cmd_microflows_show_helpers.go; mdl/backend/mfmutator/target.go", "fix": "collectErrorHandlerStatementSpans reports each handler-body object's statement span; emitActivityStatement and emitCommentedErrorHandler record them in the source map (additive entries in ELK sourceMap too). mfmutator.OutputVariable reads the variable where the formatter does, for every action it prints as `$X = \u2026`. A comment rendering (`-- Unsupported \u2026`) is no statement, so it never becomes a handle.", "insight": "Any node the describer prints through a side path must enter the source map, or everything keyed on print order (ordinals, handles, ELK highlighting) silently disagrees with the text. Check ranking against a Studio Pro flow that has a handler body, not just VAL_Feedback.", "test": "TestDescribeWithHandles_ErrorHandlerBody, TestMicroflowTargets_PedAppEveryFlowRanksAndResolves, TestOutputVariable_EveryActionDescribePrintsAnAssignmentFor, TestCandidate_CommentRenderingIsNoStatement"}
{"area": "mdl/executor", "date": "2026-09-27", "symptom": "describe microflow \u2026 with handles printed `-- handle: commit $Order on error {` for an activity with a custom error handler (TestApp Services.SaveOrder). The handle could not be used: an `alter microflow` target ends at the `{` that opens a fragment, so `insert after commit $Order on error { \u2026 }` parsed the handler brace as the fragment.", "cause": "printedStatement ended an action's statement at a line ending in `;` or `{` and kept the `{`, which belongs to the error-handler block describe opens, not to the statement.", "file": "mdl/executor/cmd_microflows_handles.go", "fix": "printedStatement strips the trailing `{` of an error-handler block opener, so the handle is `commit $Order on error`, which parses as a target and still matches the activity.", "insight": "A handle is only useful if it can be written back as a target in the grammar that consumes it; test a printed handle by parsing it, not only by resolving it.", "test": "TestPrintedStatement_ErrorHandlerBlockOpenerIsNotPartOfTheStatement"}
{"area": "mdl/executor", "date": "2026-09-27", "symptom": "alter microflow (ako/mxcli#736) reported \"Altered microflow\" for statements mx check then rejected: two fragments in one statement declaring the same variable gave CE0111 Duplicate variable name; a fragment reading $X in the same statement as `drop $X` (either order) gave CE0109 Undefined variable. A loop fragment was refused as reading its own iterator, and drop/replace of a Studio Pro loop with more than one body activity was refused by the dangling-reference guard.", "cause": "The scope checks compared each operation with the flow as stored, never with what earlier operations of the same statement had declared, read or removed. The iterator of a fragment's loop is on its LoopSource, not an action output. Loop body flows are stored in the unit's Flows list, not in the loop, so removing the loop left them pointing at removed objects.", "file": "mdl/executor/cmd_alter_flow.go; mdl/backend/mfmutator/splice.go", "fix": "alterFlowContext tracks declaredByOps / readByOps / removedByOps across the statement's operations and checks each later operation against them; checkFragmentScope counts a fragment loop's iterator as its own; Drop and Replace remove graph.bodyFlows of a loop with it.", "insight": "A per-operation check against the stored document is only sound for a one-operation statement; every multi-operation test needs a case where operation 2 depends on operation 1. mx check on a copied TestApp is the cheap oracle: the CE numbers appear the moment a hygiene hole is hit.", "test": "TestAlterMicroflow_PedApp_ScopeSpansTheStatement; TestAlterMicroflow_PedApp_LoopFragmentDeclaresItsIterator; TestSplice_DropOrReplaceALoopTakesItsBodyFlows"}
{"area": "mdl/executor", "date": "2026-09-27", "symptom": "describe of a published REST service with no resources printed `create or modify published rest service M.S ( \u2026 )` followed by a bare `;`, which does not parse (`mismatched input ';' expecting '{'`); found while making describe end every statement with `;` (ako/mxcli#744).", "cause": "The resource block `{ \u2026 }` is mandatory in the grammar (publishedRestService: \u2026 RPAREN LBRACE publishedRestResource* RBRACE), but the describer only printed it when the service had resources and otherwise closed the statement with `;` alone.", "file": "mdl/executor/cmd_published_rest.go", "fix": "Always print the block: `{\\n};` when there are no resources.", "insight": "A describe test that asserts substrings never parses the output; feeding every Describe*_Mock output through the parser (assertTerminated, #744) found this at once. The empty-collection branch of a describer is the one no fixture exercises.", "test": "TestDescribePublishedRestService_Mock (assertTerminated)"}
2 changes: 1 addition & 1 deletion .claude/skills/mendix/cheatsheet-errors/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ declare $Products list of Module.Product = empty;

```mdl
-- object: a microflow parameter, a retrieve, a create, or a loop iterator
retrieve $Product from Module.Product where Code = $Code limit 1; -- retrieve
retrieve $Product from Module.Product where Code = $Code first; -- retrieve
$Product = create Module.Product (Name = $Name); -- create
-- or: create microflow M.Save ($Product: Module.Product) ... / loop $Product in $Products ...

Expand Down
6 changes: 3 additions & 3 deletions .claude/skills/mendix/cheatsheet-variables/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Quick reference for variable declarations in MDL microflows.
## Key Rules

1. **Primitives** (String/Integer/Long/Decimal/Boolean/DateTime/Enumeration): Use `declare $var type = value;` — these are the *only* types `declare` (a Create Variable activity) accepts.
2. **Objects (entities)**: Never `declare` an object. Mendix forbids the Create Variable activity from holding an object (CE0053/CE0038, plus CE7247 on a later `set`; flagged as **MDL043** by `mxcli check`) — bare *or* initialized. Get the object from a microflow parameter, a `retrieve … limit 1`, `$var = create Module.Entity(...)`, or a loop iterator. There is no "empty object variable" and no aliasing activity — reuse the variable you already have.
2. **Objects (entities)**: Never `declare` an object. Mendix forbids the Create Variable activity from holding an object (CE0053/CE0038, plus CE7247 on a later `set`; flagged as **MDL043** by `mxcli check`) — bare *or* initialized. Get the object from a microflow parameter, a `retrieve … first`, `$var = create Module.Entity(...)`, or a loop iterator. There is no "empty object variable" and no aliasing activity — reuse the variable you already have.
3. **Lists**: Never `declare` a list — same Create Variable restriction (CE0053/CE0038, flagged as **MDL040**). Get the list from a microflow parameter, a `retrieve`, or `$var = create list of Module.Entity;`
4. **SET requires DECLARE**: Always declare primitive variables before using SET.
5. **Parameters are pre-declared**: Microflow parameters don't need DECLARE (and a parameter *may* be an object/list type — that restriction is only on `declare`).
Expand All @@ -40,7 +40,7 @@ declare $Product as Module.Product; -- the AS keyword is also a parse er

-- CORRECT: get the object from a source that produces one
$Product = create Module.Product (Name = $n); -- create
retrieve $Product from Module.Product where Code = $c limit 1; -- retrieve
retrieve $Product from Module.Product where Code = $c first; -- retrieve
-- or accept it as a parameter, or use a loop iterator: loop $Product in $Products ...
```

Expand Down Expand Up @@ -93,7 +93,7 @@ begin

declare $Result boolean = true; -- Local primitive: must declare
-- Need a local object? Don't declare it — create/retrieve it:
$Order = create Module.Order (Reference = $Input); -- or retrieve … limit 1
$Order = create Module.Order (Reference = $Input); -- or retrieve … first

return $Result;
end;
Expand Down
4 changes: 2 additions & 2 deletions .claude/skills/mendix/check-syntax/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ Before writing any MDL, verify these requirements:

**Supported in Microflows:**
- `declare $Var type = value;` (primitives only: String/Integer/Long/Decimal/Boolean/DateTime/Enumeration)
- `$entity = create Module.Entity (...);` / `retrieve $entity from ... limit 1;` (objects — **never** `declare` an object; that fails CE0053/CE0038 and is flagged MDL043)
- `$entity = create Module.Entity (...);` / `retrieve $entity from ... first;` (objects — **never** `declare` an object; that fails CE0053/CE0038 and is flagged MDL043)
- `$list = create list of Module.Entity;` (lists — **never** `declare` a list; that fails CE0053/CE0038 and is flagged MDL040)
- `set $Var = expression;`
- `$Var = create Module.Entity (attr = value);`
Expand All @@ -260,7 +260,7 @@ Before writing any MDL, verify these requirements:

**Now Supported (previously not):**
- `rollback $entity [refresh];` - Reverts uncommitted changes
- `retrieve ... limit n` - Returns single entity when `limit 1`
- `retrieve ... first` - Returns a single entity; `limit n [offset n]` returns a list (a bare `limit 1` is the object only without the `mdl 1;` header, and warns MDL-V1-LIMIT1)
- `boolean` without `default` - Auto-defaults to `false`
- `buttonstyle: warning` and `buttonstyle: info` - Now parse correctly
- Keywords as attribute names - `caption`, `label`, `title`, `text`, `content`, `format`, `range`, `source`, `check`, etc. all work unquoted
Expand Down
7 changes: 4 additions & 3 deletions .claude/skills/mendix/test-microflows/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -405,9 +405,10 @@ with nothing but "the project cannot be deployed". An `@expect` or `@verify` tha
cannot be evaluated is reported here too, as `MDL-TEST01`.

One rule to know about, because its symptom is confusing and its shape is common
in tests: `retrieve $x … limit 1` binds a **single object**, not a one-element
list, so `head($x)` is `CE0097` at build time and `MDL-RETRIEVE01` at check time.
Drop the `limit` to get a list, or use the variable as the object it is.
in tests: `retrieve $x … first` — and `retrieve $x … limit 1` in a script without
the `mdl 1;` header — binds a **single object**, not a one-element list, so
`head($x)` is `CE0097` at build time and `MDL-RETRIEVE01` at check time. Drop the
range to get a list, or use the variable as the object it is.

---

Expand Down
16 changes: 12 additions & 4 deletions .claude/skills/mendix/write-microflows/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ declare $status Enumeration(Module.OrderStatus) = Module.OrderStatus.Open;
> not you give it an initializer. `mxcli check` now flags it as **MDL043**. There
> is **no** "empty object variable" activity. Get objects from one of these:
> - a microflow **parameter**: `create microflow M.Save ($Product: Test.Product) ...`
> - a **retrieve**: `retrieve $Product from Test.Product where Code = $c limit 1;`
> - a **retrieve**: `retrieve $Product from Test.Product where Code = $c first;`
> - a **create object**: `$Product = create Test.Product (Name = $n);`
> - a **loop iterator**: `loop $Product in $Products ...`

Expand All @@ -283,6 +283,14 @@ declare $status Enumeration(Module.OrderStatus) = Module.OrderStatus.Open;
> The `calendar*Between` functions (`calendarMonthsBetween`, `calendarYearsBetween`)
> return whole units (Integer) and are fine to assign directly.

### Changing a variable: always `set`

`set $Counter = $Counter + 1;` changes a variable. `$x = …` without `set` is an
activity that creates `$x` — required under `mdl 1;` (`MDL-V1-SET` otherwise).
List operations and aggregates are one statement per activity and never nest:
`$Open = filter $Orders by Status = M.Status.Open;` then `$N = count $Open;` —
see [`reference/data-operations.md`](reference/data-operations.md#one-statement-per-activity).

### ❌ INCORRECT Syntax

```mdl
Expand Down Expand Up @@ -579,7 +587,7 @@ Before executing a microflow script, verify:
```mdl
declare $primitive type = value; -- Primitives (String/Integer/Decimal/Boolean/DateTime)
declare $status Enumeration(Module.Enum) = …; -- Enumerations are primitives too
-- Objects: never declare. Use a parameter, retrieve (limit 1), `$obj = create Module.Entity(...)`, or a loop iterator.
-- Objects: never declare. Use a parameter, retrieve (… first), `$obj = create Module.Entity(...)`, or a loop iterator.
-- Lists: never declare. Use a parameter, retrieve, or `$list = create list of Module.Entity;`
```

Expand Down Expand Up @@ -676,8 +684,8 @@ close page on error { return; };
of them; `continue` is fine on `declare`, `set`, `retrieve`, `delete` and
`call microflow`. Measured on 11.14.0 — note that create-*variable* and
change-*variable* accept `continue` while change-*object* does not.
- **The list-operation and aggregate forms of `set`** (`$x = head($l)`,
`$n = count($l)`) have no error handling in Mendix at all — **MDL077**.
- **List operations and aggregates** (`$x = head $l;`, `$n = count $l;`)
have no error handling in Mendix at all — **MDL077**.

**In a nanoflow, almost none of them take a clause at all.** `change`, `log`,
`show page`, `close page`, `show message` and `validation feedback` are CE6035
Expand Down
Loading
Loading