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/mdl-executor.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,7 @@
{"area": "mdl/executor", "date": "2026-10-05", "symptom": "On Mendix 11.15, `TestMxCheck_DoctypeScripts/40-message-definition-examples.mdl` fails with CE0270 \"No root element could be found in the schema\" at MsgTest.IMM_Order. On a converted or Studio Pro 11.15 project, `list message definition collections` finds nothing, and `describe import mapping` prints no source", "cause": "Mendix 11.15 replaced `MessageDefinitions$MessageDefinitionCollection` with one `MessageDefinitions$MessageDefinition2` document per definition. `mx convert` turns the collection into a `Projects$Folder` with the same unit ID and name, and the ExposedEntity tree is byte-identical apart from $IDs. mxcli read and wrote only collections, so on 11.15 there was nothing to resolve a mapping against", "file": "`mdl/backend/modelsdk/messagedefinition_document.go` (read/write, reusing the collection's exposedNodeFromGen/messageNodeToGen), `mdl/executor/cmd_messagedefinition_documents.go` (create/describe/drop/list + version refusals), `mdl/executor/mapping_messagedefinition.go` (`findMessageDefinition(ctx, ref)` returns the stored reference), `cmd_messagedefinitions.go` (alter target resolution, association-drop guard), grammar `createMessageDefinitionStatement` / `DROP|DESCRIBE MESSAGE DEFINITION` / `LIST MESSAGE DEFINITIONS`", "insight": "**`mx convert` is the oracle for a storage change.** Converting a known 11.14 project gives a Studio Pro-authored 11.15 reference for the new shape and for how references move, so no guessing is needed. **Under the revert, 11.15 fails earlier than CE0270.** A two-part name written into the old `MessageDefinition` key makes mxbuild refuse to load the project (`StorageLoadException: ... is not a valid OldMessageDefinitionIdentifier`). The old key is typed as a three-part identifier even though 11.15 no longer reads it. The doctype script uses `-- @version: ..11.14` / `11.15+` sections, and the 11.15 mapping needs a different name from the older section's, because a plain `check` sees both sections and reports MDL-DUPDEF", "refs": ["ako/mxcli#987"]}
{"area": "mdl/executor", "date": "2026-10-06", "symptom": "v0.25.0: `describe microflow` 4-8x slower than v0.24.0 on a flow arranged by hand in Studio Pro, and the cost grows faster than the flow (60 if/else blocks: 4.7 s -> 35.6 s); flows laid out by mxcli barely change", "cause": "The canonical describe's layout derivation (#748) re-renders the description every round, and a hand-laid flow takes gatedLayoutRounds+2 = 8 rounds. Each render re-ran the stored flow's split/merge analysis (findSplitMergePoints, labelCrossedMerges) and the body warnings (postDominators via microflowgraph.Analyze) - superlinear, and independent of which annotations the round keeps", "file": "`mdl/executor/cmd_microflows_derived_layout.go` (flowAnalysisMemo, installed by useDerivedFlowLayout, rebuildOnly view for the rounds), `mdl/executor/cmd_microflows_show.go` (findSplitMergePoints / labels / microflowBodyWarnings go through it)", "insight": "**Profile before believing the issue's theory.** The report blamed the round count, but rounds were already capped at 8; the rebuild itself was cheap. The CPU profile showed the cost was the *render* inside each round - the stored flow's graph analysis, redone per round. Memoising it per describe (keyed on the stored collection pointer) and leaving the `-- WARNING` comment lines out of rounds (the rebuild never reads them) gave 14.2 s -> 1.7 s at 60 blocks, with the printed description byte-identical to the unfixed build. A wall-clock assertion would be flaky; the test counts split/merge analyses per describe (18 over 8 rounds before, 2 after), with the round count as its control", "refs": ["mendixlabs/mxcli#1301"]}
{"area":"mdl-executor","date":"2026-10-05","symptom":"A view entity selecting a **non-localized** DateTime column (Studio Pro's \"Localize\" unticked — the normal choice for a calendar date) passes `mxcli check --references` and exec, then `mx check` fails CE6770 \"View Entity is out of sync with the OQL Query.\" The view attribute was always written LocalizeDate = true, and MDL has no spelling for the flag, so describe -> exec re-broke it on every run","cause":"execCreateViewEntity built every view attribute with convertDataType, whose DateTime is Mendix's default LocalizeDate = true; nothing looked at the attribute the column reads","file":"`mdl/executor/oql_view_localize_date.go` (viewDateTimeLocalize), `mdl/executor/cmd_entities.go` (execCreateViewEntity); test `mdl/executor/view_entity_localize_date_test.go`; bug-test `mdl-examples/bug-tests/1297-view-entity-non-localized-datetime.mdl`","insight":"**Derive, don't add syntax**: on a view entity the column's localization is a property of the query, so the fix reads it from the source attribute and describe -> exec round-trips (`Unchanged`) with nothing new to spell — same reasoning as the view association (the column is the declaration). **Measure the shapes before choosing the scope**: one project, one view per shape, mxbuild 11.12.5 — pass-through, `s/Attr`, MIN, MAX and CASE over a non-localized source are ALL CE6770 when written localized and 0 errors when patched to false, so a pass-through-only rule (the obvious one, mirroring the string-length rule) would have fixed the report and left MAX(date) broken. Sources that disagree (coalesce of a localized and a non-localized column) are unmeasured and keep the default. **Repro needs a Studio Pro-authored flag**: mxcli writes every persistent DateTime localized, so the bug is invisible from MDL alone — the reporter's pymongo patch flipping Sale.SaleDate is the cheapest stand-in. Control: stubbing the assignment fails 5 of 6 cases with `LocalizeDate = true`; pre-fix binary on the same project gives 5 × CE6770, fixed gives 0","refs":["mendixlabs/mxcli#1297"]}
{"area": "mdl-executor", "date": "2026-10-07", "refs": ["mendixlabs/mxcli#1324"], "symptom": "A button directly inside data view `dvGate` passing `$dvGate` (its own container, by widget name) passes `check --references` and `exec`, then `mx check` reports `[error] [CE0117] \"Error(s) in expression.\" at Action button 'btnOwn'`. The same `$dvGate` from a button in a NESTED data view builds clean", "cause": "No check modelled which data-container names are in scope. A container's widget-name variable exists only for the data containers nested below it; in its own context the object is $currentObject", "file": "`mdl/executor/validate_page_button_context.go` (`checkButtonContextTree` carries `nearest`; `checkOwnContainerName`, `exprReadsVariable`, MDL-BUTTON02)", "insight": "**Measure the neighbours before writing the rule — the report's shape is one of six.** One probe page with ten buttons through `mx check` 11.14.0 settled the whole rule in one build: own data view, own data view through a plain container, a control bar of a grid inside it, `$dv/Attr`, a list view's own name from its item, a grid's own name from its column and a gallery's from its template are all CE0117; the enclosing name from a grid column or list item is clean, and so is a grid's own name from its control bar (the selection). So the rule is 'the NEAREST data container's name', and it moves exactly like the existing MDL-BUTTON01 context (a control bar takes its context from above its grid) — which is why it went into the same walk rather than a new one. A data-view-only fix, the literal reading of the issue, would have missed four of the six. **Unmeasured, deliberately not flagged:** the same name in a nested widget's DATA SOURCE arguments and in widget expressions (visibility, dynamic text). Control: stubbing the call fails all three tests with `got []`; end to end the fixed binary refuses the issue's script ('Nothing was written') and the suggested `$currentObject` rewrite builds at 0 errors. Repro `mdl-examples/bug-tests/1324-own-data-container-name.fail.mdl`; tests `validate_page_own_container_name_test.go`"}
{"area": "mdl-executor", "date": "2026-10-07", "symptom": "`retrieve … where starts-with(Name, 'MS-' + $Key)` passes `check --references` and `exec`, then mx check CE0161 \"Error(s) in XPath constraint.\" — the same `+` at the constraint's top level (`Name = 'X-' + $Key`) builds clean", "cause": "MDL091 only matched expression-only function NAMES (startsWith/endsWith/getKey) by regex over the rendered XPath string; an operator inside a function ARGUMENT is a different expression construct the string regex never looked for", "file": "`mdl/executor/validate_microflow.go` (`xpathFunctionArgumentOperators`, `checkXPathFunctionArgumentOperators`, MDL091)", "insight": "Walk the `RetrieveStmt.Where` AST, not the XPath string: a call's argument that IS a `+`/`-` BinaryExpr is CE0161 (after unwrapping ParenExpr AND `SourceExpr` — the retrieve where-clause is wrapped in a SourceExpr, and a walker that misses it silently matches nothing, which is how the first cut passed nothing while compiling clean). Only the unbracketed form reaches the writer: the bracketed `[…]` grammar already refuses `+` in a function argument as a parse error. Measured on mxbuild 11.14.0 with a stubbed-out check to force the fault in: `+` and `-` in a string argument CE0161 (also nested in `not()`), `year-from-dateTime(Due) = $N - 1` clean. `*`/`div`/`mod` deliberately NOT flagged — the only argument they fit is numeric, and the control `contains(Name, $N)` is already CE0161 with no operator, so no measurement isolates the operator; MDL091 is exec-enforced, so an unmeasured operator would be a write barrier on a guess. Tests `TestValidateMicroflow_XPathFunctionArgumentOperator`, `TestCheckExecAgree_RetrieveConstraintFunctionArgumentOperator`; repro `mdl-examples/bug-tests/1326-xpath-function-argument-operator.fail.mdl`", "refs": ["mendixlabs/mxcli#1326", "mendixlabs/mxcli#1213"]}
{"area": "mdl/executor", "date": "2026-10-07", "symptom": "`set $Cursor = $Next;` where both are OBJECT variables (Reference retrieves followed from the FROM entity) passes `check --references` and `exec` (\"Created microflow\"), is written as a Microflows$ChangeVariableAction, and mx check reports [CE7247] \"Variable 'Cursor' does not have a primitive type.\" Natural to write when walking a parent chain in a `while` loop", "cause": "`set` on a plain variable had two outcomes: Change list Replace for a known list (ako/mxcli#949) and Change variable for everything else, so an object fell into the primitive-only action. Mendix has NO action that reassigns an object variable. Check could not have caught it even with a guard: the check-time validator (validateFlowBody) typed every association retrieve as `List of <assoc>`, because it had no association multiplicity, so the forward-Reference object read as a list", "file": "`mdl/executor/cmd_microflows_builder_actions.go` (`objectVariableType`, `refuseSetOnObject`), called from `cmd_microflows_builder_graph.go` (exec) and `cmd_microflows_builder_validate.go` (check); `validate.go` passes `checkAssociationShapes(ctx, sc)` into `validateFlowBody` so `forwardReferenceTarget` types a forward Reference retrieve as an object", "insight": "A refusal is only as good as the variable typing under it, and check and exec type variables differently: exec asks the backend for the association, check guessed `List of`. A guard added to the check validator alone passes the reported repro because the object it should refuse is, to check, a list. Write the check-path test against the REPORTED shape (association retrieve), not an object parameter, or it goes green while the issue stays open. Reuse `checkAssociationShapes` (project + script associations, built for MDL-ASSOCDS01) rather than a new lookup. Type only the unambiguous shape (Reference followed from FROM, not a self-association): Mendix types a self-association retrieve as a list, and that `set` is a valid Change list Replace. Measured on 11.14.0: faulted build exec -> CE7247; the self-association and recursive sub-microflow forms -> 0 errors. Plain `check` with no -p does not run validateFlowBody at all, so a `.fail.mdl` cannot pin this", "refs": ["mendixlabs/mxcli#1323", "ako/mxcli#949"], "ce": ["CE7247"]}
{"area": "mdl/executor", "date": "2026-10-07", "symptom": "Plain `mxcli check` (no -p) passed `set $X = $Y;` on an OBJECT variable (entity parameter, create, retrieve first, loop iterator, head) even after check --references and exec refused it; the build then failed with CE7247. The bug-test could not be a .fail.mdl because check-mdl runs plain check", "cause": "Plain check runs only the MDL0xx rule set (ValidateProgram -> ValidateMicroflow/ValidateNanoflow); validateFlowBody, where the first #1323 refusal lived, runs only under --references (validate.go) and in exec. A refusal added to one validator is invisible to the other path", "file": "`mdl/executor/validate_microflow_set_object.go` (`checkSetOnObjectVariable`, MDL-SET01), wired from `validate_microflow.go` (validate) and `validate_nanoflow.go`; `cmd_microflows_builder_validate.go` now reports only `assocObjectVars`", "insight": "Know which validator each entry point runs before adding a refusal: plain check = ValidateMicroflow; --references = that PLUS validateFlowBody; exec = the builder (plus execEnforcedMicroflowRules). Putting the rule in both check validators made --references print it twice; split by what each can see — the rule takes every object visible in the text, validateFlowBody only the association-typed ones that need the project. Measure every producer a rule judges, with the faulted build: all five plus a nanoflow gave CE7247 on 11.14.0, but a PARAMETER gives different wording, \"Parameter 'A' cannot be changed.\" — and that wording also fires for a PRIMITIVE parameter (`set $N = 1` on `$N: Integer`), a separate unrefused gap the object-only rule does not cover. Leave `find` out of the object producers: it is also the String function", "refs": ["mendixlabs/mxcli#1323", "ako/mxcli#1011"], "ce": ["CE7247"], "rules": ["MDL-SET01"]}
Expand Down
1 change: 1 addition & 0 deletions .claude/skills/mendix/cheatsheet-errors/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ Run with `-p` for the fullest coverage.
| CE0104 | Action activity is unreachable | Code after RETURN |
| CE0105 | Must end with end event | Missing RETURN |
| CE0117 | Error in expression | Unqualified association path |
| CE0117 | …on a button inside a data container | The button passes the container it sits in by its widget name (`$dvGate` inside `dvGate`). That name is a variable only for containers nested below it; use `$currentObject` there. MDL-BUTTON02 |
| CE1571 | No argument selected for parameter | A microflow/nanoflow call with a parameter nothing fills — as a `datasource:` **or** an `action:`. Give it an argument (`action: call nanoflow M.NF(P = $value)`), or nest the widget in a data container of the parameter's type. `check -p` reports both |
| CE1571 | …in a control bar | A control bar is **not** row-scoped, so the grid's row does not fill it: pass the grid's selection (`$dgOrders`, with `Selection:` set) or move the widget into a column. `$currentObject` there is MDL-BUTTON01 |
| CE1834 | The 'Page' property is required | Workflow user task without a `page` — `check` flags MDL-WF01 |
Expand Down
9 changes: 9 additions & 0 deletions .claude/skills/mendix/create-page/reference/widgets.md
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,15 @@ A **container** takes an argument list exactly like an `actionbutton` does — t
two share one action grammar. Reaching for a button because a container "cannot
pass parameters" changes the rendering for no reason (mendixlabs/mxcli#1082).

**A data container's name is a variable only *below* it.** `$dvOrder` reads data
view `dvOrder`'s object from a data view, list or grid nested inside it. In the
container's own context — directly inside it, through plain containers, or in the
control bar of a grid inside it — the object is `$currentObject`, and
`$dvOrder` is **CE0117** "Error(s) in expression." (`mxcli check` reports
MDL-BUTTON02). The same goes for a list view, gallery or grid read by its own
name from its item or row. A grid's own name *is* valid from its control bar —
that is the selection, above (mendixlabs/mxcli#1324).

### Charts (Charts.mpk — ColumnChart / BarChart / AreaChart / PieChart)

Charts are pluggable widgets whose data lives in one or more `series` object-list
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Fixed

- **`check` refuses a button that passes its own data container by widget name** (mendixlabs/mxcli#1324) — `actionbutton btnOwn (Action: call microflow M.F(Gate = $dvGate))` directly inside data view `dvGate` passed `check --references` and `exec`, then `mx check` reported `[CE0117] "Error(s) in expression." at Action button 'btnOwn'`. A data container's name is a variable only for the containers nested below it; in its own context the object is `$currentObject`. Reported as **MDL-BUTTON02** (error, so `exec` refuses it), for data views, list views, galleries and data grids alike, including attribute paths (`$dvGate/Name`) and a control bar inside the container. A grid's own name from its control bar (the selection) and an enclosing container's name from a nested one are not flagged. The flagged set matches mxbuild 11.14.0's CE0117s widget for widget on a ten-button probe page.
- **`set $Obj = …` on an object variable is refused** (mendixlabs/mxcli#1323) — with both variables single objects (e.g. a Reference retrieved from its FROM entity), `set $Cursor = $Next;` passed `check --references` and `exec` and was written as a Change variable action, which mxbuild refuses with CE7247 "Variable 'Cursor' does not have a primitive type". Mendix has no action that reassigns an object variable, so `check` (MDL-SET01, for the objects it can see without a project), `check --references` and `exec` now refuse it and name the alternatives (a sub-microflow that returns the next object, `change $Obj (…)`). `check --references` now types a Reference retrieve from its FROM entity as the object it is. `set` on a list variable stays a Change list Replace (ako/mxcli#949).
- **`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.
Expand Down
Loading
Loading