Sync ako/mxcli: pages, check/exec agreement, microflow error events, OData - #1141
Merged
Merged
Conversation
`mxcli check -p --references` refused MDL that `describe page` had just
emitted, while `exec --no-check` wrote it without complaint. On a blank
Mendix 11.12.2 project with no .mxcli/widgets:
check --references -> `htmlelement` is not a widget in this project [MDL-WIDGET25]
`attribute` is not a widget in this project [MDL-WIDGET25]
`tagcontentcontainer` is not a widget in this project [MDL-WIDGET25]
exit 1, so exec refused to run the script
exec --no-check -> info: updated widget definitions for Repro1135.mpr
Created page MyFirstModule.HtmlDemo
check is meant to be the strict gate and exec the thing that runs; here it
was inverted, and the script it blocked was one describe had just written.
The two read different registries. pageBuilder.initPluggableEngine calls
RefreshStaleWidgetDefinitions before LoadUserDefinitions, so exec generates
.mxcli/widgets/*.def.json from the project's installed .mpk on its way past.
LoadWidgetRegistry -- check, lint and the LSP -- called only
LoadUserDefinitions, so a project that had never run `mxcli widget init`
knew the nine embedded definitions and nothing else. The
`pluggablewidget '<id>'` branch of validateWidgetKind has packageInstalledFor
as its escape hatch; the generic-MDL-name branch has none.
LoadWidgetRegistry now refreshes stale definitions the same way, best-effort
-- a project whose definitions cannot be written gets the registry it got
before, which beats failing a check over a cache.
Control (mdl/executor/validate_widget_kind_uninitialized_test.go): before
the fix the test reports the three names above verbatim; a typo
(`htmlelemnt`) in the same project is still MDL-WIDGET25 after it, and now
suggests `htmlelement`, which it could not while the candidate list was the
nine embedded widgets.
The bug self-heals -- the first exec writes the definitions and every check
after it passes -- which is why it reads as flaky. Reproduce with
`rm -rf .mxcli/widgets` or it is invisible.
Refs: mendixlabs#1135
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LYaTtzjmMCUjo8XAVN1sQx
`alter page … set '<design property>' = off on <native listview>` failed
with
property "Remove empty text" not found (widget has no pluggable Object)
"pluggable Object" is not something the author wrote, cannot be made true by
editing the script, and is not the whole truth: design properties on a
built-in widget are written by ALTER STYLING, which the message never
mentioned. It now says so, naming the widget and the property, so the reader
has the statement rather than a bug report.
A Forms$Appearance and no pluggable Object is exactly a built-in widget,
which is when the advice applies. A pluggable widget keeps the error naming
its own declared keys -- redirecting a mistyped pluggable key to ALTER
STYLING would point at a command that cannot write it either (control:
TestSetWidgetProperty_PluggableWidgetKeepsItsOwnError).
Measured on a blank 11.12.2 project, and worth recording because it inverts
the report: the three names in it are NOT ListView design properties in the
Atlas shipped with Mendix 11.
themesource/atlas_core/web/design-properties.json declares Style, Hover
style and Row size, and nothing else. ALTER STYLING writes the reported key
happily and mxbuild then refuses the project:
alter styling … set 'Remove empty text' = on; -> mx check: 1 error,
[CE6083] "Design property Remove empty text is not supported by your
theme." at List view 'lvThings'
alter styling … set 'Row size' = 'Small'; -> mx check: 0 errors
So refusing the write was right and only the reason was wrong, which is why
this is the message and not write support.
Left open deliberately, and noted in the bug test: MDL-WIDGET11 ("design
property not defined for this widget type") covers CREATE PAGE / CREATE
SNIPPET / ALTER PAGE INSERT|REPLACE and not ALTER STYLING, whose widget type
lives only in the stored document -- so an unsupported key there is silent
until mxbuild says CE6083. Resolving it needs a third $Type-to-registry
mapping and belongs in its own change rather than bolted on here.
Refs: mendixlabs#1135
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LYaTtzjmMCUjo8XAVN1sQx
The bug test and the finding both said the ALTER STYLING design-property gap was "tracked separately" without saying where, which is how a note goes stale. It is #509. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LYaTtzjmMCUjo8XAVN1sQx
…cannot call (mendixlabs#1089) CE7252 "The parameters for remote action '<x>' have changed" survived DROP + CREATE OR REPLACE MICROFLOW and CREATE OR MODIFY EXTERNAL ENTITIES, and was reported as a stale parameter fingerprint needing a REFRESH ODATA CLIENT command. No fingerprint is stored — Mendix re-derives the alignment from the consumed service's cached contract on every build — so recreating the microflow really does rewrite everything the call holds, and its failure means the rewrite was equally wrong, or the action is not callable at all. Both turned out to be true, on different types. Measured on mxbuild 11.12.0, one action per EDM shape written by mxcli into a real app and checked on its own: String Guid Boolean Byte SByte Int16 Int32 Int64 Decimal 0 errors Double Single Date DateTime DateTimeOffset 0 errors an EnumType, an EntityType, Collection(EntityType) 0 errors Edm.TimeOfDay CE7252 / CE7269 Edm.Duration Stream Binary Geography* CE7255 (+CE7253) a ComplexType, a TypeDefinition, Collection(Edm.*) CE7255 (+CE7253) Edm.TimeOfDay is the only type that failed without Mendix also calling it unsupported, which is what identifies it as mxcli's gap rather than the platform's: it was unmapped, so the call was written with no ParameterType or no VariableDataType, and neither field is reachable from MDL — both are derived from the contract, never typed by the developer. It is now DateTime. Everything Mendix answers with CE7255 is beyond any write. Those statements executed silently and left a project that could not build, which is the dead end in the report; they are now refused, naming CE7255 and saying outright that no MDL clears it. Edm.Binary loses its mapping for the same reason — it mapped cleanly to DataTypes$BinaryType and Mendix rejects the action regardless, so a kind there only routed it past the refusal. A third case sat between the two: an entity-typed PARAMETER whose external entity was never imported. That one is fixable and the remedy is one statement, which mxcli already named for a RETURN type and not for a parameter — the parameter side being the one that reports as CE7252. The rule is applied by `mxcli check --references` and by the writer, from one function, as CheckLayoutPlaceholderNames already is. Only the checker would have missed the reported workflow: `mxcli exec` does not run the project-resolved reference pass, so the statement would still have executed. An enum-typed parameter builds at 0 errors with no type written at all, so "mxcli could not name a Mendix type for it" is not on its own grounds to refuse; the accepts-what-builds test carries that control. Controls: reverting the TimeOfDay mapping takes the probe app 0 -> 2 errors, CE7252 on the parameter and CE7269 on the return, the reported symptom verbatim; stubbing the type check restores the silent write. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TH4k6nnB86KpiuWU9x68Ba
An earlier commit on this branch said Atlas declares "Style, Hover style and
Row size, and nothing else" for a List View. That came from reading the
`ListView` key of themesource/atlas_core/web/design-properties.json
directly, and it misses the `Widget` group, which applies to every widget.
Measured on 11.12.2:
$ mxcli -p App.mpr -c "show design properties for listview"
From: Widget (inherited) Spacing, Align self, Hide on
From: ListView Style, Hover style, Row size
Six. The conclusion the number was cited for is unaffected — none of the six
is 'Remove empty text', so CE6083 still says mxcli was right to refuse the
write and wrong only in the reason it gave.
But the number is load-bearing for the follow-up in #509: someone
building the MDL-WIDGET11 pass for ALTER STYLING from the wrong three would
reject Spacing, Align self and Hide on as unknown keys.
ThemeRegistry.GetPropertiesForWidget already prepends the inherited group,
so the existing validator is correct as written and only the prose was
wrong. Both notes now say to count with `show design properties`, never from
the JSON key.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LYaTtzjmMCUjo8XAVN1sQx
MDL-WIDGET20 warned that `Editable` on a list view or a grid column is "silently dropped on write and the widget stays enabled". Every clause after the "but" was wrong, and the suggestion is addressed to a button --- the tell that the branch was written for a different widget. Measured on 11.12.2: exec writes it, `describe page` reads back `Editable: true`, and mxbuild reports 0 errors. Two different Mendix properties were conflated. editableWidgetTypes is the set of Pages types carrying Editability / ConditionalEditabilitySettings, which is correct for the bug the rule was written for (mendixlabs#928, `editable:` on a button). Pages$ListView and Pages$GridColumn carry neither: they have a plain `Editable bool`, a different property meaning "make the inputs INSIDE me editable". Parsing generated/metamodel for that shape returns exactly those two --- GridColumn was not in the report and would have been missed by fixing only what was reported. So: a second set, not more entries in the first. The bracket form `Editable: [expr]` (lowered to EditableIf) still warns on both, because neither type has ConditionalEditabilitySettings and that form genuinely is dropped. Silencing both would re-create the worse half of mendixlabs#928, where the shape the docs recommend vanishes without a word. TestEditableWidgetTypesMatchMetamodel could not have caught this: it enumerates types carrying Editability, and these carry none. TestPlainEditableBoolTypesMatchMetamodel is its sibling for the other property, so a new one fails a test instead of becoming a false positive. Controls, all passing: the mendixlabs#928 button case still fires; the bracket form on a list view still fires; input widgets stay clean. Why it was worth fixing rather than tolerating --- buildListViewV3's own comment records that a list view without `Editable` renders every input as `<div class="form-control-static">`, with entity access ReadWrite and `mx check` at 0 errors. The warning steered authors away from the one property that fixes a symptom the code itself calls hard to diagnose. Fixes #510 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LYaTtzjmMCUjo8XAVN1sQx
`template for <the list view's own entity>` passed check, was written by
exec, and mxbuild then refused the project:
[CE0543] "The entity of the list view template is 'MyFirstModule.Vehicle'
and this is not a specialization of the entity of the list view."
The guard existed and was one case too generous. Both call sites gated on
entityIsOrDescendsFrom(spec, listEntity), which returns true on its first
loop iteration when spec == listEntity. Mendix requires a strict
specialization: the list view's own body already renders an object no
template matches, so a template for the base entity is a second,
unreachable default.
The belief was encoded three times and measured zero times. The guard's own
message offered the case Mendix refuses --- "X is not <listEntity> or a
specialization of it" --- and TestBuildListViewTemplateOnTheListEntityItself
asserted it was "the base case Mendix permits", justified by what
entityIsOrDescendsFrom returns rather than by any run. That test is now
inverted, and carries the measurement.
Measured on a blank 11.12.2 project, list view over MyFirstModule.Vehicle:
template for mxcli exec mx check
----------------- ------------ --------
Car (specialized) writes 0 errors <- control
Thing (unrelated) refused --- <- guard was not missing
Vehicle (its own) WRITES CE0543 <- the bug
All three rows matter: the first is the control against a guard that
refuses everything, the second shows the guard was already there.
entityIsOrDescendsFrom is left alone --- its other callers resolve
association direction, where the reflexive case is correct. The rule lives
in one new method that both CREATE PAGE and ALTER PAGE INSERT/REPLACE call,
since the message and the rule were already duplicated across them and that
is how two copies drift. The syntax help said the same wrong thing and is
corrected.
Not fixed here, and noted in #514: the guard runs at exec, not at
check, so all three rows still report `Check passed!`. A false green, but a
safe one --- nothing is written when it refuses.
Fixes #514
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LYaTtzjmMCUjo8XAVN1sQx
fix(odata): type Edm.TimeOfDay, and refuse an external action Mendix cannot call (mendixlabs#1089)
…ries The catalog's SourceType/TargetType, ElementType and AccessType values were ALL-CAPS string literals repeated across ~100 emit sites. They are a public vocabulary — Starlark lint rules filter on them through `reference.source_type`, `permission.element_type` and `permission.access_type` — but there was nothing for a consumer, or for documentation, to be checked against. Name them, and publish the four lists: RefSourceObjectTypes, RefTargetObjectTypes, PermissionElementTypes, PermissionAccessTypes. The source and target lists differ on purpose: a LAYOUT or a WIDGET is only ever pointed at, a SCHEDULED_EVENT or a PROJECT_SETTINGS only ever points, and one list for both would put values in front of rule authors that their filter can never match. Mechanical: every literal becomes the constant of the same value, including the 'WIDGET' baked into insertWidgetRefs' SQL, which becomes a bound parameter. No behaviour changes. This mirrors SourceObjectTypes in builder_source.go, which exists for the same reason. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018M6ewJmoi3dEjEHDmZoVLt
The bundled write-lint-rules skill is the only documentation of the Starlark
rule API, and six of its example rows named values the API has never returned.
Both failure modes are silent: a rule built from them compiles, runs, matches
nothing and reports a clean pass.
action_type listed Mendix's BSON *storage* names — CreateChangeAction,
CommitAction, ShowFormAction — where the catalog labels an
action with its SDK name (CreateObjectAction, ShowPageAction,
ClosePageAction, …), derived from the parsed Go type.
source_type documented lower-case ("microflow", "page") against an
target_type upper-case vocabulary.
element_type the same lower-casing, unreported.
access_type
data_type documented "string"/"integer"/"datetime"; emitted String/
Integer/DateTime. The most-used filter in a lint rule.
ref_kind documented no values at all.
The last three were not in the report. They are the same defect in the same
tables, found by checking the siblings rather than only the rows that were named.
Correct all six against the functions that produce them, and add a callout above
the tables: what the case convention is, why action_type is never a storage name,
and the two sqlite3 probes that answer the question against a real project.
mdl/catalog/lint_rule_doc_vocabulary_test.go pins each documented value to its
producer, so the tables cannot drift again. action_type needs no list — the label
IS the Go type name, so the test reads the isMicroflowAction marker methods out
of sdk/microflows/microflows_actions.go with go/ast; the others check against the
vocabularies named in the previous commit. Each check fails rather than passes
when its table row goes missing, and each scan carries a vacuity control.
This is the second time this vocabulary has bitten: CONV010's allowlist held the
storage names and was fixed a month ago, pinned by lint_rule_vocabulary_test.go.
It recurred because the documentation the rule author copied from was never
corrected.
Refs: mendixlabs#1027
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018M6ewJmoi3dEjEHDmZoVLt
…flat multi-select value Two ways a design property reached mxbuild instead of `mxcli check`. ALTER STYLING (#509) was the one statement whose entire job is writing design properties, and the one statement MDL-WIDGET11 never looked at --- ValidateDesignPropertiesForStatement switches on CreatePageStmtV3, CreateSnippetStmtV3 and AlterPageStmt, and *ast.AlterStylingStmt is not among them. `set 'Remove empty text' = on` passed check, wrote, and failed the build with CE6083. The obvious fix was the wrong one. Mapping the stored $Type to a theme-registry key would be a THIRD consumer of one concept --- there is already mdlKeywordToDesignPropsKey, and an unused bsonTypeToDesignPropsKey with zero non-test callers, hence never validated --- and duplicate resolvers drifting apart is what this area keeps producing. So the pass asks the question that IS answerable without opening the document: does any widget type in the theme declare this key? A key declared nowhere cannot be right here either, which is the reported case exactly. A key declared for another widget type is accepted: under-reporting, never over-reporting, the only safe direction for a check that cannot see what it is judging. The declared set is built from every group, not one. Three of a List View's six properties come from the inherited `Widget` group, so a single-type lookup would report `Align self` as unknown. Multi-select (#511) turned out not to be a missing capability. The reference document was already in the project: grep found three Studio Pro-authored Atlas pages carrying `Hide on`, and decoding one gives Forms$DesignPropertyValue Key: "Hide on" Value: Forms$CompoundDesignPropertyValue Properties: [ Forms$DesignPropertyValue{ Key: "Phone", Value: Forms$ToggleDesignPropertyValue } ] which is structurally `Spacing`, which MDL already writes. Measured: `DesignProperties: ['Hide on': ['Phone': on, 'Tablet': on]]` writes, round-trips through DESCRIBE and builds at 0 errors. Only the FLAT spelling was broken --- 'Phone' is a declared option, so it serialized as a plain Option and mxbuild refused it with CE6084. So the fix is a refusal naming the spelling that works, not a feature. `multiSelect` is now parsed (it was read nowhere), the inline path refuses a flat value, `show design properties` marks such a property instead of listing it identically to a single-select one, and both paths warn at check time. ALTER STYLING genuinely cannot express a compound --- a StylingAssignment carries one flat value and the grammar has no nesting --- so there it refuses and points at the inline form. Controls: `Align self` (same declared control type, not multi-select) still writes a plain option; the compound spelling still writes; a property the theme says nothing about is still written as asked; an empty or absent registry reports nothing at all. Both rule-ID guards --- TestWidgetRuleIDsAreNotReused and TestRuleIDHasOneOwner --- fail when a rule is raised from a second file. MDL-WIDGET11/12 are registered as shared rather than renumbered: someone suppressing MDL-WIDGET11 means both sites. Fixes #509, #511 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LYaTtzjmMCUjo8XAVN1sQx
…estigation-179ttu
…L084)
`raise error;` on a microflow's main flow passed `mxcli check`, was written
by `exec`, and the build then failed with
[error] [CE0710] "The main flow cannot join an error flow or end in an
error event." at Sequence flow
Mendix's error event re-raises the error being handled, so one is legal only
where an error is in scope — inside an `on error { … }` handler. Studio Pro
will not draw the connection from the normal flow to an error event; mxcli
could, and did.
The reported diagnosis (a trailing End event appended because RaiseErrorStmt
never set the builder's "ends with return" flag) is not the cause:
`isTerminalStmt` has treated it as a terminator all along, and the graph mxcli
builds is exactly the one the report asks for — start event, error event, one
sequence flow, no trailing End event, no outgoing flow from the error event.
That shape is now pinned by a test. No wiring makes a main-flow error event
legal, so the fix is a refusal rather than a builder change.
MDL084 walks the main flow — branches, splits and loop bodies included — and
skips `ErrorHandlingClause.Body` subtrees, which is why it cannot reuse
walkBody (that descends into handlers on purpose). Rules go through the same
flow builder and are covered by the same predicate.
Posture follows the mendixlabs#893 CE-gap rules: error severity, so exec's pre-flight
refuses with nothing written, and off execEnforcedMicroflowRules, so
`--no-check` still reproduces the build failure.
Measured on mxbuild 11.13.0, two copies of a blank 11.13.0 app from a 0-error
baseline:
main-flow raise error (the 4 reported shapes) -> 4x CE0710
the same statement inside `on error { … }` -> 0 errors
Control: with the two call sites stubbed out, the new tests fail with the
reported symptom ("main-flow `raise error;` was accepted").
Repro `mdl-examples/bug-tests/microflow-1030-raise-error-main-flow.fail.mdl`,
control `…-main-flow.mdl`.
Fixes mendixlabs#1030
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSAPMZexnVRhe4UYjsz2P5
`mxcli syntax` advertises, in its own help:
mxcli syntax workflow user-task targeting # Drill down to targeting
Handed over as ONE string — a quoted copy-paste, a tool wrapper, `sh -c` —
that answered `Unknown topic: workflow user-task targeting`, spaces and all.
The command joined its arguments on "." and never split them, so the path it
built was the string it was given. The REPL's `help` had resolved multi-word
topics since it was written: one question, two answers, and the CLI held the
weaker copy.
The spaces in the reported message are the whole diagnosis — the command
prints the path it built, and the documented invocation cannot produce one
with spaces in it. Every other line of the report follows: `syntax workflow`
works (one word), `--json` works (a flag is not part of the topic), the three
multi-word forms fail. Reproduced verbatim on the reported v0.20.0 binary and
on main.
The mendixlabs#955 segment-match fallback could not save it either. It passed the
DOTTED path to `BySegmentMatch`, and no segment contains a ".", so it was
silently dead for every multi-word query; it now runs on the last word.
One resolver, `syntax.Lookup`, shared by both surfaces: arguments are split on
whitespace and on ".", then adjacent words are greedily hyphen-joined against
the registry. `workflow user-task targeting`, `"workflow user-task targeting"`,
`workflow.user-task.targeting` and `workflow user task targeting` are now one
query. `resolveHelpPath` is deleted rather than kept in step.
The guard is not the three cases from the report but
`TestEveryRegisteredPathIsReachableBySpelling`: all 164 registered paths,
tried dotted, as separate arguments, and as one string. The registry prints
dotted paths and then tells the reader to drill down with words, so a spelling
that does not resolve is the command contradicting its own output. Control:
stub the whitespace split and it fails with the reported path, spaces and all.
Not widened: the grammar's `helpStatement: IDENTIFIER (identifierOrKeyword)*`
still takes neither a hyphen nor a dot, so `help workflow.user-task` remains a
parse error in the REPL while `mxcli syntax` takes all three.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PuBqbZSg6vuQKQU9RiDn86
…ady model
MDL-WIDGET23 reported `Action:` on a listview / staticimage / dynamicimage
as "Mendix does model one, but mxcli has no writer for it", with a
workaround: wrap the clickable part in a container.
The warning was wrong about the cause, and the gap was one line per widget.
The model already carried the field --- pages.ListView.ClickAction,
pages.StaticImage/DynamicImage.OnClickAction --- and the writers already
serialised it: widget_write.go calls clientActionToGen(x.ClickAction) for
the list view, widget_write_legacy_gaps.go does the same for both images.
Only the builders never called w.GetAction(), so the field was always nil.
Measured on a blank 11.12.2 project:
exec -> Created page MyFirstModule.OnClick
mx check -> The app contains: 0 errors.
describe -> listview lvClick (DataSource: …, Action: microflow …)
re-exec -> Unchanged page
That last line is the evidence that matters: the describe output rebuilds
the stored document exactly, not merely to something that re-checks clean.
The describe half is included because without it the fix is not a fix. The
write half landed on its own first and the action vanished on the next
describe -> exec: valid BSON, clean build, construct silently gone. Adding
the emitter then printed `Editable: true` twice on every list view, since
the shared property formatter already prints it from the same field --- the
parse side's own comment predicted that, and it only surfaced by
round-tripping a page other than the one under test.
Two metamodel-sync tests asserted the gap still existed, and are inverted
rather than deleted so the metamodel half keeps its guard:
TestClickCapableTypesCarryClickActionInMetamodel still requires those three
types to carry a click action in generated/metamodel, and now requires them
to be absent from clickCapableInMendix.
clickCapableInMendix is empty but kept: the category is real and will recur.
A widget Mendix gives a click action and mxcli does not write earns a
different sentence from one Mendix models no click action on at all, because
the remedy differs. The control for that --- a dataview still reporting
MDL-WIDGET23 --- is unchanged and passing.
Part of #512; search attributes and optimize-for remain open there.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LYaTtzjmMCUjo8XAVN1sQx
A List View's "Search attributes" had no MDL spelling: `SearchAttributes:`
on the widget was MDL-WIDGET07, "not recognized and will be silently dropped
on write", so the search bar could only be configured in Studio Pro.
listview lv (
DataSource: database from Mod.Vehicle
where [Brand != '']
sort by Brand asc
search by Brand, Model
) { … }
The syntax mirrors `sort by` --- same position on the database source, same
comma list --- and takes no direction, because a search attribute is only a
name. Reusing the shape is the point: one example teaches both. List view
only, since Forms$ListViewXPathSource is the source type that declares
Search and the grid sources do not.
Pinning the stored shape needed a detour. No marketplace module ships a
populated Forms$ListViewSearch --- all 18 in a blank 11.12.2 app are empty
--- so there is no direct reference. SearchRefs is a list of
DomainModels$AttributeRef, and a Studio Pro-authored Forms$GridSortItem
carries the identical element, so the sibling supplied the reference
(System.Language.Description in Atlas).
That also decided the implementation: reuse attributeRefToGen, the helper
`sort by` already uses. The new list therefore inherits exactly what a sort
bar has always written, including two deviations from Studio Pro that are
consequently not regressions --- typed-array marker 3 where Studio Pro
writes 2, and no `EntityRef: null` key. Both markers are visible side by
side when dumping one project (Studio Pro's 2, mxcli's 3), and compare
CLAUDE.md's note on Forms$Page.AllowedModuleRoles. Changing the shared
helper would alter every existing sort bar, so it is recorded rather than
fixed here.
Full stack, per the checklist: lexer token (and the keyword rule, or
TestKeywordRuleCoverage fails), parser rule, AST field, visitor, semantic
model, builder, writer, and both halves of DESCRIBE.
Measured on 11.12.2:
exec -> Replaced page MyFirstModule.SearchBy
describe -> … sort by Brand asc search by Brand, Model
re-exec -> Unchanged page
mx check -> The app contains: 0 errors.
`Unchanged page` is the evidence that matters: the emitted MDL rebuilds the
stored document exactly, not merely something that re-checks clean.
Not fixed, and measured before concluding it was not a new gap: neither
`sort by` nor `search by` is reference-checked, so a nonexistent attribute
passes `check --references` and reaches mxbuild as CE1613. Both clauses
behave identically.
"Optimize for" (ForceFullObjects) is NOT included --- see #512 for
why the caption-to-value mapping could not be established here.
Part of #512
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LYaTtzjmMCUjo8XAVN1sQx
Found reviewing this branch, in the commit that added `search by`.
The clause hangs off the SHARED database-source grammar rule, so it parses on
a gallery and a data grid too --- and only Forms$ListViewXPathSource declares
Search, so listViewSourceToGen is the only writer that emits it. On a
gallery, measured on 11.12.2:
check -> Check passed!
exec -> Created page MyFirstModule.GridSearch
describe -> gallery gl (DataSource: database from …, DesktopColumns: 2, …)
The clause is gone. A silent drop, shipped by the commit that was fixing
silent drops --- the round trip that proved the feature on a list view said
nothing about the other widgets the rule serves.
Now refused at build, naming the widget that can store it. An error rather
than a warning: unlike an unrecognised property key, which a newer widget
package might legitimately define, this is decided by Mendix's metamodel and
cannot become valid later.
Controls: a list view keeps it, and a widget with no clause is never
refused --- a guard that rejected either would be worse than the drop it
replaces.
Also from the review: astDesignPropToValue had no production caller left
after the multi-select work, surviving only because four tests called it.
Removed, and those tests now exercise astDesignPropToValueChecked, which is
what production calls --- a wrapper kept alive by its own tests proves
nothing about shipped behaviour.
Three patterns added to the review skill's Recurring Findings table: a
clause on a shared grammar rule written by only one of its constructs; a
coverage test that asserts a gap still exists and so fails on the correct
fix; a describe emitter duplicating a field the shared formatter already
prints.
Part of #512
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LYaTtzjmMCUjo8XAVN1sQx
fix: check and exec must read the same widget registry (mendixlabs#1135)
fix(skills): document the lint-rule values the catalog actually emits
…dixlabs#1025) `mxcli syntax workflow` lists its sub-topics as dotted paths — `workflow.user-task` — and the REPL's HELP reads the same registry. Copying a printed path into HELP was a parse error: a hyphen is its own token (HYPHENATED_ID) and a dot is DOT, and `helpStatement: IDENTIFIER (identifierOrKeyword)*` named neither. So the CLI took three spellings and the REPL took one, which is the same split the parent commit closed in the resolver. `help workflow user-task targeting`, `help workflow.user-task.targeting` and `help domain-model.entity` now parse and reach the same page as the words. The rule shape is load-bearing, not tidy. helpStatement is the grammar's catch-all — a statement that is just an IDENTIFIER and some words — so whatever it can swallow, it swallows from the statement that should have had it. With a leading `DOT?` in the loop, `Sec.ApiUser` became a complete statement of its own, and `create module role Sec.ApiUser` parsed, with no parse error to show for it, as CREATE MODULE (named "role") followed by a help topic: two statements, wrong types, six unrelated security tests red. Requiring a topic word before any dot leaves `.ApiUser` unconsumable, so the CREATE MODULE ROLE alternative wins as it did before. Bisected by shape rather than by reading the ATN: the unused rule alone was clean, the hyphen alone was clean, the optional leading DOT was the whole of it. The test that matters is therefore not for the new spelling but for what the rule must still refuse to swallow — TestHelpRuleDoesNotSwallowATrailingQualifiedName pins both statements the regression broke. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PuBqbZSg6vuQKQU9RiDn86
fix(microflow): refuse `raise error;` outside an ON ERROR handler (MDL084)
A Selection helper with `renderStyle: 'custom'` has three mandatory child
slots, and Studio Pro fills each with a Forms$StaticImageViewer. DESCRIBE
PAGE could not spell that widget, so it emitted
-- Forms$StaticImageViewer (imgAll) -- NOT re-executable: mxcli cannot
author this widget, so re-running this script would drop it
and replaying the description emptied all three slots — CE0642 "Property
'All selected' is required." x3. The reporter's workaround was an empty
`dynamictext` per slot: structurally valid, icons gone.
Two independent halves were missing, and the second is the one that hides.
`Forms$StaticImageViewer` had no case in the describe parser or emitter,
even though `staticimage` has long been a keyword the executor dispatches
and both writers serialise. And MDL had no spelling for WHICH image it
shows: staticImageToGen wrote Image as the empty string under a comment
saying so, while pages.StaticImage carried a dead ImageID (model.ID) that
nothing set and that named the wrong thing — Forms$StaticImageViewer.Image
is a by-name reference to Images$Image, so the three-part qualified name
Module.Collection.Image is what is stored.
Emitting the keyword alone would have traded a visible note for a silent
drop, which is the trap #512 and mxcli-formula1 FINDINGS §142 each
cost a round on this same widget. For the same reason the size units and
Responsive are wired through: the writer hardcoded both units to "Auto" and
the builder hardcoded Responsive to true, harmless while nothing described
the widget and a silent normalisation the moment one did.
Also emits a pluggable widget's body when a populated child slot is its only
non-default content — that branch was gated on explicit properties, object
lists and actions, so the Selection helper's slots had nowhere to go.
Measured on a blank Mendix 11.12.1 project, mxbuild 11.12.1:
pre-fix binary, describe -> exec CE0642 x3, the reported failure
fixed, describe -> exec Unchanged page
writer reverted to Image: "" CE0436 "No image selected."
CE0582 ("not supported in React client") is reported for a static image
anywhere on 11.12.1, these slots included. That is Mendix's own deprecation
advice, not an mxcli defect; a new page should use the pluggable `image`
widget, which takes the same `Image:`.
Refs: mendixlabs#1057
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016JgBheMTV6UiQstQ2nLyay
fix(pages): round-trip a static image, reference and all
fix(syntax): a topic resolves however it is spelled (mendixlabs#1025)
AI Code ReviewReview SummaryThis PR contains fourteen commits fixing various issues in pages/widgets, microflows, external actions, lint rules, and documentation. The changes are well-scoped, each addressing a specific problem with validation against real Mendix projects. The PR follows the project's contribution workflow and includes thorough testing. What Looks Good
Minor Issues
RecommendationApprove the PR. The changes are well-tested, properly scoped, address real bugs with verification, and maintain the project's quality standards. The fixes improve consistency between Automated review via OpenRouter (Nemotron Super 120B) — workflow source |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fourteen commits from
ako/mxcli:mainthat are not yet upstream. Each carriesits measurement in the commit body; the grouping below is by what was wrong,
not by file.
Everything here was validated against a real project —
mxcli check,exec,mx checkon a blank Mendix 11.12.x app — and each fix has its controlrecorded (revert the fix, see the reported symptom return).
Pages and widgets
search byon the data source. "Searchattributes" had no MDL spelling at all:
SearchAttributes:on the widget wasMDL-WIDGET07 ("not recognized and will be silently dropped on write"), so the
search bar could only be configured in Studio Pro.
search bywhere it cannot be stored. Found while reviewing thecommit above: the clause hangs off the shared database-source rule, so it
also parses on a gallery and a data grid, where only
Forms$ListViewXPathSourcedeclaresSearchand nothing writes it.checkpassed and the clause went nowhere.
MDL-WIDGET23 called this a missing writer and suggested wrapping the widget
in a container. The warning was wrong about the cause — the model carried the
field and the writers already serialised it; only the three builders never
read the action off the AST.
template for <the list view's own entity>passed check and failed the build with CE0543. Theguard existed and was one case too generous.
Editableis dropped (MDL-WIDGET20). Everyclause after the "but" was wrong, and the suggestion was addressed to a
button — the tell that the branch was written for a different widget.
mxcli checkagreeing withexecexecdoes.check -p --referencesrefusedMDL that
describe pagehad just emitted (htmlelement,attribute,tagcontentcontainer"is not a widget in this project", MDL-WIDGET25, exit 1)while
exec --no-checkwrote it without complaint.value. ALTER STYLING is the one statement whose entire job is writing design
properties, and the one statement MDL-WIDGET11 never looked at — so it passed
check and failed the build with CE6083.
SETdead-ends into. The old message("widget has no pluggable Object") names nothing the author wrote, cannot be
made true by editing the script, and omits the remedy: design properties on a
built-in widget are written by ALTER STYLING.
Microflows
raise error;outside an ON ERROR handler (MDL084). It passedcheck, was written byexec, and the build then failed CE0710 — Mendix'serror event re-raises the error being handled, so one is legal only where an
error is in scope.
Integration
Edm.TimeOfDay, and refuse an external action Mendix cannot call(CE7252 cannot be resolved from mxcli — no command to refresh OData action fingerprints #1089). CE7252 survived DROP + CREATE OR REPLACE MICROFLOW and was reported
as a stale parameter fingerprint needing a REFRESH command. No fingerprint is
stored — Mendix re-derives the alignment from the cached contract on every
build — so the recreation really does rewrite everything, and its failure
means the action is not callable at all.
Catalog and docs
write-lint-rulesskill documentsaction_type/source_typeAPI values that do not exist — rules written from the guide's own examples silently match zero violations #1027).mxcli checkpasses,execfails: native listview design properties are unwritable (false green) #1135 follow-up at the issue that tracks it.