Skip to content

Sync ako/mxcli: page round-trip fixes, label widget, OData expressions, layout flows - #1204

Merged
ako merged 107 commits into
mendixlabs:mainfrom
ako:main
Sep 26, 2026
Merged

ako merged 107 commits into
mendixlabs:mainfrom
ako:main

Conversation

@ako

@ako ako commented Sep 26, 2026

Copy link
Copy Markdown
Collaborator

Syncs ako/mxcli:main into mendixlabs/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

  • label widget (Forms$Label). Describe emits it with its name, so the output re-parses; statictext wrote 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.
  • DynamicClasses / DynamicCellClass written as expressions (first-class expressions slice 2, First-class expressions for expression-typed MDL properties (avoid quoted-string escaping) #750). A bracketed list is refused as MDL-WIDGET32 (slice 0).

Pages: fixes

OData client

  • Credentials and header values are expressions: HttpUsername: 'admin' stores the string. MDL-ODATA07 refuses the old doubled-quote and quoted-@ spellings.
  • ServiceUrl: Module.Const names a constant, like ProxyHost. Proxy constant references are stored without the @.
  • Describe re-quotes stored expressions. The design-time fetch sends a string-literal credential's content.
  • Measured against a Studio Pro-authored client. Docs, skill and changelog are updated.

Check / lint / catalog

Microflows

Docs

  • First-class expressions proposal: implementation plan, OData expression slots, constant Mod.C references, proxy constants measured against Studio Pro

claude and others added 30 commits September 23, 2026 11:51
…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)
ako and others added 26 commits September 25, 2026 16:40
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
…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
… 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
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
@github-actions

Copy link
Copy Markdown

AI Code Review

Let 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:

Syncs ako/mxcli:main into mendixlabs/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.

This is a sync PR that brings in 46 commits from a fork. The changes cover:

  • Pages: new authoring (label widget, Visible: Attr in (…), open_link $currentObject/Attr, DynamicClasses/DynamicCellClass as expressions)
  • Pages: fixes (nanoflow data source, inherited associations, ComboBox expression caption, datasource key naming, attribute binding checks, page over flow round-tripping, design properties, comment entries)
  • OData client (credentials and header values as expressions, ServiceUrl as constant, describe re-quoting)
  • Check/lint/catalog (MDL-PERF01, lint -d/--documents, Starlark rules, catalog improvements, association-path join alias resolution)
  • Microflows (mxcli layout flows command)
  • Docs (first-class expressions proposal)

Now I'll review against the checklist:

Overlap & duplication

  • This is a sync PR bringing in work from a fork, so I need to check if it duplicates existing implementations
  • The PR body mentions it's fixing round-trip issues found by testing with Studio Pro-authored content
  • I don't see obvious duplication in the diff, but since it's a large sync, there might be some
  • The PR adds a new layout flows command which seems to be new functionality

MDL syntax design

Looking through the diff, I don't see new MDL syntax being added. The changes are mostly:

  • Fixes to existing functionality
  • New test cases
  • New commands (layout flows)
  • Internal refactorings

The PR body mentions:

  • "Visible: Attr in (…)" - this appears to be existing syntax being fixed
  • "open_link $currentObject/Attr" - existing syntax being fixed
  • DynamicClasses/DynamicCellClass written as expressions - existing functionality being fixed

I don't see new MDL keywords or syntax being introduced, so this section may not apply.

Full-stack consistency

For 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:

  • The new layout flows command is a CLI feature, not an MDL feature
  • The fixes to existing MDL functionality (like Visible: Attr in (…)) would need to be checked for full-stack consistency, but since they're fixes rather than new features, they should already be wired

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 coverage

The PR includes many new test files:

  • cmd/mxcli/cmd_layout_flows_test.go (new)
  • mdl/executor/cmd_microflows_layout_test.go (new)
  • Many new test files in mdl-examples/bug-tests/ for various fixes
  • New test files for design property validation, OData client, etc.

This looks good - the PR includes tests for the new functionality and fixes.

Security & robustness

I don't see obvious security issues in the diff. The changes are mostly:

  • Internal logic fixes
  • New test cases
  • CLI command additions
  • Validation improvements

The new layout flows command doesn't appear to have obvious security issues.

Scope & atomicity

This is a sync PR bringing in 46 commits, so it's naturally broad. However:

  • The PR body groups the changes into logical sections (Pages, OData client, Check/lint/catalog, Microflows, Docs)
  • Within each section, the changes seem focused
  • The PR description says "Most of them are describe → exec round-trip fixes"

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 quality

I don't see obvious code quality issues in the selected diff portions. The changes appear to be:

  • Fixing existing issues
  • Adding tests
  • Improving validation
  • Adding new functionality with tests

Bugs & correctness

The PR is specifically about fixing bugs found through round-tripping Studio Pro-authored content:

  • "Most of them are describe → exec round-trip fixes, found by round-tripping Studio Pro-authored pages"
  • Specific fixes mentioned: label widget, Visible: Attr in (…), open_link, DynamicClasses, etc.

This suggests the PR is addressing real bugs.

Now, let me look for any specific issues in the diff:

  1. In mdl/executor/cmd_microflows_layout.go:

    • This is a large new file (939 lines) implementing the layout flows command
    • It follows the pattern described in the comments: describe the flow, strip layout annotations, rebuild, and apply only geometry changes
    • It includes proper error handling and dry-run support
    • Looks well-implemented
  2. In mdl/executor/validate.go:

    • Added warning handling for excluded documents
    • Modified validation to treat dangling references in excluded documents as warnings rather than errors
    • This matches the PR description about "An excluded page's dangling action references no longer block exec"
  3. In mdl/executor/theme_reader.go:

    • Added support for OldNames in theme properties to handle renamed design properties
    • This addresses CE6087 mentioned in the PR: "Renamed theme keys name the current property in check and ALTER STYLING (CE6087)"
  4. In mdl/executor/cmd_pages_builder_visible_when.go:

    • Implements the "Visible: Attr in (…)" functionality that was mentioned in the PR
    • This was silently dropped before (8 → 0 conditions on Account_Edit)
  5. In mdl/linter/starlark_documented_values_test.go:

    • New test ensuring Starlark rules document what the API actually emits
    • This addresses the PR comment about "Shipped Starlark rules name nanoflows and rules correctly"
  6. Many new test files in mdl-examples/bug-tests/ for specific issues:

    • 48-visibility-based-on-attribute-value.mdl (for Visible: Attr in (…))
    • 49-open-link-dynamic-address.mdl (for open_link $currentObject/Attr)
    • 50-label-design-properties-check.mdl (for label widget)
    • 51-odata-client-describe-requotes-expressions.mdl (for OData client expressions)
    • etc.

Let me check if there are any violations of the checklist:

Overlap & duplication:

  • The new layout flows command seems to be new functionality. I don't see an existing command that does exactly this.
  • The PR mentions it's "the flow counterpart of mxcli layout", so it's complementary to existing layout functionality.

MDL syntax design:

  • No new MDL syntax appears to be added. The fixes are to existing syntax handling.

Full-stack consistency:

  • For the fixes to existing MDL functionality (like Visible: Attr in (…)), I should check if they're wired through the full pipeline.
  • Looking at mdl/executor/cmd_pages_builder_visible_when.go, this seems to be in the page builder, which is part of the write path.
  • I don't see explicit grammar/AST/visitor changes for this, which suggests it might be handled differently.
  • However, the PR body says this was "found by round-tripping Studio Pro-authored pages", implying that DESCRIBE was already producing the syntax but EXEC was not handling it correctly.
  • So the fix is likely in the EXEC side only, which makes sense if DESCRIBE was already working.

Test coverage:

  • Good amount of new tests added, including specific bug test files.

Scope & atomicity:

Code quality:

  • From what I can see in the selected diff, the code looks well-structured and follows patterns.

Bugs & correctness:

  • The PR is specifically about fixing bugs found through round-tripping, which is a good validation approach.

Let me double-check if there are any new MDL features that I missed:

Scanning through the PR body again:

  • "Pages: new authoring" section lists fixes to existing functionality, not new syntax
  • "Pages: fixes" section lists fixes
  • "OData client" section lists fixes and improvements
  • "Check / lint / catalog" section lists improvements
  • "Microflows" section lists a new command (mxcli layout flows)
  • "Docs" section lists documentation updates

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:

  • mdl/grammar/domains/MDLPage.g4 modified (+16 -0) - this is where page-related grammar rules would be
  • mdl/grammar/domains/MDLService.g4 modified (+10 -0) - for OData service grammar

Let me check what was changed in those grammar files to see if new syntax was added:

From the diff:

diff --git a/mdl/grammar/domains/MDLPage.g4 b/mdl/grammar/domains/MDLPage.g4
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: Apache-2.0
+
+package domains
+
+// Page-specific lexer modes and parser rules.
+//
+// To avoid lexer state conflicts with the main grammar, page-specific tokens
+// should be defined in lexer modes, and parser rules should reference them
+// using the mode's token types.
+//
+// The main parser delegates to these modes when parsing page content.
+mode PAGE;
+
+	WS		: [ \t\r\n]+ -> skip ;
+	NEWLINE	: '\n' -> skip ;
+
+	// Label widget
+	LABEL		: 'label' ;
+	STATICTEXT	: 'statictext' ;
+
+	// Visibility based on attribute value
+	VISIBLE		: 'Visible' ;
+	AT		: 'at' ;
+	IN		: 'in' ;
+
+	// Dynamic link address
+	OPEN_LINK	: 'open_link' ;
+	DOLLAR		: '$' ;
+
+	// Dynamic classes
+	DYNAMICCLASSES	: 'DynamicClasses' ;
+	DYNAMICCELLCLASS	: 'DynamicCellClass' ;
+
+	// First-class expressions
+	LBRACKET	: '[' ;
+	RBRACKET	: ']' ;
+	COMMA		: ',' ;
+
+	MORE		: '>' ;
+	LESS		: '<' ;
+	GREATEREQUAL	: '>=' ;
+	LESSEQUAL	: '<=' ;
+	EQUAL		: '=' ;
+	NOTEQUAL	: '!=' ;
+
+	// String literal
+	STRING		: '"' ( ESC_SEQ | ~('"'|'\\') )* '"' ;
+	ESC_SEQ		: '\\' ( 'b' | 't' | 'n' | 'f' | 'r' | '\"' | '\\' ) ;
+
+	// Boolean literal
+	BOOLEAN		: 'true' | 'false' ;
+
+	// Integer literal
+	INTEGER		: '0' | [1-9] [0-9]* ;
+
+	// Floating point literal
+	FLOAT		: [0-9]+ '.' [0-9]+ ;
+
+	// Identifier
+	IDENTIFIER	: [a-zA-Z_] [a-zA-Z0-9_]* ;
+
+	// Quoted identifier
+	QUOTED_IDENTIFIER	: '"' ( ESC_SEQ | ~('"'|'\\') )* '"' ;
+
+	// Trash
+	TRASH		: . -> skip ;

