Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a859e78
fix: marketplace update --save-edits misses cross-module associations…
claude Oct 14, 2026
d798d83
fix(pages): describe and address legacy DataGrid columns and search f…
claude Oct 14, 2026
0ae417c
fix(pages): describe and write a layout-grid row's Visible: (mendixla…
claude Oct 14, 2026
c8ba7fb
fix(widgets): native widgets no longer take over image/radiobuttons (…
claude Oct 14, 2026
40a1d6c
fix(executor): MDL055 covers widget datasource XPath (mendixlabs/mxcl…
claude Oct 14, 2026
fc1114f
docs(skills): write the legacy-grid alter example as a full statement…
claude Oct 14, 2026
1641c40
fix(pages): describe and write role-based widget visibility (mendixla…
claude Oct 14, 2026
8759dda
fix(check): resolve ALTER PAGE targets and inserted-widget references…
claude Oct 14, 2026
f82a052
fix(check): resolve DataGrid 2 column attributes against the grid's e…
claude Oct 14, 2026
51738d9
fix(check): resolve the members an access rule's WHERE names (mendixl…
claude Oct 14, 2026
4a3d151
fix(check): judge an access rule's XPath members case-insensitively (…
claude Oct 14, 2026
7d1f5b0
fix(check): resolve an entity event handler's microflow (mendixlabs/m…
claude Oct 14, 2026
0328812
fix(check): resolve enumeration keys named in expressions (mendixlabs…
claude Oct 14, 2026
5fdbbe5
fix(check): resolve the entities a view entity's OQL reads from (mend…
claude Oct 14, 2026
a1025e0
docs(check): record what check -p now resolves, and what it still doe…
claude Oct 14, 2026
4cb7473
Merge pull request #1261 from ako/fix/1459-legacy-datagrid-describe
ako Oct 14, 2026
f2733fe
Merge pull request #1358 from ako/claude/intelligent-archimedes-xlp213
ako Oct 14, 2026
9a35f96
Merge branch 'mendixlabs:main' into main
ako Oct 14, 2026
06f61fd
test(roundtrip): strike the page #1460 lets round-trip (mendixlabs/mx…
claude Oct 14, 2026
43bbb56
docs(skills): write the row-visibility example as a full statement (m…
claude Oct 14, 2026
d68e3f9
docs(skills): keep custom-widgets under the 700-line body cap (mendix…
claude Oct 14, 2026
6a68c8b
docs(skills): move check's resolved-reference list into a reference f…
claude Oct 14, 2026
57905c0
docs(skills): regenerate create-page's line index (mendixlabs/mxcli#1…
claude Oct 14, 2026
3a0a211
test: pin alter-entity on a system-date index end to end (mendixlabs/…
claude Oct 14, 2026
dc4467d
Merge pull request #1364 from ako/claude/mxcli-issue-1451-xtdnvp
ako Oct 14, 2026
2df4bea
ci: Bump actions/setup-java from 5 to 6
dependabot[bot] Oct 14, 2026
0633b97
Merge pull request #1359 from ako/fix/1460-visible-module-roles
ako Oct 14, 2026
46a8e14
Merge origin/main into fix/1461-layoutgrid-row-visible
claude Oct 14, 2026
5c98d27
docs(skills): regenerate alter-page's line index
claude Oct 14, 2026
5e1924f
Merge pull request #1360 from ako/fix/1461-layoutgrid-row-visible
ako Oct 14, 2026
39466bf
Merge pull request #1361 from ako/fix/1463-native-widget-keywords
ako Oct 14, 2026
e977a6c
Merge pull request #1362 from ako/fix/1466-datasource-xpath-two-hops
ako Oct 14, 2026
e50c558
Merge pull request #1363 from ako/fix/1472-check-reference-kinds
ako Oct 14, 2026
b712d9e
Merge pull request #1365 from ako/dependabot/github_actions/actions/s…
ako Oct 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "cmd/mxcli", "date": "2026-10-14", "symptom": "`mxcli marketplace update <id> --to <v> --save-edits <dir>` saves only elements owned by the module being replaced; an association a LOCAL module owns that points at an entity of the marketplace module (e.g. a local Config entity linked to the module's Settings entity) is not saved, the update proceeds without --force because the module itself is unedited, and afterwards the association is gone and Studio Pro reports the missing association (mendixlabs/mxcli#1502).", "cause": "Not merely a save gap: the update DESTROYS the association. `PerformUpdate` runs DROP MODULE, which deletes the module's entities, and `DeleteEntity` (mdl/backend/modelsdk/domainmodel_alter.go) cascades into every other domain model, removing cross-module associations whose TO end names the deleted entity. `SnapshotModule` enumerated only `objects WHERE ModuleName = <module>`, so the comparison, the gate and --save-edits never saw an element stored in another module. Fixed by `Snapshot.Dependents` (catalog `associations` view: other module, FROM/TO prefixed `<module>.`, matched with substr since `_` is a LIKE wildcard), carried as `Report.Dependents`, saved as `association-<Module>_<Name>.mdl`, counted by `gateOnLocalEdits`, listed in `marketplace diff` and the update summary.", "file": "cmd/mxcli/marketplace/snapshot.go", "insight": "The question to ask of any replace-a-module path is not 'what does the module contain' but 'what does deleting it cascade into' \u2014 read DeleteEntity's cascade before trusting a module-scoped enumeration. The cheapest proof is a two-line characterisation test (create the cross association, PerformUpdate, DESCRIBE it fails) on the vendored expr-checker fixture, which has MyFirstModule beside Administration; it doubles as the control that something was at risk. Keep dependents OUT of LocallyModified: they are not edits to the marketplace module, and reporting them as such would make `diff` accuse the module of a change the user made elsewhere \u2014 gate on them separately. The saved replay creates the association with a new GUID, so link data is not guaranteed to survive; preserving the association's raw BSON across the drop is the real fix and is not done here.", "refs": ["mendixlabs/mxcli#1502"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/backend", "date": "2026-10-13", "symptom": "describe printed a legacy Forms$DataGrid as one `-- Forms$DataGrid (grid1) -- NOT re-executable` line, so its columns and search fields had no names in the output; `alter page … drop column14` passed check -p --references and failed at exec with `widget \"column14\" not found`", "cause": "findInWidgetChildren walked the grid's ControlBar.Items but never its Columns (Forms$GridColumn) or DataSource.SearchBar.Items (Forms$*SearchField), although both carry a stored Name; parseRawWidget had no Forms$DataGrid case so describe never read them either", "file": "`mdl/backend/pagemutator/legacygrid.go` (`legacyGridItemLists`, used by `findInWidgetChildren` and `collectWidgetScopeInChildren`), `mdl/executor/cmd_pages_describe_legacy_grid.go`", "insight": "Unlike DataGrid2 columns (derived names, #78), legacy grid columns and search fields are NAMED — the gap was only which lists the finder walks, so the existing drop/set-Caption paths work unchanged once they are reachable. An unauthorable widget can still be printed structurally if every line stays a comment: exec of the output then drops the grid (as before) instead of building something different. Gate the walk on $Type: a LayoutGrid row also has a `Columns` key. No fixture holds a legacy grid (pedapp/testapp grep for Forms$DataGrid is empty), so tests build Studio Pro-shaped BSON from the property names in modelsdk/gen/pages", "refs": ["mendixlabs/mxcli#1459"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"date": "2026-10-14", "area": "mdl/backend", "symptom": "`alter entity … add attribute` on an entity with a Studio Pro index over (createdDate, changedDate) rewrote each segment as `Type = Normal` with still no attribute pointer; mx check then crashed with ArgumentOutOfRangeException at IndexedAttribute.get_Attribute instead of reporting a CE (mendixlabs/mxcli#1451, reported on v0.24.0 / 0.26.0, Mendix 11.12.5).", "cause": "Same defect as #1401, already fixed on main when #1451 was filed: indexFromGen dropped IndexedAttribute.Type and indexToGen hard-coded \"Normal\". #1401's tests pinned the converter pair and describe, not the reported ALTER sequence through the real write path.", "file": "mdl/backend/modelsdk/issue1451_alter_system_index_test.go (regression only; fix in domainmodel.go indexFromGen / domainmodel_write.go indexToGen from #1401)", "insight": "Check whether the report predates a fix before writing one: the issue quoted main's code, but a grep of the cited line (`seg.SetType(\"Normal\")`) on the current tree showed it already gone, and the real repro on a blank 11.12.5 app passed. What earned its place was the end-to-end test: seed the Studio Pro shape by patching the unit below the writer (Type CreatedDate/ChangedDate, zero AttributePointer), alter through UpdateEntity, read the raw segments; reverting either half of #1401 makes it fail with exactly the reported `Type = \"Normal\"`. Real run, both variants: fixed binary 0 errors; binary with indexToGen forced to Normal reproduces the get_Attribute crash. Do not chase the owner/changedBy half of the title: the Model SDK (mendixmodelsdk 4.116.0) does add IndexedAttributeType Owner/ChangedBy/Association and an `association` reference (AssociationPointer) in 11.11.0, absent from our gen, and a rewrite does zero a stored AssociationPointer — but mx 11.12.5 AND 11.14.0 crash in Index.Check -> get_Attribute on ANY segment of those types (zero pointer, a dangling one, or one naming a real association in the unit), so no project holding one passes mx check today; carrying the pointer protects a shape with no checkable instance. Revisit when a Studio Pro-authored owner/association index exists to measure. The domain-model unit stores entities as DomainModels$EntityImpl, so a raw patch matching `DomainModels$Entity` silently finds nothing.", "refs": ["mdl/backend/modelsdk/issue1451_alter_system_index_test.go", "mdl-examples/bug-tests/1451-alter-keeps-system-date-index.mdl", ".claude/skills/fix-issue/findings/mdl-executor/2026-10-12-describe-index-on-system-changed-date.json", "docs-wiki/bug-patterns/rewrite-drops-unauthored-state.md"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"date": "2026-10-13", "area": "mdl/executor", "symptom": "`mxcli check -p --references` printed \"All references valid\" for seven kinds of wrong reference that exec then refused or mxbuild reported (mendixlabs/mxcli#1472): an ALTER PAGE INSERT/REPLACE/DROP target widget and the flows/pages its inserted or replacing widgets name (exec: widget/microflow not found); a DataGrid 2 column Attribute (CE1613); an access rule's WHERE path (CE1613 at the access rule); an entity event handler's microflow (exec: microflow not found); `Module.Enum.Key` in an expression (CE1613 \"The selected enumeration value … no longer exists\"); and a view entity's FROM/JOIN entity (CE0174 \"Cannot resolve object name\").", "cause": "Each statement's reference check stopped at the document or entity level and never looked inside: ALTER PAGE resolved only its document, validateWidgetReferences ran only for CREATE page/layout/snippet, the column scope rule asked only whether a name belonged to ANOTHER entity, grantResolver never parsed the WHERE, neither entity statement looked at its handlers, exprcheck read enum values but never asked whether the key exists, and an unknown OQL source just made its columns untyped (and untyped columns are skipped).", "file": "mdl/executor/validate_alter_page_refs.go; mdl/executor/validate_datagrid_column_attrs.go; mdl/executor/validate_grant_refs.go (xpathProblem) + validate_retrieve_members.go (scriptXPathResolver); mdl/executor/validate_entity_event_handlers.go; mdl/executor/validate_enum_value_refs.go; mdl/executor/validate_view_source_entities.go; wired from mdl/executor/validate.go", "insight": "Probe every sibling: the reported kinds were seven faces of one shape — a check that resolves the container and not its contents — and each was closed by reusing what exec or a neighbouring rule already runs (exec's AlterTargetResolver against the stored page, validateWidgetReferences, the retrieve check's XPath walk, extractAliasMap) rather than restating it. Two measurements changed the plan: a DataGrid 2 header filter's Attribute is never written by the column builder (describe prints a bare `textfilter`, mxbuild is silent), so refusing a misspelt one would refuse a script that builds; and pedapp sits at security level Off, where mxbuild skips access rules entirely — the access-rule CE1613 only appears after `alter project security level prototype`. The pedapp control for the grant WHERE also caught a latent false refusal in the shared XPath resolver: an entity the script ALTERs was judged from its stored attribute list.", "refs": ["mdl/executor/validate_alter_page_refs_test.go", "mdl/executor/validate_datagrid_column_attrs_test.go", "mdl/executor/check_grant_xpath_pedapp_test.go", "mdl/executor/validate_entity_event_handlers_test.go", "mdl/executor/validate_enum_value_refs_test.go", "mdl/executor/validate_view_source_entities_test.go"], "ce": ["CE1613", "CE0174"], "rules": []}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-13", "symptom": "mendixlabs/mxcli#1461: DESCRIBE printed a Studio Pro layout-grid row with a visibility condition as a bare `row { … }`, and `row (Visible: …)` passed check and exec and was not written; describe -> exec (create or modify / replace) of such a page silently made every conditional row always visible, mx check clean. Visible: on a layout-grid column was dropped too, without a warning.", "cause": "Same shape as the tab page (#1465): a row is built by buildLayoutGridV3/buildLayoutGridRowV3, not buildWidgetV3, so the visibility tail never ran; sdk/pages.LayoutGridRow had no field; layoutGridRowToGen never set it, so the registered NullFields default wrote null over the stored condition on every rewrite; parseLayoutGridRows never read it. A column has no slot at all: gen Forms$LayoutGridColumn has no ConditionalVisibilitySettings and 0 of 732 Studio Pro columns (TestApp + PedApp) carry the key, while 483 of 483 rows do.", "file": "sdk/pages/pages_widgets_container.go (LayoutGridRow.ConditionalVisibility); mdl/executor/cmd_pages_builder_v3_layout.go (childVisibility, shared with buildTabPageV3; top-level row leaves it to the wrapping container); mdl/backend/modelsdk/widget_write.go (layoutGridRowToGen); mdl/executor/cmd_pages_describe_parse.go (parseLayoutGridRows -> extractConditionalSettings); mdl/executor/validate_layoutgrid_column_visible.go (MDL-WIDGET07 warning for a column inside a row)", "insight": "Scan the fixtures' stored BSON for the key per $Type before deciding row vs column: one Go walk over mprcontents settled it (rows 483/483 have the key, columns 0/732) where the issue asked for both. TestApp's only conditional row (WorkflowCommons.Snip_DashboardContext_Timeline) sits inside a pluggable timeline slot that is passed through, so the TestApp round trip never saw the loss and ALTER (raw mutator) preserves it too — the destruction is in the rebuild path only; reproduce it by writing a conditional row with the fixed binary and re-running describe -> exec with the old one (Replaced, conditions gone) vs the new one (Unchanged). A top-level `row` is a container around a one-row grid; buildWidgetV3 already puts Visible: on the container, so the row must not carry it a second time. Note MDL-WIDGET43 (datagrid column $currentObject) also fires on a layout-grid `column`, labelled datagrid column.", "refs": ["mendixlabs/mxcli#1461", "mendixlabs/mxcli#1465", "docs-wiki/bug-patterns/silent-property-drop.md"], "test": "mdl/executor/cmd_pages_layoutgrid_row_visible_test.go (TestLayoutGridRow_VisibleIsWritten, TestLayoutGridRow_TopLevelRowVisibleOnContainerOnly, TestDescribe_LayoutGridRowVisible, TestCheck_LayoutGridColumnVisibleDropped); mdl/backend/modelsdk/widget_write_layoutgrid_row_visibility_test.go (TestLayoutGridRow_WritesConditionalVisibility)"}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-13", "symptom": "with native mobile widget packages installed, `check` reports `image img1 (Image: …)` \"has no property Image [MDL-WIDGET01]\" and validates `radiobuttons` against the native RadioButtons, while exec writes a Forms$RadioButtonGroup", "cause": "DeriveMDLName took only the last id segment, so `widget init` wrote image.def.json / radiobuttons.def.json for com.mendix.widget.native.*; the registry let a user def of a DIFFERENT widget id replace a built-in name (info log only), and check resolves keywords through the registry while the builder's switch owns `radiobuttons` before any lookup", "file": "`mdl/executor/widget_registry.go` (`loadDefinitionsFromDir`), `widget_keywords.go` (`builderOwnedWidgetKeywords`), `validate_widgets.go` (`lookupWidgetDef`), `widget_defs.go` (`DeriveMDLName`)", "insight": "Fixing only the init-time name leaves the bad files already on users' disks, so the guard has to sit where definitions are loaded: refuse a different-id def under a built-in or builder-owned keyword, keep it reachable by id. The builder-owned keyword set is pinned to buildWidgetV3's switch by a go/ast test, so check and exec cannot drift again. DESCRIBE needed nothing: pluggableWidgetHeader already round-trips the name through Get and falls back to `pluggablewidget '<id>'`", "refs": ["mendixlabs/mxcli#1463"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-13", "symptom": "Role-based widget visibility (ConditionalVisibilitySettings.ModuleRoles) is not printed by DESCRIBE, so a `create or replace` from describe output silently drops it — the widget becomes visible to every role, with mx check clean", "cause": "Four gaps on one property: `extractConditionalSettings` read Expression and Attribute/Conditions and never ModuleRoles; MDL had no spelling for it; `conditionalVisibilityToGen` always wrote the list empty (and `pages.ConditionalVisibilitySettings.ModuleRoles` was an unused `[]model.ID` though the property is BY_NAME); ALTER `set Visible` rebuilt the settings node from scratch, dropping stored roles. Navigation-list items read and wrote no visibility at all", "file": "`mdl/executor/cmd_pages_describe_parse.go` (`extractConditionalSettings`, `parseNavigationListItems`), `mdl/executor/cmd_pages_builder_visible_when.go` (`applyVisibleRoles`/`withVisibleRoles`), `mdl/backend/modelsdk/widget_write.go` (`conditionalVisibilityToGen`, `navListItemToGen`), `mdl/backend/pagemutator/mutator.go` (`setWidgetConditionalSettingMut`), `mdl/executor/validate_grant_refs.go`, `MDLPage.g4` (`Visible: roles (…)`)", "insight": "The roles are a SEPARATE setting from the condition, stored on the same element: TestApp's Studio Pro-authored WorkflowCommons snippets carry Expression + ModuleRoles [1, \"WorkflowCommons.Administrator\"] on one node, and Attribute + Conditions + ModuleRoles on another, and roles alone (Expression \"\") on a third — so the MDL is a second `Visible:` line, not a variant of the first, and every path that rebuilds the node (builder tail, ALTER SET, tab page, nav item) must carry it. A BSON scan of mprcontents for non-empty ModuleRoles settled the shape in minutes; the roundtrip allowlist's 'a module-role visibility drifts' note pointed at the sample. mx 11.13 resolves the names: a dangling role is CE1613 \"The selected module role … no longer exists\" at the widget (positive control), valid roles add no error (1002 = control). Revert controls: stubbing the describe read, the writer and the builder each fails its test with the reported drop", "refs": ["mendixlabs/mxcli#1460"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-13", "symptom": "A widget database datasource `where [Name = $currentObject/Mod.Assoc/Name]` passes `mxcli check` and `exec`, then `mx check` reports **CE0161** \"Error(s) in XPath constraint.\" at the widget (combobox, list view, data grid 2, gallery). Reported as the ONE-hop form `[Mod.District_Region = $currentObject/Mod.AreaPicker_Region]` on a combo box", "cause": "MDL055 (#831) ran only over microflow retrieve constraints; the page side had MDL047 (`= empty`) but no hop-count check, so the two-hop comparand reached mxbuild", "file": "`mdl/executor/validate_widgets.go` (`validateDatasourceXPathVariableTraversal`), `mdl/executor/validate_microflow.go` (`xpathVariableTraversals`, shared with the retrieve rule)", "insight": "**The reported form was valid: measure the issue's own script before writing the rule it asks for.** On fresh 11.13.0 and 11.14.0 projects the one-hop `$currentObject/Mod.Assoc` comparand built at 0 errors in a combobox (`Association:` and `Attribute:` spellings), list view and data grid 2, and is stored verbatim (bson dump), so neither a refusal nor a writer fix was right for it; a rule flagging one hop would refuse valid MDL. The reduced script's only errors were CE0642 \"Property 'Caption' is required\", one per combo box, because it omits `CaptionAttribute:`. Shapes that DID give CE0161 on 11.14.0 with check passing, one page each in one project: two hops off `$currentObject` (all four list widgets; now MDL055), a one-hop comparand whose target entity differs from the left side's (`[Mod.District_Region = $currentObject/Mod.AreaPicker_District]`), and an UNQUALIFIED association in the comparand (`$currentObject/AreaPicker_Region`). The last two are still unflagged and are candidates for the reporter's real app. Do not measure on testdata/pedapp: its stale widget definitions turn every new combobox into CE0642 Attribute/Values/Caption noise; `mxcli new --theme none --layout none --skip-init --skip-build` gives a 0-error control.", "refs": ["mendixlabs/mxcli#1466", "#831"], "ce": ["CE0161", "CE0642"]}
28 changes: 20 additions & 8 deletions .claude/skills/mendix/alter-page/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ description: "Modify an existing page or snippet's widget tree in place with ALT
| 28-31 | Overview |
| 32-56 | When to Use |
| 57-107 | Syntax |
| 108-460 | Operations |
| 461-539 | Examples |
| 540-630 | DataGrid 2 columns: how to address them, and what you can set |
| 631-642 | Common Mistakes |
| 643-664 | Limitations — prefer binding at page creation (ledger finding #45) |
| 665-672 | Validation Checklist |
| 673-682 | Related Commands |
| 683-687 | Related Skills |
| 108-472 | Operations |
| 473-551 | Examples |
| 552-642 | DataGrid 2 columns: how to address them, and what you can set |
| 643-654 | Common Mistakes |
| 655-676 | Limitations — prefer binding at page creation (ledger finding #45) |
| 677-684 | Validation Checklist |
| 685-694 | Related Commands |
| 695-699 | Related Skills |

<!-- /skill-index -->
# ALTER PAGE / ALTER SNIPPET - Modify Existing Pages and Snippets
Expand Down Expand Up @@ -401,6 +401,18 @@ drop dgProducts column(Name)@2

The older dotted form `gridName.columnName` still works; it matches a name mxcli derives (the short attribute name, else the sanitized caption, else `colN`).

A **legacy (Dojo) data grid** (`Forms$DataGrid`, from an upgraded app) cannot be created in MDL, so `describe page` prints it as comments — but it lists every column, search field and control-bar button by its *stored* name. Those are addressed by that bare name, like any widget:

```sql
mdl 1;
alter page MyMod.Order_Overview {
drop column14, searchField3; -- one column and its search field
set (caption: 'Note') on column2
};
```

Only that item is removed or changed; the rest of the grid is left as stored. To restructure the grid itself, replace it with a DataGrid 2.

### ADD Variables - Add a Page Variable

```sql
Expand Down
Loading
Loading