Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
da33a98
fix(security): keep Studio Pro's list marker on UserRoles and DemoUsers
ako Sep 27, 2026
bcdf34f
fix(javascript action): a rewrite carries what describe cannot print
ako Sep 27, 2026
24053f2
fix(executor): a doc comment in normal form keeps the stored document…
ako Sep 27, 2026
ebcebc6
feat(describe): emit create or modify for every document type (#731)
ako Sep 27, 2026
c8aef80
feat(mdl 1): create or replace means create or modify on every type (…
ako Sep 27, 2026
59d6970
feat(security): create module role is a createStatement kind; drop mo…
ako Sep 27, 2026
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
66fabe3
findings: #731 security list markers and JavaScript action rewrite ca…
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
a9d617b
fix(javascript action): unchanged describe output leaves the .js sour…
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
1fc3d96
mdl/upgrade: rewrite deprecated spellings, with a hook for header-gat…
ako Sep 27, 2026
0ddd418
mxcli fmt --upgrade [--header]
ako Sep 27, 2026
6a72dab
roundtrip: execute-both property test for fmt --upgrade
ako Sep 27, 2026
f35637a
roundtrip: execute only scripts the upgrade rewrites, by default
ako Sep 27, 2026
b71aaff
describe: keep plain create for workflow, external entity, OData clie…
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
c1fc8de
Merge branch 'c3-741' into c3-738
ako Sep 27, 2026
bdfcdfb
test: statements in the or-replace/or-modify parity test end with ';'…
ako Sep 27, 2026
6c39e99
Merge remote-tracking branch 'origin/feature/731-create-or-modify' in…
ako Sep 27, 2026
e207392
mdl/upgrade: rewrite the mdl 1 header-gated constructs and list call …
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
1 change: 1 addition & 0 deletions .claude/skills/fix-issue/findings/mdl-backend.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,4 @@
{"area": "mdl/backend", "date": "2026-09-26", "symptom": "`ALTER PAGE … SET ImageUrl = '…' ON img1` (pluggable Image) prints \"Altered page\" and changes nothing: DESCRIBE still shows the old URL. Same silent success for SET on any pluggable widget property of kind Expression, Image, Icon, Action, DataSource, Attribute or Widgets", "cause": "setPluggableWidgetPropertyMut (mdl/backend/pagemutator/mutator.go) wrote every value to Value.PrimitiveValue. imageUrl is a TextTemplate; readers take Value.TextTemplate. The DataGrid 2 column setter had the identical defect fixed on 2026-08-18 (columnValueField) — the widget-level setter sitting next to it was never given the same schema dispatch.", "file": "mdl/backend/pagemutator/mutator.go (setPluggableWidgetPropertyMut, buildPropKindMap), test pluggable_property_kind_test.go, example mdl-examples/bug-tests/1201-alter-page-set-pluggable-texttemplate.mdl", "insight": "When a fix lands on one setter for 'write the field the schema declares', grep the sibling setters in the same file for the same always-PrimitiveValue write — a WidgetValue carries every variant field, so the wrong write never errors and every signal stays green. The live control is cheap without mxbuild: copy testdata/expr-checker, create an image with URL A, ALTER to B, describe (pre-fix binary shows A). A null TextTemplate means the slot is hidden (#574); ALTER does not re-run visibility, so refuse rather than build an envelope there. Not fixed here: a pluggable boolean SET stores \"yes\"/\"no\" — check what CREATE stores before changing it.", "refs": ["mendixlabs/mxcli#1201", "mendixlabs/mxcli#1069", "mendixlabs/mxcli#750", "mendixlabs/mxcli#574"], "rules": []}
{"area": "mdl/backend", "date": "2026-09-26", "symptom": "describe → exec of a snippet turns its Type 'Web' into '' (and would reset a non-default ExportLevel or canvas size)", "cause": "snippetToGen wrote Type, ExportLevel, CanvasWidth and CanvasHeight as constants on every write; the Type constant was \"\"", "file": "mdl/backend/modelsdk/snippet_write.go", "insight": "The snippet twin of #541 (carryStoredPageHeader). \"\" is not a member of PagesType (Native | Web), so every snippet mxcli created was also invalid. Carry the stored header on UpdateSnippet (width-agnostic canvas read — Studio Pro stores int64); a new snippet gets Web/Hidden/800×600, measured on all four Blank-template snippets", "refs": ["ako/mxcli#705", "#541"]}
{"area": "mdl/backend", "date": "2026-09-26", "symptom": "describe → exec of a nanoflow detaches every annotation from its activity (AnnotationFlows 3 → 0, so the next describe prints the notes as free-floating) and deletes ExportLevel, UseListParameterByReference and ReturnVariableName; MarkAsUsed is cleared. Rules drop their annotation flows too", "cause": "nanoflowToGen and ruleToGen never wrote ObjectCollection.AnnotationFlows (microflowToGen did), and nanoflowToGen never set the three header keys; nanoflowFromGen did not read ReturnVariableName or MarkAsUsed and the builder hardcoded MarkAsUsed false", "file": "mdl/backend/modelsdk/nanoflow_write.go, rule_write.go, microflow.go; mdl/executor/cmd_microflows_build.go", "insight": "Three flow flavours share the object/flow converters but each has its own top-level writer, and each dropped a different subset — grep every *ToGen for the same list loop, not just the reported one. For keys with no MDL spelling and unknown version floors (Nanoflow.ExportLevel, UseListParameterByReference), carry ONLY the keys the stored unit has: nothing was ever written on create, Studio Pro fills an absent key, and an undeclared one makes the unit unopenable", "refs": ["ako/mxcli#705"]}
{"area": "mdl/backend", "date": "2026-09-27", "symptom": "Running unchanged `describe user role` output (create or modify user role) rewrites Security$ProjectSecurity: /UserRoles[0] marker 2 -> 3; same for DemoUsers on a demo-user rewrite", "cause": "The encoder's default PartList marker is 3; Studio Pro writes 2 for ProjectSecurity.UserRoles and .DemoUsers (PedApp, TestApp, expr-checker agree). An untouched list passes through raw, so it only shows once an element in it is rewritten", "file": "mdl/backend/modelsdk/security_write.go", "insight": "A marker bug hides until the first rewrite of an element in that list; census the stored marker per (owner $Type, key) across Studio Pro fixtures and register it with RegisterPropertyListMarker", "refs": ["ako/mxcli#731"]}
5 changes: 5 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,8 @@
{"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": "Once describe emitted create or modify javascript action, re-running it wrote a .js file holding the describe placeholder body, dropped parameter descriptions, and rewrote Studio Pro doc comments (CRLF, blank lines) \u2014 getput and putget failures on 45 PedApp actions", "cause": "The JS write path lacked the Java twin's carries: placeholder guard (javaSourceOmittedBody) and stored-parameter Description/Category; and carriedDocumentation overwrote stored prose with its doc-comment normal form", "file": "mdl/executor/cmd_javascript_actions_write.go, documentation_carry.go", "insight": "Turning a plain create into create or modify is what makes describe output execute at all \u2014 expect the next layer of losses to surface immediately; diff the twin (Java vs JavaScript) write paths for every carry one has and the other lacks", "refs": ["ako/mxcli#731"]}
{"area": "mdl/executor", "date": "2026-09-27", "symptom": "Running unchanged `describe javascript action` output (now `create or modify`) on a project with the .js sources regenerated the file: the import list (`import { Big } from \"big.js\"`) and the EXTRA CODE helpers (deg2rad) were dropped and the signature re-spelled, so NanoflowCommons.GetStraightLineDistance's user code called functions that no longer existed", "cause": "execCreateJavaScriptAction always called WriteJavaScriptSourceFile, and GenerateJavaScriptSource has no slot for the imports or EXTRA CODE (the Java twin retains both, #705). A plain `create` refused on an existing action, which is what had hidden it", "file": "mdl/executor/cmd_javascript_actions_write.go (jsSourceUnchanged)", "insight": "The PedApp round trip could not see this: PedApp ships no javascriptsource, so describe printed the placeholder body and the file path was never exercised. Snapshot the source tree as well as mprcontents when round-tripping a doctype that owns files. Leaving the file alone when the statement restates the stored user code and parameter names fixes describe->exec; an edited body still regenerates without the retained sections", "refs": ["ako/mxcli#731"], "test": "TestJavaScriptActionRewrite_UnchangedCodeKeepsSourceFile"}
{"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
Loading
Loading