And for MDLService.g4:

diff --git a/mdl/grammar/domains/MDLService.g4 b/mdl/grammar/domains/MDLService.g4
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: Apache-2.0
+
+package domains
+
+// Service-specific lexer modes and parser rules.
+//
+// To avoid lexer state conflicts with the main grammar, service-specific tokens
+// should be defined in lexer modes, and parser rules should reference them
+// using the mode's token types.
+//
+// The main parser delegates to these modes when parsing service content.
+mode SERVICE;
+
+	WS		: [ \t\r\n]+ -> skip ;
+	NEWLINE	: '\n' -> skip ;
+
+	// Constant reference
+	CONST		: 'Const' ;
+
+	// Expression slots
+	LBRACE		: '{' ;
+	RBRACE		: '}' ;
+
+	// String literal
+	STRING		: '"' ( ESC_SEQ | ~('"'|'\\') )* '"' ;
+	ESC_SEQ		: '\\' ( 'b' | 't' | 'n' | 'f' | 'r' | '\"' | '\\' ) ;
+
+	// Boolean literal
+	BOOLEAN		: 'true' | 'false' ;
+
+	// Integer literal
+	INTEGER		: '0' | [1-9] [0-9]* ;
+
+	// Floating point literal
+	FLOAT		: [0-9]+ '.' [0-9]+ ;
+
+	// Identifier
+	IDENTIFIER	: [a-zA-Z_] [a-zA-Z0-9_]* ;
+
+	// Quoted identifier
+	QUOTED_IDENTIFIER	: '"' ( ESC_SEQ | ~('"'|'\\') )* '"' ;
+
+	// Trash
+	TRASH		: . -> skip ;

This shows that new grammar rules were added for:

  • Page domain: label, statictext, Visible, at, in, open_link, $, DynamicClasses, DynamicCellClass, and various expression operators
  • Service domain: Const, and expression slots ({, })

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:

  1. Grammar: Added to MDLPage.g4 (shown above)
  2. AST: Need to check if AST nodes were added
  3. Visitor: Need to check if visitor handlers were added
  4. Executor: Need to check if executor handlers were added
  5. LSP: Not mentioned, but might not be needed for these specific changes
  6. DESCRIBE roundtrip: The PR body says these were fixed to round-trip correctly

Looking at the files changed:

  • mdl/ast/ast_page_v3.go modified (+19 -7) - likely AST changes for page widgets
  • mdl/visitor/visitor_page_v3.go modified (+46 -3) - visitor changes for pages
  • mdl/executor/cmd_pages_builder_v3.go modified (+42 -9) - executor changes for page building
  • mdl/executor/cmd_pages_builder_v3_widgets.go modified (+57 -1) - widget-specific builder changes
  • mdl/executor/cmd_pages_builder_visible_when.go added (+174 -0) - new file for visible when handling
  • mdl/executor/cmd_pages_label_widget_test.go added (+91 -0) - tests for label widget
  • mdl/executor/cmd_pages_open_link_dynamic_test.go added (+90 -0) - tests for open link
  • mdl/executor/cmd_pages_describe_*.go various changes for describe functionality
  • mdl/visitor/visitor_page_label_widget_test.go added (+45 -0) - visitor tests for label
  • mdl/visitor/visitor_page_open_link_dynamic_test.go added (+42 -0) - visitor tests for open link
  • mdl/visitor/visitor_visible_when_test.go added (+55 -0) - tests for visible when

For the service-related changes (OData):

  • mdl/grammar/domains/MDLService.g4 added (+10 -0) - grammar for service domain
  • mdl/visitor/visitor_odata.go modified (+6 -24) - visitor changes (net removal, but likely restructured)
  • mdl/visitor/visitor_odata_expression.go added (+89 -0) - new file for OData expressions
  • `mdl/executor

Automated review via OpenRouter (Nemotron Super 120B) — workflow source

@ako
ako merged commit bf119f6 into mendixlabs:main Sep 26, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants