Skip to content

Sync ako/mxcli: include associations on external action calls, describe round-trip and page fixes, case-sensitive image check - #1756

Merged
ako merged 21 commits into
mendixlabs:mainfrom
ako:main
Nov 6, 2026
Merged

ako merged 21 commits into
mendixlabs:mainfrom
ako:main

Conversation

@ako

@ako ako commented Nov 5, 2026

Copy link
Copy Markdown
Collaborator

Syncs ako/mxcli:main into upstream: 13 commits since #1747 — 7 upstream issues fixed, plus OData, image-reference and ALTER PAGE fixes.

Microflows

  • New: include associations on a call external action argument — the associations whose objects an OData action call serialises with the argument, which had no MDL (a linked sub-object was silently missing at runtime):
    command = $Cmd include associations (Odata.Quantity (Odata.Unit))
    
    The clause is the argument's whole tree; IsParent is derived from the domain model, and self-associations, associations that don't touch the entity, and ambiguous specializations are refused by exec and check -p. include associations () clears it; describe prints it. Left out on create or modify, the stored tree is carried and MDL-ODATA10 warns — including on the in-place splice path, which used to drop it silently. A non-empty clause needs 10.11. Authored trees encode to Studio Pro 11.16's stored bytes, $IDs aside. Fixes CALL EXTERNAL ACTION — no MDL syntax for IncludeAssociations flag #1721
  • A decision with an empty true branch is described as stored (if X then else … end if;), not folded into if not(X) then …, which on re-exec stored not(X) with the outcomes swapped — same behaviour, different model. Scripts written from the old output keep their meaning. Fixes execute round trip negates a decision and swaps its outcomes #1742
  • An import mapping rooted at a JSON array returns one object when it maps the array itself to its root entity; it was written as returning a list (CE0243). Fixes regenerating JSON import introduces model error #1734

Pages

OData

  • Annotation terms written with a vocabulary alias are read. A $metadata that writes Capabilities.InsertRestrictions via <edmx:Include … Alias="Capabilities"/> was matched by full namespace only, so the entity was imported as not creatable/updatable/deletable (CE6630). Measured on 11.14.0: 6 errors before, 0 after.

check: image references are case-sensitive

check --references passed Image: 'Atlas_Core.Content.mendix' for the stored image Mendix, and its hint printed the lower-cased name, so following it produced CE1613. Measured on 11.13.0: a case difference in the image, collection or module is CE1613 each. The match is now exact and a case-only miss names the stored spelling; the app icon is covered too. The two tests that asserted case-folding are inverted, not deleted

claude and others added 21 commits November 4, 2026 06:22
`mxcli check --references` passed `Image: 'Atlas_Core.Content.mendix'`
for the stored image `Mendix`, and its miss hint printed the name as
`mendix` — so following the hint produced CE1613 "The selected image …
no longer exists". The check keyed images and collections lower-cased,
on the assumption, pinned by two tests, that Mendix folds case.

Measured on a blank 11.13.0 app: a case difference in the image, the
collection or the module part is CE1613 each, and the exact spelling is
clean. The match is now exact; a miss that differs only in case names
the stored spelling, and the hint lists images as stored. Collections a
script creates keep their declared spelling the same way. The two tests
that asserted case-folding are inverted, not deleted.

