Skip to content

fix(pages): four silent page changes — list view Editable, data view footer addressing, page-variable input binding, pluggable REPLACE passthrough - #917

Merged
ako merged 16 commits into
mainfrom
fix/page-silent-changes
Oct 1, 2026
Merged

ako merged 16 commits into
mainfrom
fix/page-silent-changes

Conversation

@ako

@ako ako commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Four silent page changes. The rule behind all four: when a statement reports success, it must have written what it says, and describe must not drop anything that is stored.

Fixes mendixlabs#1214
Fixes mendixlabs#1235
Fixes mendixlabs#1247
Fixes mendixlabs#293
Closes #528

What changed (one commit per item)

mendixlabs#1214: set Editable = true on <listview>. This printed "Altered page" and stored nothing. A list view or data view stores Editable as a boolean, but the setter only wrote strings, so the boolean fell through and the setter returned nil. Now the type of the stored value decides what the property accepts:

  • list view or data view: true / false
  • input widget: Always / Never, written in canonical case ('never' used to be stored as typed)

Conditional without an expression, a boolean on an input, and Editable on a widget that has none are all refused. These are new rejections. They replace a silent no-op, or a wrong write in the bare-Conditional case. set Editable = [expr] now also writes the Conditional enum next to its settings element, the same way CREATE does.

#528 / #293: data view footer. A footer is a region of its data view. Its widgets are stored in the data view's FooterWidgets, and the footer itself has no stored name. That is why the name the script wrote, and the footer1 that describe invented, both resolved to nothing.

  • dvMain.footer is now an ALTER target, addressed the same way scroll-container regions are:
    • INSERT INTO appends. Filling an empty footer turns ShowFooter on.
    • REPLACE swaps the whole content. A describe-style footer { … } block is unwrapped.
    • DROP empties the footer.
    • A miss names the footer addresses that do exist on the page.
  • REPLACE may now reuse the names of the widgets it removes, i.e. the target's descendants. Restating btnSave used to fail with "duplicate widget name".
  • A name on a data view footer is reported as MDL-DEPR005 (unstored widget name), exactly like a layout-grid row's name. The AST is identical either way, so nothing written changes. Describe prints footer { … }. The 66 gated doc, example and skill occurrences were rewritten, and the grammar's own doc example (replace footer1 with …) was corrected.

mendixlabs#1235: input bound to a page variable. I measured the stored shape over the Studio Pro MCP tunnel on TestApp (Pages.Probe1235_pagevar): sourceVariable {localVariable: "ShowAll"}, no attributeRef, and ped_check_errors clean.

  • checkbox cb (Attribute: $ShowAll) now writes that shape when $ShowAll is a declared page variable.
  • check validates the name against the document's Variables: (a name not declared there is still MDL-WIDGET34).
  • ALTER sees the stored variables.
  • Describe prints Attribute: $ShowAll.
  • mx check then reported CE6553 for a text box, so text box and text area bound to a variable now get MaxLengthCode 0 (unlimited, Studio Pro's default for variables). -1 means "attribute length", which a variable doesn't have.

mendixlabs#1247: REPLACE of a pluggable widget. This applies #830's passthrough principle per property.

  • When one pluggable widget is replaced by one of the same package, the executor also builds the stored widget as describe prints it (the baseline).
  • The mutator keeps the stored Type, plus every property and widget field on which the replacement and the baseline agree. Those values come from the template, not from the statement.
  • Values that differ are grafted in, with their TypePointers re-aimed at the stored Type by key path.
  • Anything that can't be lined up falls back to the old plain replace.
  • On TestApp's Studio Pro-authored Rules.BusinessRule_NewEdit, adding sort by Name asc to comboBox1 used to change three things: the sort, Editable "Never" → "Always", and an expression property's PrimitiveValue "" → "false". Now only the sort changes.

ADR-0011

Test plan (what I ran)

Not in this PR (follow-ups)

  • create or modify page with a changed pluggable widget still rebuilds unstated properties from the template. The fix(pages): keep an unchanged pluggable widget's stored Type and Object (#721 L4) #830 passthrough there is all-or-nothing, and the same baseline merge could be applied.
  • Describe still doesn't print the combobox's emptyOptionText or readOnlyStyle. REPLACE now keeps them, but they still aren't visible in describe.
  • A name on a footer inside a fragment used in a data view is still discarded without a warning, because the visitor can't see the eventual parent.

🤖 Generated with Claude Code

ako and others added 11 commits October 1, 2026 19:24
… reporting success with nothing stored (mendixlabs#1214)

A list view / data view stores Editable as a boolean; the setter only wrote
strings, so `set Editable = true on lvRows` returned nil. The stored value's
type now decides the vocabulary (true/false vs Always/Never), the input enum is
canonicalised, anything else is refused, and EditableIf writes the
Conditional enum beside its settings element as CREATE does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…627)

The fluent API's AttributeModifier.Apply() rebuilt the attribute with
raw == nil and kept only its $ID, so the codec minted GUID = $ID - the
mendixlabs#1119 data-loss class. The write guard refused it, leaving the API
unusable on any Studio Pro-authored attribute. Carry the stored raw
bytes and export level via carryStoredAttribute, now shared with the
entity rewrite's carryAttributeIdentity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#528, mendixlabs#293)

A data view's footer is a region: its widgets live in the data view's
FooterWidgets and the footer has no stored Name, so the name a script wrote
and describe's invented footer1 both named nothing ALTER could find.

- ALTER PAGE: <dv>.footer resolves as a region; INSERT INTO appends, REPLACE
  swaps the whole content (a describe-style footer { } block is unwrapped),
  DROP empties it. A not-found names the footer addresses the page has.
- REPLACE may reuse the names of the widgets it removes (the target's
  descendants), which was refused as a duplicate.
- A name on a data view footer is MDL-DEPR005 (unstored widget name), like a
  layout grid row's; describe prints footer { } unnamed. The AST is
  identical, so writes do not change. Docs/examples/skills rewritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MoveEntity scanned only the plain associations of the source unit, so a
cross-association created by an earlier move was invisible: moving its
second endpoint left a ParentPointer naming an element absent from its
unit and Studio Pro could not open the project. Handle the three shapes:
convert back to a plain association when both endpoints share a module
(raw transform, GUID carried), let an own cross-association travel with
its FROM entity, and re-point a cross-association in another module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…endixlabs#849)

Studio Pro does not reload the model from disk, so a write mxcli makes
while the project is open is silently discarded by Studio Pro's next
save. The writer now refuses any write that would reach storage while
Studio Pro's <project>.mpr.lock is beside the .mpr (matched without
regard to case, as Studio Pro lower-cases it). Reads and writes elided
as no-ops are never refused; exec --force or
MXCLI_ALLOW_STUDIO_PRO_OPEN=1 override.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…its mappings and its commit option (#571)

create published rest service wrote only the path's {name} placeholders as
operation parameters, each a String: a query or body microflow parameter
failed mx check with CE0350, an Integer {id} with CE6539. import mapping,
export mapping and commit parsed and were thrown away.

- Parameters are derived from the microflow as Studio Pro derives them
  (path name -> Path, object/list -> Body, HttpRequest/HttpResponse -> none,
  else Query), each with the microflow parameter's type, merged over the
  stored parameters so a header / renamed / described one survives.
- Mappings and commit go AST -> model -> BSON and back; describe prints
  them and notes parameters MDL cannot state. An unknown commit option is
  refused by exec and check (MDL-REST03).
- create or modify carries the restated operation's summary, documentation
  and object handling; the service rewrite carries the stored keys MDL cannot
  state (authentication, CORS, documentation). List markers as Studio Pro
  writes them.

TestApp's Services.OrdersRestApi leaves the round-trip allowlist.

Fixes mendixlabs#1206

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e binding (mendixlabs#1235)

Studio Pro stores an input bound to a page variable as SourceVariable
{LocalVariable} with no AttributeRef (measured over MCP on TestApp). MDL
refused `Attribute: $ShowAll` (MDL-WIDGET34) and describe printed the
widget unbound, so describe -> exec cut the binding silently.

- builders map a bare `$name` naming a declared page variable to that
  SourceVariable; check validates it against the document's Variables;
  ALTER sees the stored variables
- describe prints `Attribute: $name` for that shape
- text box / text area bound to a variable get MaxLengthCode 0 (unlimited);
  -1 is mx check CE6553

Also wires ALTER REPLACE's stored-widget description (used by the next
commit) through cmd_alter_page.go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he statement does not state (mendixlabs#1247)

A pluggable widget is rebuilt from its template, so a REPLACE reset every
property MDL has no word for. On TestApp's Studio Pro-authored
Rules.BusinessRule_NewEdit, adding a sort to comboBox1 also turned its stored
Editable Never into Always and an expression property's PrimitiveValue into
'false'; the reporter lost a translated placeholder and readOnlyStyle.

For one pluggable widget replaced by one of the same package, the executor
builds the stored widget as describe prints it beside the replacement;
the mutator keeps the stored Type and every property/field the two builds
agree on, and grafts the differing values with their TypePointers re-aimed
at the stored Type by key path. Anything that cannot be lined up falls back
to the plain replace. Running the same REPLACE twice writes nothing.

Also: findings, mutator-addressing pattern page, alter-page skill.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- mendixlabs#591: list activities and cast carry the flow flavour's
  error handling (Abort in a nanoflow, where "Rollback" was CE6035); create,
  commit, call nanoflow and call microflow in a nanoflow accept only a handler
  without rollback (measured), refused otherwise; check now reports the
  nanoflow rules exec's build enforces (MDL091) and the annotation rules.
- mendixlabs#698: the MCP mapper writes errorHandlingType and refuses a
  custom handler / error-handler flow PED cannot express.
- mendixlabs#991: @anchor and @Curve inside an error handler are applied;
  describe emits the handler body's layout annotations.
- mendixlabs#992: `@anchor(true: (to: top))` parsed as a division; the
  paren value now wins, unusable @anchor parameters are refused (MDL092), and
  `@curve(true: …)` is refused on nanoflows too.
- mendixlabs#870: lock/unlock name their workflow; `pause all` /
  `unpause all` is Studio Pro's "(Un)pause instances"; bare `all` is refused
  (MDL-WF17); describe no longer turns a Studio Pro lock into `all`.
- mendixlabs#175: `call workflow … on error continue` refused (MDL076),
  MDL076 exec-enforced.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s too (mendixlabs#1247)

Restating TestApp's dataGrid2_1 with one column caption changed fell
back to the template rebuild: the filter widgets nested in the columns
value point into their own Type, which the graft could not re-aim, so
itemSelectionMethod, onClickTrigger and every column's unmapped
properties were reset. A nested pluggable widget is now grafted as
built, and an object list that lines up with the baseline is merged
object by object.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hint told authors to declare `Variables: { … }`, the brace form
MDL-DEPR123 deprecates; it now writes `Variables: ( … )`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ako

ako commented Oct 1, 2026

Copy link
Copy Markdown
Owner Author

Independent review: pushed two commits.

Ran: make build, lint, check-conformance, go test ./cmd/mxcli, pagemutator + executor unit tests, make test-integration-roundtrip (pass), and the executor integration tests from the PR. On a TestApp copy edited by every scenario (Editable, footer insert/replace/drop, page-variable inputs of six kinds, DataGrid 2 caption change and column add), mxcli docker check reported 0 errors and each script, run a second time, wrote nothing.

🤖 Generated with Claude Code

@ako
ako merged commit 643ceb9 into main Oct 1, 2026
31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment