Skip to content

fix: refuse a page, snippet or layout rewrite that would delete widgets MDL cannot author - #1566

Merged
ako merged 1 commit into
mainfrom
claude/cool-keller-jtch0r
Oct 22, 2026
Merged

ako merged 1 commit into
mainfrom
claude/cool-keller-jtch0r

Conversation

@ako

@ako ako commented Oct 22, 2026

Copy link
Copy Markdown
Owner

Closes #1554 (mendixlabs#1622)

What it does

describe page M.P prints a classic data grid, a reference selector, a drop-down or a reference set selector as a -- NOT re-executable comment. Executing that output replaced the page with the widget gone. The only output was Replaced page M.P, with no warning, and mx check was clean on both sides.

The issue offered two remedies: refuse, or keep the widgets. This PR refuses, which is the repo's guard-don't-drop posture (ADR-0005, the same as validate_workflow_rewrite.go). A create or modify / create or replace of a stored page, snippet or layout is refused when the stored document holds such a widget and the statement doesn't declare it by name.

The refusal says what to do next:

page MyFirstModule.Orders holds 2 widget(s) MDL cannot author, and rewriting it from this statement would delete them:
  - grid1 (Forms$DataGrid)
  - referenceSelector1 (Forms$ReferenceSelector)
  `describe page` prints them as `-- NOT re-executable` comments, which is why executing its output drops them.
  To change the page and keep them, use ALTER, which edits only what it names:
    alter page MyFirstModule.Orders { set (Caption: '…') on <widget>; insert after <widget> { … }; replace <widget> with { … }; };
  To rebuild it without them, remove them on purpose first:
    alter page MyFirstModule.Orders { drop grid1, referenceSelector1; };
  • check -p reports the same before anything is written. This is the "pre-check" the issue asks for. It runs in statement order and skips a document that an earlier alter, create or drop in the same script touches.
  • Where the widget list comes from: describe's own bookkeeping. describeDroppedWidget already ran for exactly these widgets; now it also feeds the guard, which describes the stored document into io.Discard. There is no second list of authorable $Types to drift from the describe switch.
  • A widget the statement declares by name is exempt. Otherwise a widget the builder can write but describe cannot print would refuse its own script.
  • Every rebuild call site is covered: pages, snippets and layouts. Layout describe now goes through describeWidgetBody; its output is identical when nothing is dropped.

Keeping the widgets in place across a rewrite would need new MDL syntax for "the stored widget here" and a raw-BSON splice that carries selection references. That is a larger proposal, not this fix.

Testing

  • make build, make test, make lint (including conformance), make check-findings and make test-integration-roundtrip all pass locally.
  • New tests are in mdl/executor/issue1622_page_rewrite_unauthorable_test.go. They run on a real backend against a fixture copy, with a Studio Pro-shaped Forms$DataGrid (the [up#1565] A fall-through error handler on an activity inside an IF branch is wired to the wrong place: an endless loop with a result variable, a silent end without one #1459 shape) and Forms$ReferenceSelector spliced into the stored unit. No fixture project holds these widgets.
    • The page, snippet and layout rewrites are refused and the stored widgets are kept.
    • check -p reports it.
    • alter … drop then rewrite goes through.
    • An earlier alter in the same script is honoured by check.
    • A widget declared by name is not counted.
  • Control: I removed the fix and kept the tests. Every guard test then fails with the reported symptom: Replaced page MyFirstModule.Orders, the stored snippet lost its data grid, the stored layout lost its reference selector. The two "goes through" tests pass both ways; they are the controls against over-refusal.
  • mdl/roundtrip/dangling_selection_1415_test.go asserted the old behaviour, namely that the description of a page with a legacy grid executes, which is this bug. It now asserts three things: the refusal names the grid, the [up#1533] Two unhelpful parse messages in workflow MDL (reserved activity name close, exposed before returns) #1415 dangling-selection error stays gone, and the stored page keeps its grid.
  • make test-integration-executor had one local failure, TestMxCheck_DoctypeScripts (REST/workflow property sets), which this change doesn't touch. I ran it on a clean origin/main worktree with the same mxbuild 11.6.6 and it fails the same way. CI pins 11.12.2.

Mendix validation

This is a refusal on the mxcli side, so nothing new is written. The repro in the issue (Mendix 10.24, MPR v1) isn't available here. No fixture holds these widgets, so they were seeded into stored units.

Agentic Code Testing

  • Skill updated (alter-page: a page holding such widgets refuses the rewrite; use alter)
  • docs-site create-page.md and alter-layout.md, CHANGELOG, bug-test mdl-examples/bug-tests/1622-rewrite-keeps-unauthorable-widgets.mdl
  • The error message names each widget and both ways forward

Generated by Claude Code

…ts MDL cannot author

`describe page M.P` prints a classic data grid, a reference selector, a
drop-down or a reference set selector as a `-- NOT re-executable` comment,
and executing that output replaced the page with the widget gone — only
"Replaced page M.P", mx check clean on both sides (mendixlabs#1622,
#1554).

A `create or modify` / `create or replace` of a stored page, snippet or
layout that holds such a widget, and does not declare it by name, is now
refused (guard-don't-drop, ADR-0005). The refusal lists every widget, the
`alter` that edits the document while keeping them, and the
`alter … { drop … }` that removes them on purpose first. `check -p`
reports the same, following an earlier `alter` of the document in the
script.

The widgets come from describe itself: describeDroppedWidget already runs
for exactly the widgets MDL cannot author, and now also feeds the guard,
which describes the stored document into io.Discard. Layout describe goes
through describeWidgetBody so the guard sees its widgets too.

The #1415 round-trip test asserted the old behaviour (the description of a
page with a legacy grid executes); it now asserts the refusal, that the
#1415 dangling-selection error stays gone, and that the grid is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NUbjJ5DeS2vysn9hfKir4Y
@ako
ako merged commit 82e48a8 into main Oct 22, 2026
33 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.

[up#1622] describe writes widgets it cannot execute, and exec then drops them silently

2 participants