Control: on the unfixed build the four-page script passes check and mx
reports three CE1613; on the fixed build check reports exactly those
three, naming Atlas_Core.Content.Mendix / Atlas_Core.Content, and the
exact reference passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019httGxDh8UXt1Zoyh3Gh52
The app icon is resolved by the same image check, and its test accepted
`myfirstmodule.images.logo` on the same unmeasured case-folding
assumption. Measured on 11.13.0: main writes `app icon
Atlas_Core.Content.mendix` for the stored `Mendix`, and mx reports
CE1613 at the navigation profile. The finding records it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019httGxDh8UXt1Zoyh3Gh52
…ment (#1914)

Studio Pro's Associations to include — the associations whose objects an
OData external action call serialises with an argument — had no MDL, so a
sub-object linked to the command was silently missing at runtime.

  command = $Cmd include associations (Odata.Quantity (Odata.Unit))

The clause is the argument's whole tree. IsParent is derived from the
domain model (the entity on each node's side is the association's FROM
end); a self-association, an association that does not touch that
entity, and one whose side specializes both ends are refused by exec and
by check -p. include associations () clears the tree. describe prints
the clause, or a comment for a tree holding a self-association. Left out
on create or modify, the stored tree is carried (#1922) and check -p and
exec warn MDL-ODATA10. A non-empty clause needs 10.11.

Measured on a fresh 11.12.6 app with the reporter's service shape: the
authored call builds at 0 errors and describes back; the authored tree
encodes to Studio Pro 11.16's stored bytes, $IDs aside.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82tQYYsMeM1r2ihkNch
…lude (#1914)

A create or modify of a stored flow is applied as an in-place splice
first, and the splice's replace rebuilt the call external action through
the alter-flow fragment builder, which never reached the #1922 carry: the
stored Associations to include were dropped, with no warning, in the
common case. The replace now carries the stored tree onto a call that
does not state the clause, and exec warns MDL-ODATA10, as the rebuild
does. Proved against PedApp with a local $metadata; without the change
the test fails with the tree dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82tQYYsMeM1r2ihkNch
feat(microflows): include associations on a call external action argument (#1914)
…get (#1968)

snippetcall … Params: (Item = $dvOuter) passes a data view's object by
the data view's name. The builder classified it as a widget, as it does
for a flow call's argument (#1560), but the snippet call kept only the
snippet-parameter and page-variable kinds and wrote everything else in
PageParameter: a page parameter the page does not have, CE0115. It is now
written in the Widget slot alone, and describe reads that slot back.

Measured on a fresh 11.14.0 app with the reporter's repro: the base build
gives 2 x CE0115 (scA, scC), this build 0 errors; describe -> exec of the
page is Unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82tQYYsMeM1r2ihkNch
)

A pluggable widget's textTemplate property bound to an optional data
source (widget.xml dataSource="X") was written as an empty client
template while X was unset. Studio Pro stores null there, and mx check
reported CE0463 "The definition of this widget has changed". The builder
now nulls such a template when it is unset; with X set it stays empty,
and authored text is never touched.

Measured on a fresh 11.14.0 app with a probe widget declaring the
reporter's property pairs: the base build gives 3 x CE0463, one per page,
this build 0, the control included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82tQYYsMeM1r2ihkNch
fix(pages): two silent build-breakers — snippet call widget argument (#1968), unset-data-source text template (#1967)
…1963)

describe folded a decision whose true branch goes straight on into
if not(X) then <false branch> end if; — and executing that description
stored not(X) with the true and false outcomes swapped: the same
behaviour, a different model. It is now printed as stored,
if X then else <false branch> end if;, which builds Studio Pro's shape.
The condition-edit splice no longer undoes a fold (#1930), and the
helpers that made and undid it are gone.

A script written from the old output keeps its meaning (the splice diffs
the branches independently); an alter target in the folded form no
longer resolves and is refused. On PedApp, the reporter's describe ->
exec round trip now stores the same expression and outcomes; with the
fold restored the test reproduces the reported table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82tQYYsMeM1r2ihkNch
…1965)

alter page … set (DateFormat: Custom, CustomDateFormat: …) on a date
picker whose stored format is not Custom was refused by check, and so by
exec's pre-check, in either order and as two SETs: check dry-runs one
property at a time on the stored widget, and the DateFormat setter
refused Custom on the stored blank pattern. The setter no longer judges
it; check now applies the statement's earlier SETs on the widget, then
the whole SET, and asks the same CE0493 question exec asks after it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82tQYYsMeM1r2ihkNch
set (CaptionParams: ({1} = Attr)) on a button and set (ContentParams:
…) on a dynamic text were parse errors, so a text template's parameters
could be written by create page and replace but not changed in place.
They now parse, are resolved by the create page builder against the
entity of the data container the widget sits in, and replace the
template's parameters; describe prints them back.

Measured on a fresh 11.14.0 app with the reporter's pages: check and
exec pass, mx check reports 0 errors, and the parameter written equals
the one create page writes, $IDs aside.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82tQYYsMeM1r2ihkNch
fix: describe/alter round trips — empty-then decision (#1963), Custom date format check (#1965), alter set template params (#1966)
… object (#1938)

An import mapping over an array-rooted JSON structure that maps the
array itself to its root entity, with the items a child, returns one
root object. The cardinality check fell back to the structure's root
kind whenever the mapping's root path did not cross an array, so every
array-rooted structure read as list-returning, and the import activity
was written as returning a list: mx check CE0243. A stored root path
now decides alone; the structure decides only when there is none.

Measured on a fresh 11.14.0 app with the reporter's mapping: the base
build gives 1 x CE0243 on the first create; this build 0, on create and
after re-executing the microflow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82tQYYsMeM1r2ihkNch
fix(microflows): an import mapping rooted at a JSON array returns one object (#1938)
)

A $metadata may alias a vocabulary (<edmx:Include
Namespace="Org.OData.Capabilities.V1" Alias="Capabilities"/>) and write
its terms with the alias. Every term was matched by its full namespace
only, so aliased Insert/Update/DeleteRestrictions and the Core terms were
skipped: create external entities imported the entity as not creatable
or deletable, and mx check reported CE6630. The parser now rewrites each
aliased term to its full namespace right after reading the document; an
undeclared prefix is left as written.

Measured on a fresh 11.14.0 app with an alias-annotated contract: the
base build gives 6 errors (4 x CE6630, CE0852, CE9435), this build 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82tQYYsMeM1r2ihkNch
fix(odata): read annotation terms written with a vocabulary alias (#1977)
fix(check): resolve an image reference case-sensitively, as Mendix does
set (Attribute: …) on <grid> column(…) was refused for every spelling,
and Assoc/Attr did not parse: replacing the column, which drops what the
replacement does not restate, was the only route. The attribute is now
bound as CREATE binds a column's, against the grid's data source entity:
a bare name qualified by it, Module.Entity.Attr kept (refused when not
the grid's entity or a generalization), Assoc/…/Attr with the hops as
the AttributeRef's EntityRef, and $Param.Attr refused. The mutator only
writes the binding.

The attribute is applied after the rest of the SET and the reset of what
the widget hides, which re-finds the column by its address: changing the
attribute moved it, and the reset failed with "column(Old) not found".

Measured on a fresh 11.14.0 app: all three spellings apply, describe
prints them back, mx check reports 0 errors, describe -> exec is
Unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82tQYYsMeM1r2ihkNch
feat(pages): alter page set a Data grid 2 column's attribute (#1644)
…tcome drops (#808)

- `drop X boundary event N` picks one event among several, numbered as
  `describe workflow` lists them; out of range is refused at exec with
  nothing written. Grammar, AST, visitor, both mutators and the mock.
- `drop X outcome 'V'` / a branch value matching two outcomes is refused
  as ambiguous in wfmutator and the MCP mutator instead of dropping the
  first.
- resolveStoredActivity prefers a Name match over caption matches without
  @n, as the mutator's preferNameMatches does, so check-time guards no
  longer abstain where exec resolves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82tQYYsMeM1r2ihkNch
fix(workflow): address boundary events by number, refuse ambiguous outcome drops (#808)
@ako
ako merged commit d82c738 into mendixlabs:main Nov 6, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment