Skip to content

Sync ako/mxcli: page properties on Mendix 9.x/10.x, workflow button actions, pluggable widget events; recovering old-mxcli merge bases - #1671

Merged
ako merged 23 commits into
mendixlabs:mainfrom
ako:main
Oct 28, 2026
Merged

ako merged 23 commits into
mendixlabs:mainfrom
ako:main

Conversation

@ako

@ako ako commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator

Syncs ako/mxcli:main into upstream: 11 commits from fork PRs ako#1758 and ako#1759.

Page properties lost on rewrite (ako#1759)

These are three failures of the same kind: a page property that describe didn't print, or that the writer rebuilt, so a describe → exec round trip lost it. Each commit does one thing and has a test written first, a control, a finding, a bug-test script, and a real mx check of both the fixed and the fault variant.

Mendix 9.x / 10.x page shapes

Five page properties kept their meaning but changed storage shape at a version, and mxcli read and wrote only the newer shape. The older shapes were measured on a Studio Pro-authored 9.24.30 app:

Property Shape below the version Version
design property flat Type/StringValue/BooleanValue 10.2
data view source parameter PageParameter/SnippetParameter names 10.0
data view editability Editable bool 10.7
text box/area/date picker placeholder Placeholder (Texts$Text) 10.11
a data source's ForceFullObjects not stored 10.8

What this broke on 9.24:

  • describe printed none of them, so a round trip removed them.
  • Writing a page with a data view or a design property was refused.
  • alter styling on a stored 9.x design property reported success and changed nothing.

The fix: a new canon.LowerToProjectShape runs on every unit write, before the property floor and Reconcile. So every writer gets the project's shape.

A text box's KeyboardType now survives describe → exec as well. A rebuilt native text box used to lose it.

Workflow button actions

The workflow button actions now have an MDL spelling, so a rewrite keeps them. Re-running an unchanged describe page WorkflowCommons.TaskInbox used to report "Replaced page" and leave 4 of 6 Forms$OpenUserTaskClientAction.

A rewrite keeps a widget's stored schema, and the docs say to update it with fix widgets.

Pluggable widget events and enumerations

  • Action slots the built-in definitions left out are now written and described. These are:

    • a data grid filter's OnChange:;
    • a barcode scanner's onDetect:;
    • a combo box's enter, leave and filter-input events.

    Before, they were refused under mdl 1; and dropped with a warning under mdl 0.

  • alter page … set (Action: …) reaches a pluggable widget's click slot, such as a gallery's. It used to be refused. MDL-WIDGET36 now applies to ALTER too.

  • An enumeration value is written as the key the widget declares. For example, onClickTrigger: Double is stored as the declared key, and one the widget doesn't declare is refused. Before, the value was stored exactly as the script spelled it.

Docs: recovering a merge whose base an older mxcli wrote (ako#1758)

The problem: a Studio Pro rebase failed with "Objects with ID … of type DomainModels$AccessRule do not have the same properties", and afterwards the project wouldn't reopen.

The cause: the root cause is #1373, which was fixed in v0.26.0, but the merge base holds an access rule that a pre-0.26 mxcli wrote. fix property-sets, like any Studio Pro save, only completes revisions from its own commit on.

What's documented:

  • docs-site/src/tools/outside-studio-pro.md explains that the repair reaches forward, not back, and gives five steps to get past a merge that already fails.
  • fix property-sets --help gives the same advice in short form.

How it was validated: with mx merge, which runs Studio Pro's merge engine, on mxbuild 11.13.0:

  • The reported exception reproduces before the steps, and is fixed after them.
  • A revision written by v0.26.0 is the control.

Also

  • The changelog has entries for these fixes.
  • The platform-CI wiki pattern page names version-shape lowering

claude and others added 23 commits October 28, 2026 10:07
…ets)

`mxcli fix property-sets`, like any Studio Pro save, completes property
names only from its own commit on. A merge or rebase whose merge base is
an earlier revision written by mxcli before 0.26 still throws "Objects
with ID … do not have the same properties" on every document that was
incomplete there and that both branches changed. A customer on 11.12.2
hit this during a rebase and could not reopen the project, because
Studio Pro re-runs the unfinished merge on open.

The user manual (Working Outside Studio Pro) and the command's help now
say that the repair only works forward, that it should run once on the
main branch, and how to get past a stuck merge: abort it; find the
units with --dry-run on the merge base; put the ones both branches
changed back to the merge base on your branch (with fix hashes --repair
for the .mpr index); merge rather than rebase; redo the edits. They
also warn against re-creating an entity, attribute or association,
whose new GUID costs its data.

Measured with `mx merge` (Studio Pro's merge engine) on 11.13.0, using
testdata/pedapp and an access rule written by a v0.25.0 build:
- before the steps, exit 129 with the customer's exception
- with the repaired side against the old base, also exit 129
- after the steps, exit 0; the merged project keeps both branches'
  other edits, and `mx check` matches the untouched fixture

Taking one side of the conflicted files at the git level does not work
in general: the v2 .mpr indexes every unit and conflicts too.

Records the diagnosis as a finding (refs mendixlabs#1373,
#1097).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82tNcDLefAskVSzcKPm
"When a native child widget is rebuilt, its KeyboardType and Placeholder
are lost, for example on text boxes in the native snippets."

Forms$TextBox.KeyboardType is the keyboard a native app opens for the
box. Its members are Default, NumberPad, DecimalPad, EmailAddress,
PhonePad and URL (mendixmodelsdk, declared from 8.0). It had no MDL
spelling: describe printed nothing and the writer wrote "Default". So a
rewrite reset every native text box's keyboard, on every version.

Now `textbox t (Attribute: Email, KeyboardType: EmailAddress)`:
- Members are matched case-insensitively and stored as Mendix spells
  them.
- Describe omits Default.
- An unknown member is refused by exec, and by check as MDL-WIDGET18.
- The key is a text box's only. It is exempted per type, like the
  submit keys, so it still warns on any other widget.

The placeholder half of the report is a version-shape gap, fixed in the
next commit.

Verified: builder, writer-facing model, describe and check tests, each
red before the change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
"describe drops widgets' design properties on 9.24, so a describe → exec
round trip removes them". Rebuilt text boxes lost their placeholder too.

Five page properties kept their meaning and changed storage shape at a
version. mxcli read and wrote only the newer shape. Measured on the
pages of a Studio Pro-authored 9.24.30 app and in mendixmodelsdk:

| Property                     | Older shape                          | Until |
|------------------------------|--------------------------------------|-------|
| design property              | Type/StringValue/BooleanValue flat   | 10.2  |
| data view source parameter   | PageParameter/SnippetParameter names | 10.0  |
| data view editability        | Editable bool                        | 10.7  |
| text box/area/date placeholder | Placeholder Texts$Text             | 10.11 |
| data source ForceFullObjects | not stored                           | 10.8  |

The effects on 9.24:
- describe printed none of these, so a round trip removed them. On
  Home_Web it printed "Replaced page" and changed the bytes.
- Any page with a data view or a design property was refused at
  write.
- The placeholder, the data view source's parameter and
  ForceFullObjects are declared on abstract supertypes. The property
  floor cannot see those, so the newer key passed unrefused and the
  older key was never written. The data view source showed up as
  CE6050.
- alter styling on a stored 9.x entry reported "Updated styling" and
  changed nothing. It updated through DSet("Value"), which does nothing
  when the key is missing.

The fix:
- canon.LowerToProjectShape runs in normalizePropertySetsFor, the hook
  every unit write passes, before the floor and before Reconcile. It
  rewrites the current shape into the project's, so every writer is
  covered, ALTER and pluggable templates included. A lowered rewrite of
  a stored unit still elides.
- What the older shape cannot hold is refused, naming the widget:
  - Spacing, colour or toggle-group design properties;
  - conditional editability;
  - placeholder parameters;
  - a data view fed by another widget.
- describe reads both shapes.
- The mutator rebuilds the entry it sets, keeping its $ID.

Measured on fresh 9.24.30 apps (mxbuild 9.24.30.51827):
- bug-test 1209-mendix9-page-shapes.mdl: mx check 0 errors, the same as
  the blank app. main stops on KeyboardType; without it, main refuses
  the page for Editability and DesignPropertyValue.Value.
- describe → exec of Studio Pro's Home_Web: "Unchanged page", bytes
  identical. On main: "Replaced page", design properties removed.
- describe → exec of the new page: "Unchanged page", bytes identical.
- alter styling on Home_Web's text2 stores "Header color". On main the
  entry stays "Detail color".
- Doctype gate on 9.24: 12-styling passes. 15-fragment's data views
  write, then stop on CE0560. 29-datagrid stops on
  MicroflowParameterMapping.Expression. Both of those are other #1163
  rows.
- On 10.24 and 11.13, where nothing is lowered, 03-page, 12-styling,
  15-fragment and 29-datagrid pass.

Tests (each red first): writer_version_shape_test.go, covering 9.24,
the boundaries either side of 10.2/10.7/10.11, the refusals and an 11.13
control; cmd_pages_describe_mendix9_shapes_1209_test.go, with a
10.11-shape control; TestSetDesignProperty_OverwritesAMendix9Entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
Two fixes in a row (#1754, #1209) had the same shape: one fact
stored differently below a version. The floor sees keys, not facts, and
misses supertype-declared ones. The page now says where the cover-all
remedy sits (canon.LowerToProjectShape), and where to get the older
shape's real bytes.

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

Symptom: with the pluggable patch from df82f53 in place, re-running
`describe page WorkflowCommons.TaskInbox` unchanged on ako/TestApp
(11.15.0-rc.4) still reported "Replaced page" and left 4
Forms$OpenUserTaskClientAction where there had been 6: the link button in
the last column of the "My open tasks" and "All open tasks" grids, which
opens the task, became a Forms$NoAction. TaskDashboard lost the same two.
check, exec and mx check were all clean.

Cause: describe had no case for Forms$OpenUserTaskClientAction ("Show user
task page"), Forms$OpenWorkflowClientAction ("Show workflow admin page") or
Forms$CallWorkflowClientAction ("Call workflow"), so it printed no Action,
and a classic widget is rebuilt from the statement: no Action builds a
NoAction. The grids' own row click survived only because the patch keeps a
property both builds agree on; a column's content always takes the
statement's build. `complete task` also rebuilt ClosePage and Commit as true
whatever was stored, and describe printed neither.

Fix: spell them in the words the microflow actions use, with their settings
in `with ( … )`:
  open user task [with (AssignOnOpen: false, OpenWhenAssigned: true)]
  open workflow with (DefaultPage: Module.Page)
  call workflow Module.WF [with (ClosePage: false, Confirmation: '…')]
  complete task 'X' [with (ClosePage: false, Commit: false)]
describe prints each, and only the settings that differ from a new action
(the Model SDK's constructor defaults). A setting value may now be a
qualified name. DefaultPage is required: `open workflow` without it is
CE1834, so the visitor refuses it and describe prints a stored page-less
one as a NOT re-executable note. Commit is never written on a workflow
call (Mendix 9.12 removed it); AssignOnOpen/OpenWhenAssigned are left
unwritten below 9.20, which introduced them.

Measured:
- Tests seed Studio Pro-authored actions with non-default flags; with the
  describe case stubbed back out they fail with the symptom
  ($Type = Forms$NoAction, AssignOnOpen = <nil>).
- TestApp round trip of TaskInbox/TaskDashboard/ConflictedWorkflowDefinition
  View: OpenUserTaskClientAction 6 -> 6 on both (fault-forced build: 6 -> 4).
  mx check 11.15.0-rc.4: control 3 errors (CE0067 x3), fixed 3, fault 3 —
  mx check cannot see a lost action, the BSON count is the evidence.
- Fresh 11.13.0 app, bug-test exec'd: mx check 0 errors; describe -> exec of
  all five pages "Unchanged page"; `mx convert -p` of a copy re-serializes
  all 8 written actions to the same key set and values (key-sorted), so the
  property sets are Studio Pro's. Without DefaultPage: CE1834; outside a
  WorkflowUserTask / Workflow data container: CE1843.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
…th fix widgets (#832)

widget's stored schema, as Studio Pro does, instead of upgrading it to the
installed .mpk, and users need to know what upgrades it instead.

The guide already said a rewrite keeps the schema "until you sync it", but
its remedy predated `mxcli fix widgets`: it pointed at the partial
`mxcli widget sync`, at Studio Pro, and warned off `mx update-widgets`.
`mxcli fix widgets` runs Mendix's own `mx update-widgets` and restores the
MPR v2 layout (its --help, and the marketplace guide's "Repairing the
model"); `widget sync` clears 7 of 40 CE0463 on the reference fixture by its
own --help. The guide now leads with `fix widgets`, says that earlier beta
builds upgraded widgets as a side effect of a rewrite (the template is
reconciled against the installed .mpk, per the guide's own mechanism
section) and that a passing `docker check` does not prove the stored
project clean. The create-page skill gets the same one-paragraph rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
…tions left out (#854)

Symptom: an action on a data grid filter (`OnChange:` on a text, number,
date or drop-down filter), on a barcode scanner (`onDetect:`) or on a combo
box's onEnterEvent / onLeaveEvent / onChangeFilterInputEvent was refused
under `mdl 1;` and dropped with a warning under mdl 0. On an 11.13.0 app the
bug-test page gave "9 issues: 4 errors, 5 warnings" (4x MDL-WIDGET37 for the
filters, 4x MDL-WIDGET06 for the named slots); without the header it was
written with every action missing. describe printed no filter action, so a
Studio Pro-authored one could not be restated.

Cause: these widgets' definitions are hand-written and embedded
(sdk/widgets/definitions), and a hand-written definition beats the
generator, which maps every action slot of a generated widget. Theirs had
no mapping for these slots, so nothing read the action. Describe's filter
branch read no action, and its known-type output branch printed no named
slots.

Fix: the four filters map `onChange` from `OnChange:` (in every mode of the
drop-down filter); the barcode scanner maps `onDetect` and the combo box
`onEnterEvent` / `onLeaveEvent` / `onChangeFilterInputEvent` by their own
keys, in all three combo box modes — the spelling the generator gives the
same slots. Describe reads them back. A slot a definition sources from
`OnChange:` under a key the generic matcher does not know (the
database-mode `onChangeDatabaseEvent`) is not also printed by key.

The issue's second bullet — a Studio Pro-authored filter onChange lost when
its widget is rebuilt — already held on main through the per-property patch
of #832: seeding a real Forms$MicroflowAction into a stored filter,
then rewriting the page from describe with the filter edited, keeps it
(TestIssue854_EditedFilterKeepsItsStoredOnChange). Control: stubbing
patchStoredPluggable fails it with "editing tfTitle reset its onChange to
Forms$NoAction"; the sibling-edit variant fails only with the patch AND the
passthrough stubbed. What this change adds is that describe can say the
action, so a statement can keep, retarget or remove it. The combo box's
onChangeDatabaseEvent was already mapped by mendixlabs#1583.

Measured (Mendix 11.13.0, mxcli new app, mx check --no-update-widgets and
with the widget update): all eight actions stored, 0 errors, describe ->
exec "Unchanged page". Fault variant (definitions and describe at HEAD):
refused under mdl 1, 0 errors under mdl 0 with the actions missing.

TestBuiltinDefsMapEveryActionSlot audits every embedded definition against
the action properties its .mpk declares, and requires a by-key slot in
every mode, so the next unmapped slot fails a test rather than a user.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
… slot, and MDL-WIDGET36 applies to ALTER (#854)

Symptom: `alter page … set (Action: call nanoflow M.Open) on gallery1` was
refused — "widget "gallery1" (CustomWidgets$CustomWidget) has no on-click
action — Action can only be set on a widget that has one: a button, a
container, a list view or an image" — while `set ('onClick': …)` wrote the
very same slot. And no ALTER spelling applied MDL-WIDGET36: on an 11.13.0
app, `mdl 1; alter page … set ('onClick': …) on gSingle` (default
Selection Single, trigger single) reported "Altered page", and mx check
then failed: "The item click action is ambiguous. Change "On click
trigger" to "Double click" or set "Selection" to "None"." at Gallery
'gSingle' — 1 error.

Cause: ALTER routed the `Action` key to SetWidgetAction, which writes a
built-in widget's own click property (Action / OnClickAction /
ClickAction). A pluggable widget has none; its click is the action-typed
property its definition reads `onClick:` from. MDL-WIDGET36 lived only in
the CREATE validator, which reads the statement's widget tree — a SET
carries no widget, and may name only one of the three settings the
Gallery's own rule turns on.

Fix: `Action:` and its alias `onClick:` go to the stored pluggable
widget's click slot (the one action property actionSourceForKey reads
`onClick:` from), and keep SetWidgetAction on a built-in widget; a
pluggable widget without a click slot is refused naming the action slots
it has. After a SET that changes a gallery's click, selection or trigger,
the stored gallery is judged as the SET leaves it, with the Gallery
editorConfig's own condition (`"None" !== itemSelection && onClick &&
"single" === onClickTrigger`): refused under `mdl 1;` before the page is
saved, written with the MDL-V1-GALLERYCLICK warning under mdl 0 — what
`create page` does. check predicts both on a probe copy of the stored
page. The page mutator gains a PluggableWidgetState capability that
reports what a stored pluggable widget holds.

Measured (Mendix 11.13.0, mx check --no-update-widgets): `set (Action:
…)` on a Selection None gallery, and `set (onClickTrigger: 'double',
Action: …)` on a single-selection one, both under `mdl 1;` — 0 errors;
the ambiguous `set (Action: …)` under `mdl 1;` — refused, nothing
written; the same under mdl 0 (fault forced in) — written with the
warning, and mx check reports the ambiguity, 1 error. Pre-fix binary:
both legal SETs refused ("has no on-click action"), the quoted form
written without MDL-WIDGET36 and mx check failing as above.

Tests: TestIssue854_AlterSetActionOnGallery (Action / onClick /
'onClick'), _AlterSetActionControls (a button keeps its setter, a combo
box is still refused), _AlterSetGalleryClickAmbiguity (mdl 1 refuses both
spellings, mdl 0 writes and warns, Selection None control),
_CheckAlterSetGalleryClickAmbiguity. Before the fix they failed with the
reported refusal, and the 'onClick' spelling under mdl 1 returned no
error where MDL-WIDGET36 was wanted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
… it declares, refuse one it does not (#854)

Symptom: an enumeration property of a pluggable widget was stored exactly
as the script spelt it, and nothing compared the value with the widget's
keys. On an 11.13.0 app, `gallery g (…, onClickTrigger: Double, …)`
stored "Double", `onClickTrigger: triple` stored "triple" and a data
grid's `pagination: 'Buttons'` stored "Buttons" — mx check reported 0
errors for all three, with and without its widget update. The Gallery's
runtime (Gallery.mjs, 3.x) runs the row click only for `"single" ===
clickTrigger` or `"double" === clickTrigger`, so with "Double" the row
action never ran on a click. The issue named the hand-written
definitions (the gallery's onClickTrigger); the generated ones (the data
grid) had the same gap.

Cause: the engine's primitive writes — the mapping pass and the
explicit-property pass — and ALTER's pluggable setter wrote the value
as given, and no check rule looked at a widget's own enumeration
property (MDL-WIDGET08 covered object-list items only).

Fix, split by kind as ADR-0011 asks:
- a value that names a declared key in another case is a wrong write,
  not a new meaning: it is stored as the key under every language
  version, by `create page` (the builder now reports its schema's
  enumeration keys, read from the Type the build writes, which the
  loader has aligned with the installed package) and by `alter page …
  set` (PluggablePropertyScope carries the keys);
- a value that names no key is a new rejection, gated as
  MDL-V1-WIDGETENUM: refused under `mdl 1;` as MDL-WIDGET08, naming the
  keys, with nothing written; written as given with a warning under
  mdl 0. `check -p` predicts it from the installed package; a check
  without a project cannot know a newer widget's keys and says nothing.
The migration reference is regenerated with the new change.

Measured (11.13.0, mx check --no-update-widgets): with the fix,
"Double" and "Buttons" are stored as "double" / "buttons", 0 errors;
`triple` under `mdl 1;` is refused by check ("valid values are single,
double") and nothing is written; under mdl 0 it is written with one
MDL-V1-WIDGETENUM warning. The earlier run (fault in place) stored all
three verbatim, also 0 errors — mx check is not the oracle here, the
widget's runtime bundle is. A check -p sweep of all 1100 example
scripts reports the new rule nowhere.

Tests (failing before the fix with `onClickTrigger stored "Double", want
the key "double"`, `pagination stored "Buttons"`, `set pagination stored
"VirtualScrolling"` and no MDL-WIDGET08 refusal):
TestIssue854_EnumValueIsStoredAsItsKey (both headers; hand-written and
generated definition), _UndeclaredEnumValue, _CheckUndeclaredEnumValue
(with a case-variant control), _AlterSetEnumValue.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
The fix commits for #1209 (version-shape pages, KeyboardType)
and #832 (workflow button actions) added no Unreleased entry. Neither
did #1754 (workflows below 11.3), merged earlier in PR #1757. The
entries follow the file's style.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
The struct-field alignment the go1.27.3 toolchain's gofmt (CI's) expects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
docs: recover a merge whose base an older mxcli wrote (fix property-sets)
fix: page properties lost on rewrite — Mendix 9.x/10.x shapes, workflow button actions, pluggable widget events
Symptom (#1756, mendixlabs#1664): a profile's application
title -- Navigation$NavigationProfile.AppTitle, "Mendix" in a new project,
shown by the runtime as "Mendix - <page title>" in every browser tab -- had
no MDL form. `create or modify navigation Responsive app title 'EWO' ...`
failed with "extraneous input 'app' expecting the start of a statement",
`describe navigation` printed neither the title nor the icon (AppIcon), and
`create or modify translations ... for en_US` refuses the source language,
so every generated app kept "Mendix" in the tab unless someone opened
Studio Pro.

Cause: navigationClause had no alternative for either property, the reader
did not read them, and the writer never wrote them (only a profile mxcli
adds got an empty AppTitle and AppIcon).

Fix: `app title '<text>'` and `app icon <Module.Collection.Image> | none`,
order-free with the other clauses. No new token: APP, TITLE and ICON were
already lexer keywords and are all in `keyword`, so `app`/`icon` stay
usable as names (tested). The title is written as the project default
language's item of the stored Texts$Text with bsonnav.SetTextsTextTranslation
-- the rule ALTER's texts use -- keeping its $ID and every other language's
item. An omitted clause keeps the stored value (#1522's rule). The icon is
resolved with resolveImageRefs, as a page's image reference is: check
--references reports a missing one (script-created collections count) and
exec refuses it before writing. Describe prints both, first, whenever
stored (Studio Pro's 'Mendix' / Atlas_Core.Content.Mendix included); an
empty title or icon is omitted, which replays as "keep". A native profile
has neither property, so both clauses are refused there; the MCP backend
refuses them rather than dropping them. AppTitle/AppIcon were introduced
in 8.12.0, below mxcli's floor, so there is no version gate.

Tests: visitor (clauses, order, escaping, none, identifiers still parse),
executor with MockBackend (spec, native refusal, icon resolution at exec
and in --references, describe + reparse), backend on encoded BSON (default
language item patched in place, other languages and $IDs kept, silent
statement keeps both) and end to end on the fixture project (nl_NL item
survives, restating is elided, a changed title writes), MCP refusal.

Controls: before the grammar change every visitor case failed with
"extraneous input 'app'"; replacing the in-place patch with a fresh
Texts$Text fails the encoded-BSON test (nl_NL item and stored $IDs lost)
while the end-to-end test still passes, because canon.CarryTranslations
restores the other language on the real write path; stubbing the icon
resolution fails both resolution tests; removing the MCP refusal fails its
test with a PED read before any refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
The clauses added for #1756 in the places a user or an agent
looks for navigation syntax: the manage-navigation skill (its routing
description now names the browser tab title, a section, the setup
pattern, the native-profile refusal list and a checklist line), the
docs-site CREATE NAVIGATION reference, the quick reference table and
example, and the `mxcli syntax navigation.create` entry.

They say what is not obvious from the grammar: the title is the prefix
of every browser tab ("Mendix - <page title>" in a new project), it is
written in the project's default language with the other languages
kept, and translating it needs an unscoped `create or modify
translations` because the navigation is a project-level document.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
The CHANGELOG "Fixed" entry and the fix-issue finding for #1756
(mendixlabs#1664), kept apart from the code so they can be squashed
into it on integration. The finding records the controls: a fresh
Texts$Text in place of the in-place patch is caught only by the test on
navPatchWebProfile's output, since canon.CarryTranslations restores the
other language on the real write path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
The #1756 clauses landed without a real mx check, so I ran one on Mendix
11.13.0. The bug-test, which set the 3164×1086
docs/images/mxcli-cli.png as the app icon, executed cleanly, but mx check
then reported:

  CE6859 "The selected image must be a square PNG image that is at least
  512 pixels wide." at Navigation profile 'Responsive'

Studio Pro's own Atlas_Core.Content.Mendix passed, with 0 errors.

exec now reads the PNG header of the stored image and refuses one that
is not a square PNG of at least 512 px, naming its size. On a fresh
11.13 app the script that creates the collection and sets the icon is
refused and rolled back, so no collection is left behind. A generated
512×512 PNG and the default icon both pass mx check with 0 errors. An
image with no bytes is left to mx check. One the script itself creates
is judged when the navigation statement runs, by which time it is
stored.

Also measured: a dangling AppIcon is CE1613 "The selected image … no
longer exists." So mx check does catch a missing icon. The previous
commit's "reported only by the next build" was wrong, and the comment is
corrected. The exec refusal is about failing early.

The bug-test now uses the default icon. On a fresh 11.13 app:
- mx check reports 0 errors, the same as the blank app;
- a later statement without the clauses is "Unchanged";
- describe → exec of the navigation is "Unchanged".

The finding and the docs gain the icon rule.

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

Symptom (mendixlabs#1663, Mendix 11.14.0, Combo box 2.10.0): a combo
box's custom content - how it shows more than one attribute per option -
could not be written. `mxcli widget describe combobox` listed
optionsSourceAssociationCustomContent as a `widgets` property, but

  combobox cbPerson (…) { optionssourceassociationcustomcontent cc { dynamictext … } }

was refused by `check -p`: MDL-WIDGET26 "`optionssourceassociationcustomcontent`
is not a container of `combobox`, which declares no containers", and exec
failed on the block ("unsupported widget type"). describe printed stored
content only as a NOT SHOWN note, so a combo box Studio Pro gave content could
not be restated, and a rewrite that changed it was refused (#1648).

Cause: the combo box's built-in definition declared no child slots; the
content keys were listed as known-but-unmapped properties. The custom content
belongs to an option-source MODE (widget.xml binds it to that mode's
datasource), and nothing read a mode's child slots except the engine itself:
the unrouted-children rule, the container check, `widget describe` and the
registry's keyword lower-casing all read the definition's own slots only.

Fix:
- combobox.def.json (both copies, now identical): association mode declares
  `optionssourceassociationcustomcontent`, database mode
  `optionssourcedatabasecustomcontent`. The engine already builds a mode's
  slots, after the mode's datasource, so the content binds to the option
  entity.
- A mode's slot is a container of the widget (MDL-WIDGET26 accepts it); the
  slot of another mode, or any slot on an enumeration combo box, is
  MDL-WIDGET30 at check and refused at exec, naming the slot the mode has
  (one predicate, mode-aware: selectedChildSlots mirrors selectMappings).
- The slot's key written as a value is MDL-WIDGET27, as for any container,
  and the explicit-property pass no longer writes a scalar into a widgets
  property's PrimitiveValue.
- describe prints the described mode's content as its block; content of any
  other slot keeps the NOT SHOWN note. The passthrough's #1648 refusal now
  covers only content describe did not print (undescribedContent), since a
  statement restates the rest.
- `widget describe` lists the two slots, marked per mode, and leaves them out
  of its enumeration-mode example.

Measured on a fresh Mendix 11.13.0 app (Combo box 2.5.0, the version 11.13
ships; 2.10.0 was not available here), bug-test
mdl-examples/bug-tests/1744-combobox-custom-content.mdl:
- fault forced back (definition without the slots): check -p reports the two
  MDL-WIDGET26 errors above; exec --no-check fails on the block.
- fixed: check -p 0 errors; exec "Created page"; `mxcli docker check
  --no-update-widgets` and with update-widgets 0 errors (blank app: 0);
  `mxcli fix widgets` (mx update-widgets in place) changes nothing in the
  page's BSON; describe -> exec "Unchanged page"; the description with the
  association combo box's label changed -> "Replaced page", content kept,
  0 errors.
- not covered: the content type written without content (CE0642) and
  content under type 'no' (measured CE0463) - the next commit. A static-mode
  combo box's per-value content: static values have no MDL spelling yet.

Tests: widget_combobox_custom_content_1744_test.go (write in both modes,
check accepts, wrong-mode refusal at check and exec, describe round trip,
other mode's content stays named, slot key as value, passthrough). Control:
with the definition reverted the seven that read it fail, the check test
with "MDL-WIDGET26 … which declares no containers"; the passthrough test
reads no definition and carries its own control (content describe did not
print stays refused).

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

Symptom (mendixlabs#1663): the enumeration that switches a combo
box's custom content on was written on its own, so

  combobox cbPerson (…, optionsSourceAssociationCustomContentType: 'yes')

passed check and exec, and mx check reported CE0642 "Property 'Custom
content' is required." at Widgets container 'Custom content' of combo box
'cbPerson'.

Cause: nothing tied the …CustomContentType enumeration to the slot it
shows. The content is required while the type is 'yes' or 'listItem' and
hidden while it is 'no' - and the previous commit made the content
writable without the type, which leaves the template's 'no' in place.

Fix: a child slot may declare a `switch` (property, off, on, caption); the
combo box's two custom-content slots declare theirs. One predicate,
slotSwitchProblem, read by check (MDL-WIDGET53, new: MDL-WIDGET16 is the
combo box's caption/datasource rule and the rule-ID guard keeps one owner
per ID) and by exec (refused before anything is written, also under
--no-check):
- the switch on with no content: refused, naming CE0642 and the block;
- content under the switch off: refused - the editor hides it;
- content with the switch left out: the build writes it on ('yes').
describe prints content stored under 'no' in the NOT SHOWN note instead of
as a block, which would re-execute as shown content.

Measured on a fresh Mendix 11.13.0 app with Combo box 2.5.0:
- the type alone, fault forced back (definition without the slots): check
  -p clean, exec "Created page", `mxcli docker check --no-update-widgets`
  CE0642 exactly as reported. Fixed: check -p and exec --no-check refuse it.
- content with the type 'no', and with the type left out, on the previous
  commit: CE0463 at both combo boxes. Fixed: 'no' is refused; left out is
  written 'yes' and the bug-test page (three combo boxes, both modes) is 0
  errors, `mxcli fix widgets` (mx update-widgets in place) leaves its BSON
  identical (10505 ndsl lines, 0 diff), describe -> exec "Unchanged page".

Tests: widget_combobox_custom_content_type_1744_test.go (on without
content in both modes and for 'listItem', content under 'no', content
switching the type on, describe of content under 'no', controls). Control:
with the switches removed from the definition all of them but the controls
fail - exec returns nil, check reports nothing, the type stays "no".
Bug tests: 1744-combobox-custom-content.mdl gains the type-left-out combo
box; 1744-combobox-custom-content-type-without-content.fail.mdl is the
reported shape, which check must refuse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
Left by a conflict resolution; it split the Fixed list in two.

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

Symptom (mendixlabs#1662, reported on v0.28.0 / Mendix 11.14.0): a
text box's Events -> "On enter key press" (Forms$TextBox.OnEnterKeyPressAction)
had no MDL property. `OnEnterKeyPress:`, `OnEnterKeyPressAction:` and
`OnEnterKey:` were each reported as "`OnEnterKeyPress` is not a property
mxcli writes on a textbox (Forms$TextBox), so it would be dropped", exec
dropped it, describe showed nothing and mx check passed, so "a search box
that should search on Enter cannot be generated". `OnChange:` on the same
widget was written and described.

Cause: nothing read it anywhere. No builder key, no describe read, and the
TextBox case of widgetToGen hard-coded SetOnEnterKeyPressAction(noActionGen()),
so describe -> exec of a Studio Pro text box with an enter-key action also
replaced it with nothing. carryNoActionExecution carries only the flag
between two NoActions, never a stored action.

Fix: `OnEnterKeyPress:` takes the action forms `OnChange:` takes; the stored
name `OnEnterKeyPressAction:` is the same property (both at once is
MDL-WIDGET18). No grammar keyword: the generic `key: action` branch already
parses it, and a flow call written without `call` arrives as a data source
that actionFromFlowDataSource converts. A value that is not an action is
MDL-WIDGET28 at check and refused at exec. The builder reads it through a
small table of input event actions (input_event_actions.go), the writer
writes it, describe prints it back. The metamodel gives the slot to
Forms$TextBox alone - gen's TextArea has no SetOnEnterKeyPressAction - so on
a text area (the report says it has the same property) and every other
widget the per-kind check refuses the key and says why. The keys stay out
of staticWidgetKnownProps, the cross-widget union.

ALTER `set (OnChange: ...) on <textbox>` is refused today ("not a pluggable
widget and has no named action slots"); `OnEnterKeyPress` gets the same
refusal, so ALTER is unchanged.

Measured on a fresh 11.13.0 app: the repro's three enter-key actions are
written, `mxcli docker check` reports "The app contains: 0 errors.",
describe prints them as `OnEnterKeyPress: ...`, and describe -> exec reports
"Unchanged page". Control, with the writer's NoAction and the unread
describe forced back in: describe of the same page prints none of the
three, exec reports "Replaced page", all three are gone, and mx check still
reports 0 errors. The new executor and writer tests fail against the
unfixed code with the reported message.

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

Symptom, found while fixing the enter-key action: an input widget's Studio
Pro Events -> "On enter" and "On leave" (focus in / focus out; OnEnterAction
and OnLeaveAction on Forms$TextBox, TextArea, DatePicker, CheckBox,
RadioButtonGroup and DropDown) had no MDL property, and describe -> exec of
a Studio Pro page deleted every one. Measured on a fresh 11.13.0 app with
the previous commit's build: a page holding seven of them described with
none, exec reported "Replaced page", all seven were gone, and `mxcli docker
check` still reported "The app contains: 0 errors."

Cause: the same as the enter-key slot. widgetToGen hard-coded
SetOnEnterAction(noActionGen()) / SetOnLeaveAction(noActionGen()) on every
input (only the text box's OnEnterAction read a model field, which no
builder set), describe never read either slot, and carryNoActionExecution
copies only the DisabledDuringExecution flag between two NoActions, so a
stored action met a rebuilt NoAction and was skipped.

Fix: `OnEnter:` and `OnLeave:` on all six inputs, as two more rows of the
input event-action table the previous commit added (stored names
`OnEnterAction:` / `OnLeaveAction:` accepted, the action forms `OnChange:`
takes, MDL-WIDGET28 / MDL-WIDGET18 as for the enter-key action). Every input
builder reads them (applyInputFocusEventsV3, now part of inputReads), the
writer writes them (focusEventsToGen, dropDownToGen), describe prints them
after OnChange. On a widget without the slots, such as a button, the
per-kind check refuses the key and says only an input widget has it.

Measured on a fresh 11.13.0 app: the seven actions are written, mx check
reports 0 errors, describe prints them, and describe -> exec reports
"Unchanged page". The control is the previous commit's build over the same
page (above). The new executor and writer tests fail against the unfixed
code: the builder leaves both fields nil, describe prints neither, the
writer writes NoActions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82sUVQEB3K5W5vh85wM
fix: text box enter/focus actions, combo box custom content, navigation app title and icon
@ako
ako merged commit 67d8bbd into mendixlabs:main Oct 28, 2026
15 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