Skip to content

Fix #834 (ALTER PAGE into a customContent column) + two defects found with it - #118

Merged
ako merged 4 commits into
mainfrom
claude/mxcli-issues-ovfoxk
Aug 8, 2026
Merged

Fix #834 (ALTER PAGE into a customContent column) + two defects found with it#118
ako merged 4 commits into
mainfrom
claude/mxcli-issues-ovfoxk

Conversation

@ako

@ako ako commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Follow-on to #115, which merged before these three commits existed. Fixes upstream #834 plus two independent defects found while verifying it.

Verified against mxbuild 11.6.6 throughout.

mendixlabs#834 — ALTER PAGE could not reach a widget in a customContent column

alter page … set <prop> on <widget> reported widget "X" not found when the widget sat inside a datagrid column rendered as customContent. The only remedy was CREATE OR REPLACE PAGE — a full page rewrite.

findInWidgetChildren's pluggable branch searched the grid's own Object.Properties[].Value.Widgets and matched columns by derived name, but never descended into a column's own content. Columns live at Object.Properties[columns].Value.Objects[]; a column's widgets are one level deeper, at Properties[content].Value.Widgets[].

Addressing is by the nested widget's own name — not the grid.column.widget path the issue asks for. DataGrid2 columns carry no stored name in the MPR (the existing findBsonColumn documents this, and the BSON confirms it: the grid is a pluggable widget, so columns are WidgetObject entries with no Name). A column segment could therefore only ever be a derived name — the bound attribute, or the caption — which goes stale the moment someone edits the caption. The nested widget's name is real and stable, so on btnEdit works directly and the grammar needs no change.

A second test pins that a column still resolves by its derived name: the new descent runs in the same loop and could otherwise shadow it.

Two defects found while verifying

set Caption failed on every action button. With the widget found, the set still failed — widget has no Caption property. Checked whether that was specific to nesting: it wasn't. It reproduces on a plain top-level button too. An ActionButton has no Caption document; its caption is a Forms$ClientTemplate under CaptionTemplate (Template → Items[] → Translation.Text), which is the same structure setWidgetContentMut already walked for Content. Both setters now share a helper.

Committed separately, since it's independent of mendixlabs#834 — and the finder fix alone would not have made the reporter's command work.

describe invented an else on a type split. Left as a known artifact in #115; now fixed. An object-type decision always carries an (empty) outgoing flow — the null-object case — which the builder emits whether or not an else was written. DESCRIBE rendered it as a bare else, so describing a split written without one produced MDL with an else, and each describe→exec pass accumulated another.

Fixed in the describer (drop the else line when its body renders empty, the same pattern the if/else emitters already use), not the builder. The obvious builder fix — don't emit the branch when no else is written — was implemented first and is wrong: without that flow the build fails CE0089 "The '(empty)' value should be configured for an outgoing flow." It's load-bearing, and MDL's else on an inheritance split is that (empty) case. That also retro-explains a result from #115: else can't substitute for the base entity's case (CE0090) because (empty) and the base type cover different things.

Worth recording how the wrong fix was caught — every shape was re-run through mxbuild, not just the test suite. The unit tests passed against it; the type splits it broke only failed at build time.

Verification

before after
on btnEdit (nested in customContent) widget not found Caption: 'Changed'
on btnTop (top-level button) no Caption property Caption: 'Changed too'
describe of a split with no else prints else prints none
mxbuild 11.6.6 0 errors
  • Full go test ./... green; make check-mdl and check-skill-mdl pass.
  • describe→exec→describe is byte-stable for an empty-branch split, an all-branches-return split, and an authored else.
  • The describe fix was confirmed to fail with the exact symptom when reverted.
  • Re-verified after merging the latest main (which moved twice while this was in flight).

Note for review

I deliberately did not add the 3-segment grid.column.widget grammar the issue requests — reasoning above. If ambiguity ever bites (two columns with same-named children), the better answer is an ambiguity error like the one columnMatchCount already produces, rather than a path built on a name that isn't stable. Worth saying so on the issue when it's closed.

🤖 Generated with Claude Code

https://claude.ai/code/session_013uQvFDd5R4eNqqita59jM8


Generated by Claude Code

claude added 4 commits August 8, 2026 12:10
DESCRIBE printed a bare `else` for a `split type` that never had one, and a
describe→exec roundtrip accumulated another each pass.

An object-type decision always carries an `(empty)` outgoing flow — the
null-object case — which the builder emits whether or not an `else` was
written. DESCRIBE rendered that flow as an `else`. The artifact was invisible
until the InheritanceCase writer landed: before that every branch flow degraded
to a bare NoCase, so nothing distinguished the `(empty)` flow from a real case.

Fixed in the describer: drop the `else` line when its body renders empty, using
the same elseLineIdx/truncate pattern the if/else emitters already use. Exec
re-creates the flow, so the omission is lossless — verified that describe→exec
→describe is byte-stable for an empty-branch split, an all-branches-return
split, and an authored else.

The obvious fix — not emitting the branch in the builder when no `else` is
written — was implemented first and is wrong. Without that flow the build fails

    CE0089 "The '(empty)' value should be configured for an outgoing flow."

so it is load-bearing, and MDL's `else` on an inheritance split IS the `(empty)`
case. That also explains a result from the previous commit: an `else` cannot
substitute for the base entity's own case (CE0090) because `(empty)` and the
base type cover different things.

Worth recording how the wrong fix was caught: every shape was re-run through
mxbuild, not just the test suite. The unit tests passed against it — the type
splits it broke only failed at build time.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uQvFDd5R4eNqqita59jM8
`alter page … set <prop> on <widget>` reported "widget not found" when the
widget lived inside a datagrid column rendered as customContent, so the only
way to touch it was CREATE OR REPLACE PAGE — a full page rewrite.

findInWidgetChildren's pluggable branch searched the grid's own
Object.Properties[].Value.Widgets and matched columns by their derived name,
but never descended into a COLUMN's own content. Columns live at
Object.Properties[columns].Value.Objects[]; a column's widgets are one level
deeper, at Properties[content].Value.Widgets[].

Addressing is by the nested widget's OWN name. A `grid.column.widget` path was
considered and rejected: DataGrid2 columns carry no stored name in the MPR (the
existing findBsonColumn documents this), so the column segment could only ever
be a derived name — the bound attribute, or the caption — which changes the
moment someone edits the caption, leaving such a path silently stale. The
nested widget's name is real and stable, and the grammar needs no change.

A second test pins that a column still resolves by its derived name, since the
new descent runs in the same loop and could otherwise shadow it.

Fixes mendixlabs#834

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uQvFDd5R4eNqqita59jM8
`alter page … set Caption = '…' on <actionbutton>` failed with "widget has no
Caption property" for every action button — nested or top-level.

An ActionButton has no `Caption` document. Its caption is a
Forms$ClientTemplate stored under `CaptionTemplate` (Template → Items[] →
Translation.Text), which is the same structure setWidgetContentMut already
walked for `Content`. setWidgetCaptionMut only looked for `Caption`.

Both setters now share setClientTemplateText.

Found while fixing mendixlabs#834 and worth separating: the reporter hit it inside a
customContent column, but it reproduces on a plain top-level button too, so it
is an independent defect — and the mendixlabs#834 finder fix alone would not have made
their command work. Verified end-to-end that both the nested and the top-level
button now take a new caption, and that the resulting projects build with 0
errors on mxbuild 11.6.6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013uQvFDd5R4eNqqita59jM8
@ako
ako merged commit d36b29e into main Aug 8, 2026
3 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