Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m

## Unreleased

- process(inbox): **five field notes from an existing-app legacy-widget rebuild** — exec.sh keeps an MDL write when mxbuild crashes on it and the next exec runs on "Baseline NOT measured"; a signal stop of `run --test-endpoint` leaves AfterStartupMicroflow on `MxTest.RegisterEndpoint` (second field case for #244); Unit-row delete refused as ORPHAN, rename-then-drop works; a durable-rebuild kit with a "restate from DESCRIBE" STOP row (DESCRIBE omits module-role and layout-row visibility: 97 role locks in 16 documents, none shown); learned entries for File Uploader image mode (CE0463), native-mapped `radiobuttons`/`image`, tab-page `CaptionParams`/`Visible:` silently dropped, BUG-39 on widget XPath, and two-hop data-source paths. Three further notes (restore-mpr usage, build-plan-status flag order, another project's mxcli test blocking exec.sh) were dropped because master already fixes them. Mendix 11.14.0 Beta, mxcli v0.25.0, toolkit c1efcb3 — AlvinnYeo
- fix(build-plan-status): --refresh keeps an existing build-plan.json current, so a day of building no longer reads as 0 built — MendixMau
- learn(unit-test): the unit-test skill names the per-flow `Unit flows:` list next to `Unit modules:`, so whether a flow gets a test reads the same in both places (follow-up to #435) — MendixMau
- learn(load-test): **new on-demand skill for load testing a page without correlating Mendix's client protocol by hand: Playwright records the real `/xas/` calls, a probe maps each opaque `runtimeOperation` to its microflow through Jaeger, and the chosen call is replayed with the session's own cookie and CSRF token.** Ten field traps (HTTP 200 on a failed action, caught REST errors under a green microflow span, the wrong call hammered, a recording that still held the previous page, a tracing check that failed open, a harness that throttled itself, harness vs app limits, requests vs users, sampled trace counts, empty data) and a four-line report that says what the run does not prove. Routed ondemand for lead, test and review; no instrument ships yet and the roadmap's "no load testing" line is reworded. Field evidence: a warehouse demo app, Mendix 11, 16 runs 2026-09-09 to 2026-10-08, bursts of 1000 and 2000 concurrent calls on one session — MendixMau
Expand Down
83 changes: 83 additions & 0 deletions contrib/inbox/2026-10-13-durable-rebuild-kit-legacy-widgets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Durable rebuild of legacy-widget pages: a four-part kit, and "restate from DESCRIBE" as a STOP item

**From:** a field project (anonymised) — an existing Mendix app (old widgets throughout) changed with mxcli + this toolkit
**Date:** 2026-10-13
**Kind:** process
**Field evidence:** 79 non-writable widgets on 40 live documents (DESCRIBE `-- NOT re-executable` count)
and 97 role-locked widgets in 16 documents (BSON scan; DESCRIBE showed none). 34 pages and snippets
were restated whole; the read-back afterwards found 0 non-writable widgets and 0 role locks on them.
Two drafted scripts would have silently dropped role or row visibility had they run. Mendix 11.14.0
Beta (Classic client, Production security), mxcli v0.25.0, toolkit c1efcb3.
**Proposed target:** `skills/existing-app-change.md` (a "legacy-widget pages" section), a STOP row in
`skills/learned-mdl-preflight.md`, possibly a `project-bin/` scan once field-proven as a file

**Currency check:** 2026-10-16 against master d1cbd41: partly stale. STOP row 21 in `skills/learned-mdl-preflight.md` now covers restating a document another script also edits. Still open: the legacy-widget section in `skills/existing-app-change.md` and the scan for role locks that DESCRIBE does not show.
---

## What is already covered

- `bug-logs/mxcli-bugs.md` BUG-115 addendum: "Grep a generated script for `NOT re-executable` before
executing it" (for the File Manager widget).
- `bug-logs/mxcli-bugs.md` (the `hasRole()` entry): no MDL syntax writes `ConditionalVisibilitySettings.ModuleRoles`.
- `skills/learned-mdl-cannot-express.md` (master bda5bed, #277): "A widget visible to one role and not
another — nothing", with the execute-grant recipe for action buttons.

## What is not covered (findings)

1. **DESCRIBE does not print module-role visibility.** One portal page had 19 role-locked widgets; DESCRIBE
printed none of them (nor a hidden "show page" action behind 9 image widgets). A page restated from that
DESCRIBE builds clean and shows every widget to every role.
2. **A drafted restatement said "the snippet has no visibility rules".** It restated a side-menu snippet
from DESCRIBE (navigation-list items have no names, so one item cannot be dropped alone). A BSON audit
found 13 role-locked items. Running the draft would have exposed all of them, with a clean build.
3. **DESCRIBE does not print layout-grid row visibility.** Two form pages had nine detail sections shown
by type through row visibility; a snippet had 56 row-level "show only when it has a value" rules. None
appear in DESCRIBE. The drafters re-wrote them as container `Visible:` (which DESCRIBE does print and
mxcli does write); an audit confirmed every rule was carried over.
4. **Tab-page `Visible:` and `CaptionParams` are accepted and not written** (see the learned-entries note).
Found only by reading the unit back after exec.

So "copy the page from DESCRIBE, edit, `create or replace`" is silently lossy on any app built in Studio Pro.

## Proposed STOP row (learned-mdl-preflight.md)

| # | Operation | Why STOP | Do instead |
|---|---|---|---|
| new | Restate an existing page, snippet or layout from `describe` output (`create or replace`, or `alter … replace` of a container) | DESCRIBE omits module-role visibility, layout-grid row/column visibility and legacy-widget internals; check, exec and mxbuild all pass while the rules vanish | Run the visibility scan (below) on the unit first. Zero role locks and zero row rules: go. Otherwise rewrite each rule as container `Visible:` or flow access, or leave the page to `alter page` / Studio Pro |

Probe that falsifies it: in Studio Pro, set one widget visible to one module role and one layout row
visible on an attribute; `describe page`; grep the output for the role name and the attribute. Today
(v0.25.0): 0 hits for both.

## The kit (each part was done by hand in the field; none is a toolkit file yet)

1. **BSON visibility scan, per document.** Decode each page/snippet/layout unit under `mprcontents/`
(the toolkit's `project-bin/mxunit_bson.py` codec is enough) and count (a) any
`ConditionalVisibilitySettings` whose `ModuleRoles` list is non-empty, (b) any
`ConditionalVisibilitySettings` on a `Forms$LayoutGridRow` or `Forms$LayoutGridColumn`. Report per
document: widget name, kind, roles or attribute. The field scans were inline one-offs, not a saved
script; a shipped version needs its own fixture.
2. **Non-writable count, per document.** `describe` every page, snippet and layout in the app's own
modules (Marketplace modules excluded) and count `-- NOT re-executable` lines, grouped by the BSON
type in the comment. Field result: `Forms$DropDown` 29, `Forms$ReferenceSelector` 27,
`Forms$DataGrid` 16, sign-in widgets 4, `Forms$TemplateGrid` 1, `Forms$ImageUploader` 1,
`Forms$SidebarToggleButton` 1.
3. **Rebuild brief per document.** Inventory first (DESCRIBE plus the scan), then each widget marked
KEEP / CUT / MOVED / NEW against the wireframe, then the restatement. Replacements used:
drop-down → `combobox` on the enumeration; reference selector → `combobox` on the association (a
microflow data source when the filter needs the context object); old data grid → Data grid 2;
template grid → `listview`; image uploader → File Uploader in files mode plus a `dynamicimage`;
role-only visibility → a per-viewer flag from a data-source microflow, or flow access (Mendix hides a
button whose flow the user cannot run). The restating script becomes the document's single owner;
older `alter page` blocks on it are marked superseded (compare #269).
4. **Post-exec read-back.** After each exec: DESCRIBE again (0 `NOT re-executable`), rerun the scan
(0 role locks unless intended), and grep the unit for every `Visible:` / `CaptionParams` the script
wrote. Item 4 above was caught only this way.

Cost, for scale: six drafting agents in parallel; one gated exec per document (about 5–10 minutes each
with mxbuild and lint), serial because there is one writer to the `.mpr`.

Upstream asks (mxcli), for the maintainer to file or link: print module-role and row/column visibility in
DESCRIBE (or at least a `-- role-based visibility not shown (N roles)` comment), accept it on write, give
navigation-list items stable names, and a legacy-widget migration (`DataGrid` → Data grid 2,
`ReferenceSelector`/`DropDown` → combobox) behind a dry run.
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# exec.sh keeps an MDL write when mxbuild crashes after it, and the next exec runs on an unloadable model

**From:** a field project (anonymised) — an existing Mendix app being changed with mxcli + this toolkit
**Date:** 2026-10-13
**Kind:** bug
**Field evidence:** the project's BUILD-LOG shows two "gate could not run" rows 46 s apart: the
script that made the model unloadable, then the next script, which exec.sh accepted and ran on top
of it. Mendix 11.14.0 Beta (Classic client), mxcli v0.25.0, toolkit commit c1efcb3 (`project-bin/exec.sh`
is byte-identical on master 0c62f6f, blob a03adec).
**Proposed target:** `project-bin/exec.sh` (gate branch "mxbuild failed to run" and the pre-flight
"Baseline NOT measured" branch); one line in `skills/iterative-build-loop.md`

---

## What happened (finding, reproduced once in the field)

A page script set a data view's data source to a two-hop association path in its full form
(`$Parent/MyModule.A_B/MyModule.B/MyModule.B_C/MyModule.C`). `mxcli check` passed. After the
exec, the mxbuild loader itself crashed instead of reporting CE errors:

```
An error occurred when trying to set the 'DestinationEntity' property of a Entity ref step in a Page.
```

exec.sh printed:

```
✗ mxbuild failed to run (exit 1) — gate could not verify the model.
→ Snapshot preserved. Open in SP to verify manually before proceeding.
```

and kept the write. It did exit 1 and cleared the verification stamp (good), but the agent driving
the build ran the next script. exec.sh's pre-flight printed `⚠ Baseline NOT measured: mxbuild exited 1
without checking the model.`, carried on, applied the next script, and hit the same crash.

BUILD-LOG rows, verbatim except the script names:

```
| 2026-10-13T12:26:18 | `25g-PageA-rebuild.mdl` | fail | ❌ gate failed | 5 error(s): CE1613 — script rolled back · approval: auto (PROJECT.md) |
| 2026-10-13T12:28:46 | `25g-PageA-rebuild.mdl` | fail | ❌ gate could not run | mxbuild exit 1 · approval: auto (PROJECT.md) |
| 2026-10-13T12:29:32 | `25h-SnippetB-rebuild.mdl` | fail | ❌ gate could not run | mxbuild exit 1 · approval: auto (PROJECT.md) |
```

Recovery: `./bin/restore-mpr.sh <full path to the snapshot taken before 25g>` then
`./bin/verify-model.sh` (0 errors). The 25h row had to be re-run afterwards.

## Why it matters

- The header comment of exec.sh says an unverified gate is rolled back **for `--patch` only**. That is
true and deliberate, but for MDL the "kept" case was designed for "mxbuild could not start"
(wrong version, no JDK). Here mxbuild started, ran on the baseline cleanly a minute earlier, and
crashed only on the written model. That is a property of the write, not of the machine.
- The next exec has no way to tell the two apart: "Baseline NOT measured" is a warning, not a refusal.
So in an unattended chain the damage compounds: every following script is written onto a model
nothing can load, and each one gets its own snapshot of the broken state.

## Proposed fix (hypothesis, not built)

1. **Restore when mxbuild ran clean on the baseline but cannot run after the write.** exec.sh already
has both facts: the pre-flight measured a baseline (`BASE_KNOWN=1`) and the post-exec run exits
non-zero with no errors file. In that case, call `restore_snapshot` exactly as the `--patch` path
does, and log `↩ rolled back: mxbuild crashed on the written model`. Keep today's behaviour when the
baseline itself was not measured (that is the real "machine cannot build" case).
2. **Refuse a write when the baseline cannot be measured** and the last BUILD-LOG row for this model
is `gate could not run`, with one STOP line naming `bin/restore-mpr.sh` and the snapshot to restore.
`ALLOW_UNVERIFIED=1` stays the explicit override.
3. Reword the kept-write message: "Open in SP to verify" is not something an agent can do. Something
like `STOP: the model may not load. Restore with ./bin/restore-mpr.sh <snapshot path>` is actionable.

Probe that would falsify the claim (for the retest fixture): a fake mxbuild that exits 0 on the
baseline and exits 1 with no errors file after the exec; today exec.sh exits 1 and leaves the
written model in place; after the fix the model on disk equals the snapshot.

Related: `--patch` already restores on an unverified gate (header lines 25–29); PR #304 touches the
same file (per-project lock) but not this branch.
135 changes: 135 additions & 0 deletions contrib/inbox/2026-10-13-learned-widgets-and-data-sources.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Learned entries from a legacy-widget rebuild: File Uploader image mode, native-mapped keywords, tab pages, XPath comparands, multi-hop data sources

**From:** a field project (anonymised)
**Date:** 2026-10-13
**Kind:** learning
**Field evidence:** each entry below was hit while restating 34 pages of an existing app, through
`bin/exec.sh` (mxbuild delta gate) on mxcli v0.25.0, Mendix 11.14.0 Beta, Classic (Dojo) client,
toolkit c1efcb3. Error texts are verbatim; module/entity names are genericised.
**Proposed target:** per entry below (`skills/learned-file-upload-widget.md`,
`skills/learned-mdl-cannot-express.md`, `skills/learned-page-patterns.md`, `bug-logs/mxcli-bugs.md`)

---

## 1. File Uploader: image mode fails the build; files mode against a `System.Image` subtype works

Target: `skills/learned-file-upload-widget.md` §7 ("Image mode … was not tested").
Status: files mode is ALREADY covered there (PR #139); this adds image mode and the image-preview pattern.

```sql
fileuploader upPictures (
uploadMode: 'images',
associatedImages: association $currentObject/MyModule.Picture_Asset,
readOnlyMode: true,
enableCustomButtons: false
)
```

`mxcli check` passed. mxbuild: `CE0463 The definition of this widget has changed.` on
`File uploader upPictures`; the gate rolled back (rollback verified by DESCRIBE). The same widget with
`uploadMode: 'files'` builds clean (finding).

What built clean instead (finding, mxbuild 0 errors): uploads in **files** mode against an entity that
extends `System.Image` (which extends `FileDocument`), with a separate `dynamicimage` preview beside it;
read-only picture lists as a `listview` whose template holds a `dynamicimage`.

Also stale: `skills/learned-mdl-cannot-express.md` row 1 still says "A file upload control — nothing …
⛔ not buildable", which contradicts `learned-file-upload-widget.md`. Suggest pointing that row at the
File Uploader skill.

Related (finding): the installed FileDropper widget's definition lists `dataFileEntity` and
`dataContextAssociation` as known properties with no mapping; `check` accepts them, exec does not write
them, and mxbuild then fails. Upstream ask: refuse unmapped definition properties with MDL-WIDGET07.

## 2. `radiobuttons` and `image` can resolve to NATIVE mobile widgets on a web page

Target: `skills/learned-page-patterns.md` (or the widgets section of `learned-mdl-cannot-express.md`).
Status: NEW (no toolkit mention).

In this project `DESCRIBE WIDGET TYPE radiobuttons` resolves to `com.mendix.widget.native.radiobuttons`,
and `check` warns MDL-WIDGET10 that the label is dropped. The project's `image.def.json` maps `image` to
`com.mendix.widget.native.image.Image`; a probe wrote `image` with no properties. DESCRIBE prints existing
**web** radio groups with the same `radiobuttons` keyword, so re-running DESCRIBE output writes a native
widget into a web page (finding for the resolution and the warning; the radiobuttons case was not taken
through mxbuild).

Rule used: Boolean → `checkbox`, enumeration → `combobox`, images → `dynamicimage`. Before relying on a
keyword in an existing app, run `DESCRIBE WIDGET TYPE <keyword>` and check the id is a `web` widget.

## 3. Tab pages: `CaptionParams` and `Visible:` pass every gate and are not written

Target: `bug-logs/mxcli-bugs.md` as `## BUG-DRAFT-tabpage-captionparams-visible-dropped:`, plus a row in
`learned-mdl-cannot-express.md` next to the existing "count badge on a tab" row.
Status: NEW. (The existing row covers `Badge:`, which `check` refuses; these two are accepted.)

```sql
tabpage tpDue (Caption: 'Due today or earlier · {1}', CaptionParams: ({1} = DueCount)) { … }
tabpage tpOther (Caption: 'Unassigned · {1}', Visible: $currentObject/ShowOtherTab) { … }
```

`check`, exec and mxbuild all passed, with no MDL-WIDGET07. The stored caption is literal text (the tab
reads `· {1}`), and the tab page has no visibility setting: `ShowOtherTab` occurs 0 times in the unit.
Found by a BSON read-back after exec (finding).

Rule: no dynamic captions and no visibility on tab pages; put the count in a line inside the tab, and
put anything conditional in a container with `Visible:` (container visibility is written correctly).

## 4. DESCRIBE omits module-role visibility and layout-grid row visibility

Target and detail: see the inbox note "Durable rebuild of legacy-widget pages" (same date). Listed
here so triage sees it among the learned entries. Status: PARTLY (role visibility is known to be
unwritable; that DESCRIBE also hides it is not recorded).

## 5. Page XPath with a `$currentObject/<association>` comparand → CE0161 (BUG-39 on widgets)

Target: `bug-logs/mxcli-bugs.md` BUG-39 (add a confirmation), `learned-page-patterns.md`.
Status: PARTLY. BUG-39 records it for microflow retrieves only.

```sql
combobox cbLevel2 (
Attribute: Helper_Level2,
DataSource: database from "MyModule"."Level2"
where [MyModule.Level2_Level1 = $currentObject/MyModule.Helper_Level1]
)
```

`check --references` passed; mxbuild: 3 × `CE0161 Error(s) in XPath constraint`, one per cascading combo
box; rolled back (finding). Studio Pro accepts the same constraint.

Workaround that built: each combo box takes its options from a microflow data source that retrieves the
parent into a variable and filters on it.

## 6. Two-hop association data source: neither path form is writable → use a microflow data source

Target: `learned-page-patterns.md` (next to the BUG-04 multi-hop ContentParams section), and
`bug-logs/mxcli-bugs.md` as `## BUG-DRAFT-two-hop-datasource-path:`.
Status: NEW. BUG-20 (cross-module traversal writing a null `DestinationEntityId`) was fixed in v0.17.0;
this is a same-module, two-hop path on v0.25.0.

Short form, exactly as DESCRIBE prints it:

```sql
DataSource: $Parent/MyModule.A_B/MyModule.B_C
```

mxcli stored the last segment as an entity:
`CE1613 The selected entity 'MyModule.B_C' no longer exists.` (5 errors); rolled back.

Full form:

```sql
DataSource: $Parent/MyModule.A_B/MyModule.B/MyModule.B_C/MyModule.C
```

`check` passed; the mxbuild loader crashed:
`An error occurred when trying to set the 'DestinationEntity' property of a Entity ref step in a Page.`
(This crash is what exposed the exec.sh gap in the separate inbox note.)

So DESCRIBE output containing such a path is not re-executable. Rule: no multi-hop association path as a
data-view source; use a microflow data source that retrieves over each association in turn (built clean).

## Not included here

Other cases from the same run (Data grid 2 custom-content column losing its Attribute; XPath through a
reverse association needing read access mxbuild does not check; names `check --references` does not
validate) are upstream mxcli material and are left for a separate note.
Loading
Loading