Sync ako/mxcli: page round-trip fixes, label widget, OData expressions, layout flows - #1204
Conversation
…ixlabs#750) Extend PROPOSAL_first_class_expressions.md with a grounded plan: slot inventory with the grammar rule and describer for each, four slices with file tables, a test plan (T1-T8), BSON/version section. Corrections from reading the grammar: - the "expression must not consume , or )" cost is already solved twice in widgetPropertyV3; the real cost is value-form ambiguity - calculated attributes and REST/OData mappings are not expression slots; pluggable expression props and workflow due date are - `dynamicclasses: [ ... ]` (the syntax mendixlabs#750 proposes) already passes check and is dropped as a []string; split out as slice 0 bug fix Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P65SqmwwvbWdJVRwhYiMQw
extractAliasMap matched `join a/Mod.Assoc/Mod.Entity AS x` case-insensitively but then recovered the path by trimming a literal lowercase "as" off the match. With `AS` (the spelling DESCRIBE prints) the path kept a trailing " AS", the end-anchored entity regex failed, and the alias was never mapped — so every column from it skipped type inference and a wrong pass-through string length passed `check --references`, failing the build with CE6770. The final path segment is now captured as its own regex group. Fixes #652 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0154S1aRziq1CyrFepxqb5gg
fix: resolve association-path join alias written with uppercase AS
The flow counterpart of `mxcli layout`, and the alternative to a RESET LAYOUT clause on CREATE MICROFLOW (mendixlabs#837): layout is a separate, opt-in operation on a stored flow, as it is for domain models. It reuses the one layout engine flows have. The stored flow is described to MDL, stripped of every layout annotation, and rebuilt exactly as CREATE builds it (dry-run, with a new ResetLayout build option so a hand-placed StartEvent is not carried over). Only the geometry of that build is kept: it is paired back onto the stored objects by walking both graphs from the start event, and patched into the stored BSON. Positions, sizes, connection indexes and bezier vectors change; $IDs, GUIDs and every property MDL cannot express do not. The write goes through UpdateRawUnit, so canon.Reconcile elides a no-op second run. The pairing doubles as the safety check: a flow whose description does not rebuild into the same graph is skipped with the reason instead of laid out by guesswork. A pass-through merge (one flow in, one out), which DESCRIBE omits, is placed on the rebuilt edge it sits on. Measured on the Studio Pro-drawn flows in testdata/expr-checker: 23 of 27 laid out, 4 skipped (branches sharing merges), second run writes nothing, mx check 11.6.6 reports 0 errors before and after. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qfvyasDvxj5Zqrn4bxczi
User manual page for the flow layout command, linked from the microflow @position section, and a pointer in the write-microflows skill so an agent re-arranges a flow with the command instead of rewriting it without its @position lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012qfvyasDvxj5Zqrn4bxczi
…ions The previous revision said REST/OData had no expression slots. The consumed OData client has four (HttpUsername, HttpPassword, ClientCertificate, header values), and 10-odata-examples.mdl writes every literal as '''admin'''. - add the slots to the inventory and slice 3, with a worked before/after of the 10-odata-examples.mdl FullConfigAPI block - slice 0b: formatExprValue returns an already-quoted stored value unchanged, so describe -> exec turns 'admin' into admin (measured against the real function); ClientCertificate and header keys are printed unescaped - ProxyHost/Port/Username/Password are ByNameRef constants, not expressions; out of scope - open question 5 measured: the shipped odata-data-sharing skill writes HttpUsername: 'MxAdmin', which stores the identifier MxAdmin today; the example writes the triple-quoted form. Each option breaks one spelling; recommend flipping the meaning plus a check that detects the legacy form Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P65SqmwwvbWdJVRwhYiMQw
@Mod.C is expression syntax, but MDL also uses it for slots that are ByNameRefs to a constant. Once expression slots take @Mod.C bare, the same text cannot also mean a reference. Spell references by target kind, as `microflow Mod.F` already does: `ProxyHost: constant Mod.C`. Adds 6.5 with the inventory of every @-spelled slot classified by its metamodel type: OData ServiceUrl is an expression (stays @); OData proxy, database connection and REST client credentials are references. The OData proxy path stores "@Mod.C" verbatim into the ByNameRef; the REST and database paths strip it. Records that the CE0117 claim in 10-odata-examples.mdl for the unprefixed form needs a Studio Pro BSON reference before the proxy half is built. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P65SqmwwvbWdJVRwhYiMQw
…ference Decoded Odata.Bug1073 from ako/TestApp@37e0cc0 (custom proxy configured in Studio Pro) and compared it with clients mxcli writes from the describe output: - by-name proxy slots store the bare qualified name with ProxyType Override; the unprefixed MDL form already matches byte-for-byte, so the CE0117 comment in 10-odata-examples.mdl is stale - `ProxyHost: @X` stores "@x" verbatim: the live bug `constant X` fixes - describe -> exec turns HttpAuthenticationUserName 'abc' into abc (slice 0b reproduced on real data) and blanks CustomLocation when OverrideLocation is false - the constant's type is user-chosen (String/Integer/Long), so check validates only that it is a constant Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P65SqmwwvbWdJVRwhYiMQw
ako/TestApp@11a8fca changes the proxy port constant from String to Integer with no Studio Pro errors (Long reported clean too); the client still stores the bare constant name. `constant X` stays type-agnostic. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P65SqmwwvbWdJVRwhYiMQw
A client configured in Studio Pro with a literal user name stores the expression `'abc'` (quotes included). DESCRIBE printed `HttpUsername: 'abc'`, and executing that output stored `abc` - an identifier, not a string. Measured on a Studio Pro-authored client (ako/TestApp@37e0cc0, Odata.Bug1073) by decoding the mxunit before and after a describe -> exec round trip. formatExprValue passed any value that already started and ended with a quote through unchanged. The visitor unquotes the MDL string, so the text always needs one more level of quoting than the stored expression. It now always uses mdlQuote, the inverse of unquoteString. ClientCertificate, header keys, Version, MetadataUrl and Folder were printed as a raw '%s' and did not re-parse when they held a quote; they are escaped too. Tests parse the describe output with the real visitor and compare what a re-exec would store against the stored value. Before the fix: HttpUsername: stored "'abc'", re-exec of describe output stores "abc" with the `@Module.Const` password (which already round-tripped) passing as the control; the escaping test failed to parse at all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P65SqmwwvbWdJVRwhYiMQw
ProxyHost / ProxyPort / ProxyUsername / ProxyPassword are BY_NAME references to a constant. Studio Pro stores the bare qualified name with ProxyType Override (ako/TestApp@37e0cc0, Odata.Bug1073). MDL writes a constant as `@Module.Const`, and the create, create-or-modify and alter paths copied that text into the reference unchanged, so "@Module.Const" named no constant and the proxy resolved to nothing. extractConstantRef strips the `@` at every assignment site, as extractMicroflowRef already strips `microflow ` (#573). The bare, `@` and quoted-`@` spellings now all store the bare name. Tests parse real MDL and capture the value handed to the backend, on create and alter. Before the fix: ProxyHost written as @MyModule.ProxyHost: stored "@MyModule.ProxyHost" with the bare spelling passing as the control. End to end on a copy of ako/TestApp, `ProxyHost: @Odata.Bug1073_ProxyHost` now stores "Odata.Bug1073_ProxyHost", byte-identical to Studio Pro. Also corrects the comment in 10-odata-examples.mdl, which said the bare form fails with CE0117; the Studio Pro reference shows it is the form Mendix stores. The constant's type is not validated: Studio Pro accepts a String or an Integer port (ako/TestApp@11a8fca). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P65SqmwwvbWdJVRwhYiMQw
…tent HttpUsername / HttpPassword / header values are Mendix expressions. Studio Pro stores a literal credential as the string literal `'MxAdmin'`, and MDL spells that `HttpUsername: '''MxAdmin'''`. The design-time $metadata fetch sent the stored text verbatim, quotes included: a 401 and an empty client for the spelling that is correct at runtime. resolveCredential now evaluates a value that is exactly one Mendix string literal to its content (a doubled quote is one quote). Any other expression starting with a quote (`'Key ' + @M.C`) cannot be evaluated at design time and is reported unresolved instead of being sent as text. The existing spellings (bare literal, `@M.C`, `'@M.C'`, dotted literals) are unchanged and remain the controls. Before the fix: got "'MxAdmin'", want "MxAdmin" fetch sends "'MxAdmin'" / "'1'", want MxAdmin / 1 Reproduced again as the control by disabling the new branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P65SqmwwvbWdJVRwhYiMQw
…erals The four consumer clients wrote `HttpUsername: 'MxAdmin'` and `HttpPassword: '1'`. These properties hold a Mendix expression, so that stored the identifier `MxAdmin` and the integer `1` - not credentials. Studio Pro stores a literal credential as the string literal `'MxAdmin'`, which MDL spells `'''MxAdmin'''`. Verified by executing the walkthrough's client into a copy of ako/TestApp and decoding the unit: HttpAuthenticationUserName is now "'MxAdmin'" and HttpAuthenticationPassword "'1'". The design-time $metadata fetch sends MxAdmin / 1 for this spelling since the previous commit. A comment on the first client says why the quotes are doubled and that a constant (`@Module.Const`) needs none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P65SqmwwvbWdJVRwhYiMQw
feat: mxcli layout flows re-arranges microflows and nanoflows
…dget init (#663) On a fresh clone (.mxcli/ is gitignored) DESCRIBE WIDGET called an installed widget unknown and `widget list -p` showed 9 definitions, because both read only .mxcli/widgets/*.def.json. The page builder and LoadWidgetRegistry already generate those from the installed .mpk first (mendixlabs#1135). Factor that into LoadProjectWidgetDefinitions and use it from all three. The not-found error now only suggests forms that work: the quoted widget id (the unquoted one is a parse error), deduped keywords, and -p when no project is open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DESCRIBE PAGE on Administration.Account_New emitted `Attribute: UserRoles`, and exec wrote it back as `Administration.UserRoles`: [CE1613] "The selected association 'Administration.UserRoles' no longer exists." Administration.Account extends System.User, which declares the association. A bare association name was qualified with an entity's MODULE rather than looked up. Before f0d1aea (issuetracker #19) the combobox used the option list's module, which happened to be right here. That commit switched to the page entity's module, which is wrong for any association inherited from another module. Bisected: f0d1aea^ writes 0 combobox CE1613s on this page, f0d1aea writes 3. - resolveAssociationPathIn now resolves a bare name to the association with that name on the context entity or a generalization (nearest first), and qualifies it with its declaring module. Unknown or ambiguous names keep the previous guess. All four call sites go through it. - resolveAssociationAttributePath resolves each hop from the entity that hop starts at, not from the path's first entity. - DESCRIBE keeps the module qualifier on a combobox or dropdown-filter association declared outside the context entity's module. Round trip of all 17 pages of an 11.13.0 project with Administration: unfixed 5 errors (as reported), fixed 1: the CE0642 split out as #664. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ensureWidgetRegistry loaded only .mxcli/widgets/*.def.json, which a fresh clone lacks (gitignored), so completions offered the nine embedded widgets and the LSP's widget diagnostics read the same thin registry. With a project open it now uses executor.LoadWidgetRegistry — the registry check validates against — which generates the definitions from the installed .mpk first. The no-project path keeps loading global definitions as before. Follow-up to #663. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A `--` comment entry runs to the end of its line. formatWidgetProps treated it as an ordinary property, so it swallowed the rest of the list and the `)` on the single-line form, and as the last entry it left the previous line's `,` dangling before `)`. Either way `exec` of the describe output failed with "extraneous input '}' expecting the start of a statement". Comment entries now force the multi-line form, sit on their own line, and never take or receive a separator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Attr)
DESCRIBE PAGE on FeedbackModule.PopupSuccess (Feedback v4.0.2) emitted
Action: -- open_link with a dynamic address (FeedbackModule.ResponseHelper.URL) — MDL cannot author this; the button is left as-is,
which left `Action:` without a value, so exec of the output failed with
"extraneous input '}'". The note was also wrong: CREATE OR REPLACE PAGE
rebuilds the page, so an omitted Action writes a button with no action. I
measured that: 0 OpenLinkClientAction left, and `mx check` clean.
MDL now spells Studio Pro's "Address: attribute" as
Action: open_link $currentObject/URL
and writes the stored shape, taken from the Studio Pro-authored page:
Forms$StaticOrDynamicString { IsDynamic: true, Value: "",
AttributeRef { Attribute: Module.Entity.Attr, EntityRef: null } }.
The address resolves against the enclosing data container, inherited
attributes included. It is refused outside a data container, for any
variable other than $currentObject, and over an association path. An
association-path address still describes as a standalone NOT re-executable
note, never an inline `Action: -- …`.
Round trip of PopupSuccess and PopupSuccess_Logo: exec succeeds, the link
action BSON is identical before and after, and `mx check` reports 0 errors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
First-class expressions proposal (mendixlabs#750) + OData client quoting and proxy fixes
…bc37 fix: qualify inherited associations with their declaring module (#662)
Slice 0 of PROPOSAL_first_class_expressions.md. The spelling mendixlabs#750 proposes, `dynamicclasses: [ if ... then 'a' else 'b' ]`, already parses - as propertyValueV3's array alternative, into a []string - and no writer reads a list: - create page: GetStringProp / the columnClass builder take only a string, so the widget was stored with no dynamic class while check was clean and exec said "Created page". - alter page set DynamicClasses = [...]: the mutator returned nil when the type check failed - "Altered page", nothing written. - alter page set DynamicCellClass = [...] on a column: %v wrote the fused tokens `[if$x/Ythen'a'else'b']` into the Expression field. Measured with pre-fix and fixed binaries on copies of ako/TestApp (11.14.0). Pre-fix, describe showed `container c1` with no DynamicClasses while the quoted control kept its expression, and the alter left the stored value unchanged. MDL-WIDGET32 reports DynamicClasses / DynamicCellClass holding a list, with no project needed (keyed on the property, not the brackets: `visible: [cond]` stays valid). The mutator now returns an error for a list, which check -p reports through its dry run and exec stops on. Tests failed first with "got 0 violation(s), want 1" and "a bracketed list was accepted ... and reported as success"; quoted-expression and `visible: [...]` controls pass throughout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P65SqmwwvbWdJVRwhYiMQw
Entries for 26880a0, dad9be6, 6a48ed7 and dcd6f71, which shipped without them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P65SqmwwvbWdJVRwhYiMQw
The package-level explanation ran straight into describeAndReparse's doc comment, so godoc attached both to the helper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P65SqmwwvbWdJVRwhYiMQw
fix: DESCRIBE WIDGET, widget list and LSP see installed widgets without widget init (#663)
fix(pages): refuse an expression property written as a bracketed list (mendixlabs#750 slice 0)
Sync mendixlabs/mxcli main into ako/mxcli main
Refuse a bare attribute reference on every write, ALTER PAGE included
Check and type a label's design properties against the theme's Label group
…amed-design-property
…ed-bindings-check
…perty ALTER STYLING: name the current property for a renamed design-property key (CE6087)
fix(catalog): tell same-named excluded/live documents apart (mendixlabs#1185)
fix(describe): name the datasource key on a widget declaring several (mendixlabs#1199)
microflows() yields microflows, nanoflows and rules, which share one catalog table. Six shipped rules (CONV009, CONV010, QUAL001, QUAL003, QUAL004, CUSTOM002) hardcoded document_type="Microflow" and a "Microflow '...'" message, so a nanoflow with 30 activities was reported as "Microflow 'X' has 30 activities". The wrong type also reached the documentType field of the JSON and report output. The Go rules were fixed earlier with Microflow.DocumentNoun(), but Starlark rules could not call it. Expose it on the microflow struct as document_noun / document_noun_title, use it in the six rules, and document both fields in the write-lint-rules skill. Output for microflows is unchanged. QUAL004 needed only the label: rule calls from decisions are already recorded as 'call' refs, so called rules are not reported as orphaned. The test runs every shipped rule that walks microflows() over a fixture with one flow per flavour. It requires a finding on each flavour, so the assertions cannot pass vacuously, and a guard fails if a new such rule is not added to the list. Follow-up to mendixlabs#1178 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011AvRv9GAQJbrHgrgMmfsBM
…ed-bindings-check
… parameter Two gaps left after the catalog fix for mendixlabs#1183, both in how a Java action's type parameters survive MDL: A type parameter named after a primitive (Studio Pro allows `String`) could not be declared -- `entity <String>` was a parse error -- and DESCRIBE printed its references bare, so `returns String` re-parsed as the primitive. The declaration slot now takes identifierOrKeyword, and DESCRIBE renders every type-parameter name through mdlIdent: quoted `"String"` is the type parameter, unquoted `String` the primitive. describe -> exec -> describe is now identical. `list of T` for a type parameter T went down the entity path and was written as a list of the entity `.T` (mx check CE1613), and a Studio Pro "List of <type parameter>" read back as a bare `List`. ListType now carries a type-parameter reference: read and written as a ParameterizedEntityType list element (Model SDK: createInListTypeUnderParameter, metamodel 7.21.0+), bound at CREATE, described as `List of T`, and cataloged as `List of TypeParameter:T`. mx check on 11.6.6: previous build CE1613 x2; fixed build 0 errors, also with the type parameter named String. Refs mendixlabs#1183 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01G2c72jsT9JsY1c3V2eViLQ
fix(lint): pin documented Starlark field values to what the API emits
fix(visitor): store text expressions with their whitespace
check --references: refuse bare bindings ALTER PAGE inserts where no entity is in scope
…hout-context # Conflicts: # CHANGELOG.md
Refuse an input binding with no object to bind to (MDL-WIDGET34)
Resolve every widget keyword's design properties through the $Type it writes
fix(lint): name nanoflows and rules correctly in shipped Starlark rules
fix(catalog): mark java action type-parameter types unambiguously
AI Code ReviewLet me review this pull request based on the provided information and the checklist from CLAUDE.md. First, I need to understand what this PR is about. From the PR body:
This is a sync PR that brings in 46 commits from a fork. The changes cover:
Now I'll review against the checklist: Overlap & duplication
MDL syntax designLooking through the diff, I don't see new MDL syntax being added. The changes are mostly:
The PR body mentions:
I don't see new MDL keywords or syntax being introduced, so this section may not apply. Full-stack consistencyFor any MDL features, they need to be wired through the full pipeline. Since I don't see new MDL syntax being added, this may not apply directly. However:
Let me check if there are any new MDL features:
I don't see evidence of new MDL syntax being added that would require full-stack wiring. Test coverageThe PR includes many new test files:
This looks good - the PR includes tests for the new functionality and fixes. Security & robustnessI don't see obvious security issues in the diff. The changes are mostly:
The new Scope & atomicityThis is a sync PR bringing in 46 commits, so it's naturally broad. However:
While it's a large PR, it's presented as a sync of work that was done in a fork, which is a common practice. Code qualityI don't see obvious code quality issues in the selected diff portions. The changes appear to be:
Bugs & correctnessThe PR is specifically about fixing bugs found through round-tripping Studio Pro-authored content:
This suggests the PR is addressing real bugs. Now, let me look for any specific issues in the diff:
Let me check if there are any violations of the checklist: Overlap & duplication:
MDL syntax design:
Full-stack consistency:
Test coverage:
Scope & atomicity:
Code quality:
Bugs & correctness:
Let me double-check if there are any new MDL features that I missed: Scanning through the PR body again:
The only potential new MDL-related feature I see is the first-class expressions mention, but the PR says this is about fixing how existing functionality works ("DynamicClasses / DynamicCellClass written as expressions"). Actually, looking at the PR body more carefully:
This suggests that first-class expressions were already implemented (slice 2), and this PR is fixing how they're handled in specific contexts. I also see in the files changed:
Let me check what was changed in those grammar files to see if new syntax was added: From the diff: And for MDLService.g4: This shows that new grammar rules were added for:
This indicates that new MDL syntax was indeed added or modified! Let me check if these were wired through the full pipeline: For the page-related changes:
Looking at the files changed:
For the service-related changes (OData):
Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
Syncs
ako/mxcli:mainintomendixlabs/mxcli:main: 46 commits since #1179, landed in the fork through PRs ako#660–#698. Most of them are describe → exec round-trip fixes, found by round-tripping Studio Pro-authored pages (Administration v4.3.2, Feedback v4.0.2) and an OData client.Pages: new authoring
labelwidget (Forms$Label). Describe emits it with its name, so the output re-parses;statictextwrote Forms$Text, which Mendix 11 cannot load.Visible: Attr in (…): round-trips Studio Pro's "based on attribute value" visibility. It was silently dropped (8 → 0 conditions on Account_Edit).open_link $currentObject/Attr: a dynamic link address.Pages: fixes
DataSource:that the builder then rejects as ambiguous (associatedFiles / associatedImages) #1199).check --referencesrefuses them where ALTER inserts outside an entity scope (previously a projectmx checkcould not load).$Typeit writes,labelincluded (CE6085). Renamed theme keys name the current property in check and ALTER STYLING (CE6087). A compound design property's list is written with marker 2.true and falsewas stored astrueandfalse).widget listand the LSP see installed widgets withoutwidget init(DESCRIBE WIDGET calls an installed widget "unknown", and both remedies its error suggests fail ako/mxcli#663).OData client
HttpUsername: 'admin'stores the string. MDL-ODATA07 refuses the old doubled-quote and quoted-@spellings.ServiceUrl: Module.Constnames a constant, like ProxyHost. Proxy constant references are stored without the@.Check / lint / catalog
checkwarns on a commit inside a loop before it is written (mxcli checkpasses a commit-inside-a-loop thatlint(CONV011) already knows; no script-scoped lint exists (feature request: check hints,check --lint, orlint --document) #1186).lint -d/--documentsscopes a lint run to named documents.list ofa type parameter survive MDL (Catalog java actions return type confusing in case of typeparam named after primitive #1183).ASis resolved (An uppercaseASon an association-path join leaves its alias unresolved, switching off type checks for its columns ako/mxcli#652, CE6770).Microflows
mxcli layout flowsre-arranges stored microflows and nanoflows: geometry only,$ID/GUID preserved, a no-op second run (alternative toRESET LAYOUTmicroflow option is missing from mxcli entirely #837).Docs
constant Mod.Creferences, proxy constants measured against Studio Pro