Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
585f5f0
fix(pages): read a pluggable widget's microflow datasource from Micro…
claude Jul 30, 2026
78699b8
fix(pages): fail the write when a pluggable widget's child is dropped
claude Jul 30, 2026
6e59e0e
fix(pages): describe a flow datasource's context as the entity it ret…
claude Jul 30, 2026
48feadb
fix(domainmodel): read an external entity's OData source (#782)
claude Jul 30, 2026
830263a
fix(odata): keep allow-create-change-locally across an external-entit…
claude Jul 30, 2026
648b4a5
fix(microflows): serialize break/continue events, unbreaking loops (#…
claude Jul 30, 2026
5bfe84d
fix(test): leave the project as `mxcli test` found it (#802/#803/#804)
claude Jul 30, 2026
13332e5
fix(microflows): size a loop box to the space its body actually occup…
claude Jul 30, 2026
77327ba
Merge pull request #58 from ako/claude/mxbuild-diagnostics-spike-emta6h
ako Jul 30, 2026
55d9b84
Merge pull request #59 from ako/claude/pages-datasource-describe-795
ako Jul 30, 2026
1d1b931
Merge remote-tracking branch 'origin/main' into claude/odata-external…
claude Jul 30, 2026
00463ee
Merge remote-tracking branch 'origin/main' into claude/test-runner-cl…
claude Jul 30, 2026
34cfa13
Merge remote-tracking branch 'origin/main' into claude/microflow-loop…
claude Jul 30, 2026
9a12f3d
Merge pull request #60 from ako/claude/odata-external-entity-782
ako Jul 31, 2026
e14b6b3
Merge remote-tracking branch 'origin/main' into claude/test-runner-cl…
claude Jul 31, 2026
f7ff072
Merge remote-tracking branch 'origin/main' into claude/microflow-loop…
claude Jul 31, 2026
27aaedf
Merge pull request #61 from ako/claude/test-runner-cleanup-802-804
ako Jul 31, 2026
1f6a26e
Merge pull request #62 from ako/claude/microflow-loop-791-790
ako Jul 31, 2026
9997bcd
Merge branch 'mendixlabs:main' into main
ako Jul 31, 2026
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
6 changes: 6 additions & 0 deletions .claude/skills/fix-issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ to the symptom table below, so the next similar issue costs fewer reads.

| Symptom | Root cause layer | First file to open | Fix pattern |
|---------|-----------------|-------------------|-------------|
| `mxcli test` leaves the project mutated: Security Level changed, after-startup pointing at the deleted `MxTest.TestRunner`, and only a `Warning:` line about it. Or: an empty `MxTest` module accumulates after every run | Three defects in one teardown. (a) `getAfterStartup` trimmed quotes *before* trailing punctuation, so a DESCRIBE SETTINGS line ending in `,` yielded `Module.Flow',` and the restore statement was unparseable; (b) cleanup dropped only the microflow, not the module it created; (c) the Security Level was forced OFF and restored to a hardcoded PRODUCTION | `cmd/mxcli/testrunner/runner.go` (`parseSettingValue`, `quoteMDLString`, `projectState`, `setupCommands`, `cleanupCommands`) | Strip trailing `,;` before unquoting; re-emit via `quoteMDLString` (doubling embedded quotes, never backslashes); capture a `projectState` before the first mutation and restore from it; drop the module only when the run created it (a pre-existing `MxTest` is the user's); leave Security Level alone entirely; return cleanup errors instead of printing warnings, and fail the run when they occur. Command lists are pure functions so the restore is testable without a project or Docker. Issues #802/#803/#804 |
| `create or modify external entities` silently resets a per-entity setting the user had changed (e.g. allow-create-change-locally) | `applyExternalEntityFields` stamps every field on both the create and the update path, so anything not derivable from the OData contract was overwritten with a default | `mdl/executor/cmd_contract.go` (`applyExternalEntityFields`) | Separate contract-derived fields (Countable/Creatable/Deletable/Skip/Top — refresh from metadata) from local modelling choices (CreateChangeLocally — leave alone; a new entity arrives zero-valued, which is Mendix's default). Issue #782 |
| An **external (OData) entity** loses its remote settings on any read-modify-write — `describe external entity` says `is not an external entity (source: )`, and `alter entity … set allow_create_change_locally = true` reports success but the flag stays off. Works under `--engine legacy` | `entityFromGen` recognised only `DomainModels$OqlViewEntitySource`, so the three `Rest$OData*` sources read back as no source at all: `Source` empty, every remote field zeroed. The write path was fine — it switches on `e.Source`, which the read never populated | `mdl/backend/modelsdk/domainmodel.go` (`entityFromGen`'s source switch, `odataKeyFromGen`) | Mirror the legacy parser (`sdk/mpr/parser_domainmodel.go`) for all three flavours: RemoteEntitySource (capabilities + CreateChangeLocally + key), EntityTypeSource (type name + IsOpen + key), PrimitiveCollectionEntitySource (service only). `Updatable` has no gen accessor and the writer does not emit it — leaving it zero is symmetric. **Check the read side first when a write-path field "does not stick"**: a switch on a field the read never fills looks like a write bug. Repro `mdl-examples/bug-tests/782-external-entity-create-change-locally.mdl`. Issue #782 |
| A pluggable widget's datasource (or child widget, or client action) is **silently dropped at write time** — `exec` prints `Created page` but the widget lands with the piece missing, and only a `log` line mentions `not yet supported — rerun with MXCLI_ENGINE=legacy` | The converter *does* return an error, but it travels through `widgetobj.ChildSerializer`, whose methods return BSON with no error channel (the `TODO(shared-types)` in `mdl/backend/widgetobj/builder.go`), so the caller logged it and returned nil | `mdl/backend/modelsdk/widget_pluggable_write.go` (`recordChildSerializeErr`, `takeChildSerializeErr`) + the drains in `page_write.go` / `snippet_write.go` | Record the failure in a package-level accumulator and drain it at every page/snippet write entry point, so the statement fails instead of the write succeeding with data missing (ADR-0004: refuse, don't drop). The real fix is the deferred `ChildSerializer` interface change; until then, any **new** write entry point that builds pluggable widgets must drain too. Repro `mdl-examples/bug-tests/795b-flow-datasource-context-entity.mdl` |
| `describe page` reports the wrong context entity under a data container bound to a microflow/nanoflow — `-- Context: $currentObject (Module.GetOrders)` names the *flow* instead of the entity it returns | `widget.EntityContext = widget.DataSource.Reference` is correct for a database source (reference *is* the entity) and wrong for a flow source (reference is the flow's qualified name) | `mdl/executor/cmd_pages_describe_flowcontext.go` (`dataSourceEntityContext`, `flowReturnEntity`) + the five assignment sites in `cmd_pages_describe_parse.go` | Resolve the flow's return type via `ListMicroflows`/`ListNanoflows` + `getHierarchy().GetQualifiedName`, taking the entity from an Object/List return type; fall back to the reference when the flow is unresolvable or returns a scalar, so the result is never worse than before. Note `GetRawUnitByName` is unimplemented on the modelsdk engine — the list+hierarchy path is the one that works |
| `describe page` omits a **pluggable** widget's `DataSource` when it is bound to a microflow (`datagrid g1 {` with no DataSource), while a `database from` source describes fine — re-applying the output recreates the grid unbound | A `Forms$MicroflowSource` stores the name in the nested `Forms$MicroflowSettings` (`MicroflowSettings` → `Microflow`), which is what the write path and Studio Pro emit; the reader looked up a top-level `Microflow` key, got `""`, and returned no datasource. The describe *formatter* was correct all along — read bug only | `mdl/executor/cmd_pages_describe_pluggable.go` (`microflowSourceRef`, `nanoflowSourceRef`, `extractDataGrid2DataSource`, `extractGalleryDataSource`, `parseCustomWidgetDataSource`) | Read the nested settings with a top-level fallback, via one shared helper — there were four divergent copies of this lookup and two were wrong. `Forms$NanoflowSource` was missing entirely from the DataGrid2/Gallery switches; add it alongside. Do **not** touch `CustomWidgets$CustomWidgetNanoflowSource` (a different metamodel type whose `Nanoflow` really is top-level) or the `Forms$MicroflowAction` reads (actions, not datasources). Repro `mdl-examples/bug-tests/795-datagrid-microflow-datasource-describe.mdl`. Issue #795 |
| Any `ALTER SETTINGS` (any section) reports success but the Default configuration's **Custom settings** are gone, **Tracing** is reset, and every **constant override** shows blank in Studio Pro — Integer/Long constants then fail the build | The configuration was re-serialized from `model.ServerConfiguration`, which carries only the modelled fields, so CustomSettings/Tracing/OpenAdminPort/OpenHttpPort were dropped, the list version markers downgraded 3→2, and overrides were written with a flat `Value` instead of the nested `SharedOrPrivateValue` the platform reads | `mdl/settingsoverlay/settingsoverlay.go` (`Configurations`, `ServerConfiguration`, `ConstantValues`) — called by both `mdl/backend/modelsdk/settings_write.go` and `sdk/mpr/writer_settings.go` | Overlay onto the raw document instead of rebuilding: write only the fields the read path populates, take each list's marker from what is stored, and update a constant override in the slot it already occupies (nested if nested, flat if flat). New override → nested. New configuration → clone a sibling's shape, empty its collections, mint a fresh `$ID`. Refuse the write when `RawParts` is empty. Repro `mdl-examples/bug-tests/801-alter-settings-preserves-configuration.mdl`. Issue #801 |
| `ALTER SETTINGS` / `CREATE CONFIGURATION` prints "Updated …" but `DESCRIBE SETTINGS` shows the old value — an Integer property was given a non-numeric value, or a Boolean anything other than `true` | `strconv.Atoi`'s error was discarded (`if v, err := …; err == nil`) so the assignment was skipped while the caller still printed success; the boolean form compared against `"true"`, silently mapping every other spelling to false | `mdl/executor/cmd_settings.go` (`settingsInt`, `settingsBool`) + `mdl/executor/validate_settings.go` (`typedSettingsKeys`, MDL-SET01/MDL-SET02) | Parse through a helper that returns a validation error naming the setting and the offending value, and register the property in `typedSettingsKeys` so `mxcli check` and the LSP flag it before the project is opened for writing. `TestTypedSettingsKeys_MatchExecutor` guards the table against drifting from the executor's switch. Repro `mdl-examples/bug-tests/805-alter-settings-typed-values.fail.mdl`. Issue #805 |
| MCP op reports `MCP error -32000: Request timed out` but the page/document/entity EXISTS in Studio Pro afterwards | Studio Pro's ~30s server-side per-call limit fires while the op still applies — a client false failure, not a server rejection | `mdl/backend/mcp/timeout.go` (`isTimeoutErr`, `timeoutVerifyDelay`, `pedUpdateVerify`, `pedDocumentExists`) | Verify-on-timeout, never blind-retry a non-idempotent op: idempotent root-replace (`pgWritePage`) retries once; creates confirm via `ped_find_document`; entity adds confirm via a shallow `/entities` read. Unverified → error with save-before-re-run guidance (a blind re-run from a fresh session duplicates elements) |
Expand Down
4 changes: 2 additions & 2 deletions .claude/skills/mendix/create-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,8 @@ column colActions (caption: 'Actions') {
|--------|-------------|
| `datasource: database from Module.Entity` | Direct database query |
| `datasource: $Variable` | Variable bound (requires DATAVIEW parent with entity) |
| `datasource: microflow Module.GetData()` | Microflow datasource |
| `datasource: nanoflow Module.GetData()` | Nanoflow datasource (client-side, no server roundtrip) |
| `datasource: microflow Module.GetData` | Microflow datasource — no `()`, the name alone |
| `datasource: nanoflow Module.GetData` | Nanoflow datasource (client-side, no server roundtrip) — no `()` |
| `datasource: selection widgetName` | Listen to selection from another widget |
| `datasource: association path` | Retrieve by association from context (ByAssociation) |
| `datasource: $currentObject/Module.Assoc` | Sugar for `association` — same semantics, reads more naturally |
Expand Down
16 changes: 13 additions & 3 deletions .claude/skills/mendix/test-microflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,23 @@ mxcli test tests/ -p app.mpr --verbose
The test runner uses the **after-startup microflow** pattern:

1. Parses test files and extracts test blocks with annotations
2. Generates a `MxTest.TestRunner` microflow with assertion logic
3. Sets security OFF and after-startup to `MxTest.TestRunner`
2. Records the project's current after-startup microflow, and whether an `MxTest`
module already exists
3. Generates a `MxTest.TestRunner` microflow with assertion logic and points
after-startup at it
4. Builds the project and restarts the Docker runtime
5. Captures structured `MXTEST:` log lines for pass/fail
6. Restores original security and after-startup settings
6. Restores the original after-startup setting and removes the generated runner —
the whole `MxTest` module when the runner created it, otherwise just the
`TestRunner` microflow
7. Outputs results (console, JUnit XML)

The project's **Security Level is not modified**. The after-startup microflow runs
in an administrative context and is not subject to it, and forcing it off breaks
projects whose published REST/OData services use custom authentication. If a
cleanup step fails the run reports an error and names what was left changed —
the project is modified, so it must not read as a clean pass.

---

## Writing Good Tests
Expand Down
Loading
Loading