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 .claude/skills/fix-issue/findings/cmd-mxcli.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -167,3 +167,4 @@
{"area": "cmd/mxcli", "date": "2026-10-05", "symptom": "`mxcli playwright check` (and the login of `run --page-check`/`--screenshot-user`) failed with \"node not found; the page check needs the Node that Playwright uses\" on a machine with no system node whose newest cached mxbuild is 11.15, although mxbuild 11.15 ships tools/node/<platform>/node", "cause": "resolveNodeForScript found mxbuild's bundled node through resolveNodeTooling, which also requires tools/node/rollup-runner.mjs; mxbuild 11.15 dropped the rollup runner (rspack only), so the lookup failed before it looked for the node binary", "file": "`cmd/mxcli/docker/screenshot_login.go` (resolveNodeForScript)", "fix": "look up the node binary directly with findNodeBinary(<modeler>/tools/node) instead of going through the rollup-runner check", "insight": "a helper that validates a whole toolchain was reused to find one binary in it; when the platform dropped an unrelated piece of that toolchain the reuse broke a caller that never needed it. Found by a smoke test on a machine whose newest mxbuild was 11.15, not by unit tests, which put node on PATH", "test": "cmd/mxcli/docker/screenshot_login_node_test.go TestResolveNodeForScript_RspackOnlyMxBuild (PATH emptied, 11.15 tools/node layout; fails with the old lookup)"}
{"area": "cmd/mxcli", "date": "2026-10-05", "symptom": "`mxcli run --local --watch`: a model change made while the app is still booting is never built — the app keeps serving the model from before it, `--watch` logs no `Change detected`, and a waiter on the change times out. Re-running the exec does nothing (byte-idempotent: it writes nothing, so nothing re-triggers the watcher)", "cause": "watchAndApply took its baseline `last := sourceMTime(...)` after the boot finished, so an edit landing between the boot build's model read and the watch loop's start was already folded into the baseline", "file": "`cmd/mxcli/docker/runlocal.go` (`RunLocal` bootSource, `watchAndApply`)", "insight": "A watcher's baseline must be the source time the last build was MADE FROM, taken before that build — not 'now' when the watcher starts. Same class as the earlier fix that stopped moving the baseline to time.Now() after an apply. Measured with the run-lifecycle integration test: exec a page while the detached run's state says the boot build is in progress; with the old baseline `run wait` times out after 2m (no build #2), with the fix it reports `applied: build #2 via reload`. Exposed by making `run wait` race-free on source time: a generation-counting wait could not have told the change was lost.", "refs": ["cmd_run_lifecycle_integration_test.go"]}
{"area": "cmd/mxcli", "date": "2026-10-06", "symptom": "`mxcli run --local --watch` on Mendix 10.24 / 11.6 dies after 5 minutes with `starting web client bundler: web client watcher timed out after 5m0s`, while the bundler's own log says `Bundling finished in 8999 milliseconds` — the bundle was done in 9s", "cause": "`parseBundlerStatus` (`cmd/mxcli/docker/webclient_watch.go`) only understood the modern-web-bundler protocol (`{\"protocol\":\"mx-modern-web-bundler\",\"type\":\"status\",\"payload\":{\"kind\":…}}`). The rollup-runner.mjs shipped with 10.24 and 11.6 predates it and writes `{\"code\":\"START\"|\"SUCCESS\"|\"ERROR\",\"payload\":…}` (ERROR payload an object with `message`, or a bare string when the config fails to load). Every status line was dropped as plain logging, so the first-build wait never saw a success", "file": "cmd/mxcli/docker/webclient_watch.go", "insight": "--watch had never worked on these versions: the parser has accepted only the new protocol since it was written (#349). Nothing exercised the watcher below 11.12 until the run-lifecycle integration test reached the nightly matrix — and it reached it only because ResolveMxForVersion substitutes ANY cached mxbuild for the test's default 11.13.0, so each matrix leg ran it against its own version. The runner's protocol is part of the mxbuild version contract: read tools/node/rollup-runner.mjs of the oldest supported version before assuming a stdout shape. Control: the lifecycle test on 11.6.8 with the parser reverted fails with the nightly's exact timeout (MXCLI_WEB_CLIENT_TIMEOUT=60s makes it fail in a minute); with the fix it passes", "refs": ["nightly 2026-10-06 (ako/mxcli run 37437089065, mendixlabs/mxcli run 37437605368)"]}
{"area": "cmd/mxcli", "date": "2026-10-06", "symptom": "`mxcli syntax rename` printed `Unknown topic: rename` and `mxcli syntax --json` had rename only for attributes, values and definitions, while `RENAME MICROFLOW <qn> TO <name>;` passed `mxcli check` and `mxcli help rename` documented it; an agent concluded MDL could not rename a microflow and copied, re-pointed and deleted it by hand", "cause": "The renameStatement rule (MDLParser.g4) and its executor (mdl/executor/cmd_rename.go) never got a SyntaxFeature in cmd/mxcli/syntax/; the only discovery path was the cobra `rename` subcommand's Long text, which `syntax` does not read. BySegmentMatch could not rescue it because no registered path has a `rename` segment", "file": "cmd/mxcli/syntax/features_misc.go", "insight": "Same class as the TABCONTAINER gap (widget_keywords_drift_test.go): an agent treats absence from `mxcli syntax` as absence from the language, so a statement the grammar accepts but the registry omits is effectively missing. Fixed with a `rename` topic plus a guard (rename_topic_test.go) that reads the renameTarget rule from the .g4 and requires every alternative in the topic, and see_also links from microflow/page/entity/move. The CLI subcommand and the MDL statement diverge (MDL also takes JAVA ACTION and WORKFLOW); the topic says so rather than implying parity. Control: with the topic reverted the built binary prints `Unknown topic: rename` verbatim and TestSyntaxRenameTopic fails; dropping the RENAME WORKFLOW line fails the grammar guard. A sweep for other top-level statements with no topic would be the cheap next step", "refs": ["mendixlabs/mxcli#1318"]}
1 change: 1 addition & 0 deletions .claude/skills/fix-issue/findings/mdl-backend.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,5 @@
{"area": "mdl/backend", "date": "2026-10-04", "symptom": "ako/mxcli#980: create or modify navigation on a native profile ignored its menu block, sync block, login/not-found page and on-sync-error while reporting 'updated'; describe printed a native nanoflow home as `home microflow` and bottom bar items without action or icon", "cause": "navPatchNativeProfile only patched home pages; nativeNavProfileFromGen read BottomBarItem.Page only; the grammar had no nanoflow home", "file": "`mdl/backend/modelsdk/navigation_write.go` navPatchNativeProfile, `navigation_read.go` nativeNavProfileFromGen, `mdl/executor/cmd_navigation.go` checkProfileClauses", "insight": "A profile-kind-specific writer must refuse every clause it does not apply (checked in the executor before anything is written, including profile creation), and describe must not print what exec would refuse — list it as comments. No native profile exists in any local fixture, so the reader is pinned with a hand-built gen document", "refs": ["#980"]}
{"area": "mdl/backend", "date": "2026-10-05", "symptom": "On Mendix 11.15 `TestMappingFixtureRoundTrip` fails for the blank app's own FeedbackModule mappings: describe -> exec adds `\"MessageDefinition\": \"\"`; and a mapping `with message definition` built on 11.15 fails `mx check` with CE0270 \"No root element could be found in the schema\"", "cause": "Mendix 11.15 REMOVED the mapping's `MessageDefinition` key (message definitions became `MessageDefinitions$MessageDefinition2` documents; the source moved to `MessageDefinition2` as `Module.MessageName`, measured with `mx convert` 11.15.0). The writer added the key unconditionally and the executor put the source in it, which 11.15 no longer reads", "file": "`model.ImportMapping`/`ExportMapping` (`MessageDefinition *string`, `MessageDefinitionSource()`), `mdl/backend/modelsdk/mapping_read.go` (`optionalStringFromRaw`) + `mapping_write.go`, `mdl/executor/mapping_messagedefinition.go` (`mappingMessageDefinitionKeys`, `messageDefinitionsAreDocuments`), `sdk/versions/mendix-1{0,1}.yaml` (`integration.message_definition_collection` max 11.14.99, `message_definition_document` min 11.15.0)", "insight": "**A key can be REMOVED by a version, not only introduced** \u2014 the #279 carry rule applies in both directions, so `MessageDefinition` is now a pointer like `MessageDefinition2`. The source key follows the stored document's shape on an update (a pre-11.15 document transplanted into an 11.15 project keeps its source in `MessageDefinition`, which is what kept the fixture's AgentCore/Email_Connector mappings green) and the project version only on a create. Caught by the new 11.15 nightly job, not by any fixture: none of the pinned documents was 11.15-shaped until the blank 11.15 app's own FeedbackModule mappings appeared in the base project", "refs": ["ako/mxcli#987", "ako/mxcli#279"]}
{"area": "mdl/backend", "date": "2026-10-06", "symptom": "`describe microflow` prints `-- Unsupported action: Microflows$SendEmailAction` for the Studio Pro 11.13+ Send Email activity, and MDL has no statement to create it", "cause": "No reader case, and no grammar/AST/builder/writer. The vendored gen type `genMf.SendEmailAction`/`EmailMessage` predates 11.12: it binds Subject/MessageBody* as expressions, but 11.13 deleted those and stores SubjectTemplate/MessageBodyPlainTextTemplate/MessageBodyHtmlTemplate (Microflows$StringTemplate) plus a CustomHeaders list the gen type has no field for", "file": "mdl/backend/modelsdk/microflow_send_email.go", "insight": "The gen type existing (the issue cited it as 'the storage side seems known') was a false lead: it is the pre-11.12 shape, so both directions are raw-keyed against a Studio Pro-saved document (ako/TestApp Email.EmailMF) and a key-for-key shape test (TestSendEmailActionToGen_MatchesStudioProShape) pins types and markers — it caught ConnectionTimeout being int64 and the empty CustomHeaders list needing marker 3. Version facts come from mendixmodelsdk 4.116 StructureVersionInfo (templates 11.12.0, plain Subject deleted 11.13.0), hence the 11.13.0 gate. Run negative controls through mxbuild before writing check rules: of the plausible rules, 'check server identity needs SSL' and the header-name rule are ACCEPTED by mxbuild 11.15.0-rc.4 (so warnings), while type faults are CE9528 for address/host/port/user (not the CE0117 exprcheck's E009 used to name — slots can now carry their own Mxbuild code). Attachment is a bare variable name (CE0109 on a wrong one). Shapes MDL cannot restate (auth document, pre-11.13 expression subject, non-SMTP) stay UnsupportedAction. Studio Pro's Test Email tab (TestEmailMessage) is written empty on rewrite, by design. An unedited describe->exec round trip is elided as Unchanged, so it cannot demonstrate loss; author-from-script is the meaningful control (old binary: `mismatched input 'email' expecting REST`).", "refs": ["mendixlabs/mxcli#1315"], "ce": ["CE9528", "CE0117", "CE0720", "CE0109"], "rules": ["MDL-EMAIL01", "MDL-EMAIL02", "MDL-EMAIL03", "E009"]}
{"area": "mdl/backend", "date": "2026-10-07", "symptom": "After `RENAME JAVA ACTION M.JA_Old TO JA_New` (or `mxcli rename java-action`), javasource/m/actions/JA_New.java still declares `public class JA_Old`, its constructor `public JA_Old(` and toString `return \"JA_Old\";` \u2014 javac: `class JA_Old is public, should be declared in a file named JA_Old.java`. `mxcli docker build` reports BUILD SUCCEEDED regardless", "cause": "Backend.RenameJavaSourceFile (mdl/backend/modelsdk/java_write.go) only os.Rename'd the file; nothing rewrote the generated parts that carry the action's name", "file": "mdl/backend/modelsdk/java_write.go, sdk/javaactions/rename.go", "insight": "A green mxbuild does NOT prove the javasource on disk compiles: mxbuild regenerates action stubs in the copy it builds (class, constructor, toString follow the model; user code, extra code and the import list are kept byte-for-byte, a mention of the old name inside them included), so the defect only shows where the on-disk file is compiled directly \u2014 `run --local --watch` hot reload, IDEs. The cheap probe is javac on the one file with runtime/bundles/com.mendix.public-api.jar on the classpath. The oracle for the rewrite is mxbuild itself: run the mxbuild binary on the project IN PLACE (not `docker build`, which uses a temp copy) and diff the javasource file \u2014 it changed exactly three lines; that pair is now testdata/mxbuild/JA_Renamed*. Rewrite those three spots, not a full GenerateSource, which would reformat a Studio Pro-authored file. Control: unfixed binary \u2192 javac error above; fixed \u2192 javac exit 0, docker build BUILD SUCCEEDED", "refs": ["follow-up to mendixlabs/mxcli#1318"]}
{"area": "mdl/backend", "date": "2026-10-06", "symptom": "Opening a change in Studio Pro's Changes panel throws `System.InvalidOperationException: Objects with ID … of type Forms$MicroflowParameterMapping do not have the same properties. baseNames = Expression, Parameter, Variable; newNames = Parameter, Expression` after `alter page … set ('onClickAction': call microflow M.F(P = $P)) on w`. `mx check` is 0 errors; the page opens. Second, silent half: `$P` (a page parameter) is stored as the Expression \"$P\", which Studio Pro does not bind (CE1571, the #1140 form)", "cause": "(1) `bindParameterMappingValue` sets only Expression for an Expression-bound argument; the gen `Variable` Part stays unset and the encoder omits an unset Part on a new element, so the key vanished. Studio Pro writes all three keys, nulling the unused slot, and its merge library compares key sets per $ID — the canon $ID carry makes the new mapping the SAME object as the stored one, so the diff sees two shapes of one element. (2) `convertASTAction` (ALTER PAGE SET Action and named action slots) built its pageBuilder with no paramScope/localVariables, so `classifyFlowArgValue` could not recognise a page/snippet parameter", "file": "`mdl/backend/modelsdk/widget_write.go` (RegisterTypeDefaults NullFields Variable on Forms$MicroflowParameterMapping + Forms$NanoflowParameterMapping), `mdl/executor/cmd_alter_page.go` (convertASTAction seeds ParamScope, storedPageVariables, isSnippet from the mutator)", "insight": "**`mx diff base.mpr new.mpr out.mpr` reproduces Changes-panel crashes headless** — it runs the same MergeLib DifferenceComputer and prints the identical \"do not have the same properties\" line (exit 129). Base = a copy of the project before the write, made by the FIXED build so it has Studio Pro's key set. That turns a Windows-GUI-only symptom into a two-binary A/B on Linux. **The base must share the $ID** — the crash needs a paired object, which the identity carry supplies; a fresh element is only an add and never trips it, so a create-only repro passes on broken code. **Register the null slot, don't set it at the call site**: `Part.Set(nil)` still encodes as omitted, and there are three construction sites (action, nanoflow action, nanoflow data source) — NullFields covers all and any future one. **A builder constructed outside CREATE PAGE is the recurring hole for #1140-style classification**: INSERT/REPLACE already seeded the scope from the mutator, SET did not; grep for `&pageBuilder{` and check each seeds paramScope + localVariables + isSnippet. Measured on mxbuild 11.12.2: buggy mx diff exit 129 with the reported line, fixed exit 0; mx check 0 errors on both", "refs": ["mendixlabs/mxcli#1317", "mendixlabs/mxcli#1180", "mendixlabs/mxcli#1140"], "ce": ["CE1571"]}
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- **`call rest service` takes its settings as one property list** (ADR-0013) — the activity's dialog settings go in one `( Key: value, … )` list after the URL, keyed as the consumed REST service names the same concepts: `$Html = call rest service get 'https://example.com' (Headers: ('Accept': 'text/html'), Authentication: basic (Username: $User, Password: $Password), Timeout: 300) returns String;`. `Body:` is `template '…' [with ({1} = …)]`, `mapping M.EMM from $Var`, `binary <expr>` or an expression. The method, URL, `returns …` and `on error …` stay words. An unknown or repeated key, or a value of the wrong shape, is an error. `describe` writes this form. **Migrating a script:** nothing breaks — the clauses `header 'N' = v`, `auth basic $u password $p`, `body …` and `timeout n` (**MDL-DEPR720**) still parse and store the same activity, `check` / `exec` warn, and `mxcli fmt --upgrade` rewrites them; a statement cannot mix the two forms. ADR-0013 makes this the rule for every new microflow activity and every activity with several settings.
- **`run --local --page-check` signs in with `--screenshot-user` without `--screenshot`, and checks all pages in one browser** — the sign-in only ran when `--screenshot` was also given, so `--page-check --screenshot-user U` reported every secured page as the login page. The verdict also no longer counts a list view's "No items found" placeholder or a data grid's header as rows, ignores the demo-user switcher's "Select user" heading, and reports a failed same-origin request (`HTTP 560 POST /xas/`) instead of the duplicate "Failed to load resource" console line. The login script used by `--screenshot-user` falls back to `/login.html` when the app root does not show the sign-in form, and finds Playwright the way the page check does (no `playwright` CLI on `PATH` needed).

- **`mxcli rename` takes `java-action` and `workflow`** — the subcommand offered eight of the ten targets `RENAME` accepts; Java actions and workflows could only be renamed from MDL. `mxcli rename -p app.mpr java-action M.JA_Old JA_New` also renames the `.java` source file, and both update `docs/brain/` anchors like the other types. The type list is now held to the grammar by a test, so a new `RENAME` target cannot be left out of the subcommand again. `mxcli syntax rename` documents both forms. (follow-up to mendixlabs/mxcli#1318)

### Fixed

- **`RENAME JAVA ACTION` renames the Java class, not just the file** — renaming `M.JA_Old` to `JA_New` moved the source to `JA_New.java` but left `public class JA_Old`, its constructor and its `toString` inside it, which javac rejects (`class JA_Old is public, should be declared in a file named JA_Old.java`). A full build hid it, because mxbuild regenerates the stub in its own copy; `run --local --watch` hot reload and IDEs compile the file on disk. Those three places now follow the rename exactly as mxbuild rewrites them; user code and extra code are left as written. Applies to `mxcli rename java-action` too.
- **`run --local --watch` starts on Mendix 10.24 and 11.6** — it waited the full web-client timeout (5 minutes) for a bundle that had finished in seconds, then failed with `web client watcher timed out`. The rollup runner those versions ship reports its status as `{"code":"SUCCESS"}` rather than the modern-web-bundler protocol mxcli was reading; both are now understood, including that runner's error reports.
- **`run --local --watch` no longer loses a change made while the app boots** — the watch loop took its baseline after the boot, so a model written during the ~15 s boot was never built and the app kept serving the model from before it. The baseline is now the source time the boot build was made from, so that change is built on the first tick.

Expand Down
Loading
Loading