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 @@ -735,3 +735,4 @@
{"area": "mdl/executor", "date": "2026-09-27", "symptom": "describe of a published REST service with no resources printed `create or modify published rest service M.S ( \u2026 )` followed by a bare `;`, which does not parse (`mismatched input ';' expecting '{'`); found while making describe end every statement with `;` (ako/mxcli#744).", "cause": "The resource block `{ \u2026 }` is mandatory in the grammar (publishedRestService: \u2026 RPAREN LBRACE publishedRestResource* RBRACE), but the describer only printed it when the service had resources and otherwise closed the statement with `;` alone.", "file": "mdl/executor/cmd_published_rest.go", "fix": "Always print the block: `{\\n};` when there are no resources.", "insight": "A describe test that asserts substrings never parses the output; feeding every Describe*_Mock output through the parser (assertTerminated, #744) found this at once. The empty-collection branch of a describer is the one no fixture exercises.", "test": "TestDescribePublishedRestService_Mock (assertTerminated)"}
{"area": "mdl/executor", "date": "2026-09-27", "symptom": "Running an external entity's describe output (create or modify external entity, either form) turns each attribute's Rest$ODataMappedValue into a DomainModels$StoredValue (RemoteName/RemoteType/Filterable... gone), re-mints attribute $IDs on the `from odata client` form, and rewrites String(36) as unlimited", "cause": "Both rewrite paths rebuilt attributes from the AST, which has no spelling for the OData mapping; the `from odata client` path minted fresh IDs; describe external entity printed GetTypeName() (`String`) instead of formatAttributeType", "file": "mdl/executor/cmd_entities.go (carryStoredAttributeState), cmd_odata.go", "insight": "Carry per attribute by name what the statement cannot spell, in the shared merge, and enumerate every path that rebuilds attributes: there were two statements for one document kind", "refs": ["ako/mxcli#743"]}
{"area": "mdl/executor", "date": "2026-09-27", "symptom": "create or modify odata service over a Studio Pro service: EntitySet PageSize 10000 -> 0, entity sets reordered, member CanBeEmpty recomputed, ExportLevel deleted, AuthenticationTypes marker 1 -> 3", "cause": "The modify path rebuilt entity types/sets from the publish block (no spelling for PageSize without paging, set order, CanBeEmpty); the model had no ExportLevel; the writer used a marker-3 list copied from the legacy writer, not Studio Pro", "file": "mdl/executor/cmd_odata.go (carryPublishedEntityState), mdl/backend/modelsdk/odata_write.go", "insight": "Parity with the legacy writer is not evidence of what Studio Pro writes; measure markers on a Studio Pro fixture. A probe that runs describe output with the verb rewritten to create or modify (mdl/roundtrip TestTestAppCreateOrModifyProbe) shows what a verb switch would lose before switching it", "refs": ["ako/mxcli#743"]}
{"area": "mdl/executor", "date": "2026-09-27", "symptom": "describe microflow printed `validation feedback $O/A message '{1} \u2026';` with no template arguments; describe -> exec stored the feedback with none", "cause": "formatAction's ValidationFeedbackAction case never read TemplateParameters, although the reader (textTemplateFromGen) and the writer both carried them", "file": "mdl/executor/cmd_microflows_format_action.go (templateArgsClause)", "insight": "A text template has two halves, the text and its parameters; check every describe case that prints one prints both. Found while switching the template form to `with ({1} = \u2026)` (ako/mxcli#751)."}
1 change: 1 addition & 0 deletions .claude/skills/fix-issue/findings/mdl-visitor.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@
{"area":"mdl-visitor","date":"2026-09-24","refs":["#653"],"symptom":"describe entity on a view entity, exec'd back, was never idempotent: \"Each cycle reports `Modified view entity` and stores the query with every line after the first indented two spaces further.\" Re-exec of the SAME described file reported Unchanged, so it looked stable until you described again","cause":"The two directions did not mirror: describe (cmd_entities_describe.go, and cmd_diff_mdl.go) prefixes two spaces to every stored OQL line; the visitor stored extractOriginalText(oqlCtx), which starts at the query's first token, so line 1 lost its indentation and lines 2…n kept all of it — +2 per cycle","file":"`mdl/visitor/visitor_entity.go` (dedentOQL, leadingLineWhitespace); tests `mdl/visitor/visitor_view_entity_oql_indent_test.go`, `mdl/executor/view_entity_oql_roundtrip_test.go`; bug-test `mdl-examples/bug-tests/653-view-entity-oql-indent-drift.mdl`","insight":"**Any verbatim-source capture that starts at the first token is asymmetric**: line 1 is dedented for free, the continuation lines are not. Fix it on the way IN (exec), not by making describe emit less: strip the common leading-whitespace prefix of the non-blank lines, counting line 1 at its column when only whitespace precedes it (read it from the input stream, start.GetStart()-GetColumn()). Compare prefixes byte-wise, not by width, so a Studio Pro query indented with tabs comes back byte-identical under describe's two spaces. A round-trip test must run describe → exec at least twice AND start from stored text mxcli did not write (flat, tabs, blank lines, comments): one pass from a script is exactly how this went unnoticed. Control: stubbing dedentOQL to return raw fails every round-trip case with lines 2…n two spaces deeper; a real 11.12.1 project with the old binary printed Modified ×3 with growing indent, the fixed one Unchanged ×3. Separate, not fixed here: a comment AFTER the query's last token is outside the captured span and is dropped on exec"}
{"area": "mdl/visitor", "date": "2026-09-25", "symptom": "A page action's microflow argument `Flag: true and false` was stored as the expression \"trueandfalse\", `Mode: if true then 'a' else 'b'` as \"iftruethen'a'else'b'\", and a REST call parameter `$OrderId = if $x then $id else 'none'` as \"if$xthen$idelse'none'\" (measured by decoding the units on a copy of ako/TestApp). `check -p --references` passed; describe printed the fused text back", "cause": "Four sites stored an expression as text via ANTLR's ctx.GetText(), which concatenates tokens without the hidden-channel whitespace: microflowArgV3 values (page/nanoflow call arguments), contentparams values, send-rest-request WITH parameters, and a dynamic `execute database query`. Literals, `+` and a lone $currentObject are single tokens or need no spacing, so every common case looked correct", "file": "`mdl/visitor/visitor_helpers.go` (`expressionSourceText`), `mdl/visitor/visitor_page_v3.go` (`buildMicroflowArgV3`, `buildParamAssignmentV3`), `mdl/visitor/visitor_microflow_actions.go` (dynamic query, send rest params)", "insight": "**GetText() on an expression context is always a bug** — grep `Expression().*GetText()` / `expr.GetText()` in mdl/visitor; each hit either builds the AST or must use expressionSourceText (whitespace kept, MDL comments stripped). The earlier comment-leak fix moved the six microflow sites to extractExpressionText and missed these four because they lived in page/REST code, not microflow statements: fix a text-extraction defect by searching for the call, not the feature. The tell that hid it: tests used `$currentObject` and `'a' + 'b'`, both immune; a probe with `and` / `if…then` exposed all four at once. Found while writing PROPOSAL_first_class_expressions.md §5.1. Tests `visitor_expression_source_text_test.go`", "refs": ["mendixlabs/mxcli#750"]}
{"area": "mdl/visitor", "date": "2026-09-26", "refs": ["ako/mxcli#706"], "rules": ["MDL-IDX01", "MDL-ENUMDOC01"], "symptom": "Five forms parsed and `check` printed \"Syntax OK\", then the model lost or changed them: `throw 'x';` vanished from the microflow; `Amount: float` / `currency` became String(unlimited) (Void as a microflow type) and `date` became DateTime; `create association X (from A to B, type: referenceset, storage: table)` was stored as a Reference with column storage; a `/** … */` on an enumeration value and an index name (`index Idx on (…)`, `create index Idx on E (…)`) were read and written nowhere", "cause": "Three different gaps behind one symptom: throwStatement had a grammar rule and no listener (a whole statement); buildDataType/buildMicroflowDataType end in a default return (String / Void) that the FLOAT_TYPE and CURRENCY_TYPE tokens fell into, and DATE_TYPE mapped to an ast.TypeDate the writers alias to DateTime; ExitCreateAssociationStatement read only ctx.AssociationOptions(), while the parenthesised alternative puts its options directly under the statement as `(COMMA associationOption)*`. Enumeration-value docs and index names have no home in the metamodel (EnumerationValue and Index have no such property)", "file": "`mdl/visitor/visitor_silent_drops.go` (ExitThrowStatement, EnterDataType, EnterNonListDataType, rejectParenthesisedAssociation), `mdl/executor/validate_unstored_text.go` (MDL-IDX01, MDL-ENUMDOC01); tests `mdl/visitor/silent_drops_706_test.go`, `mdl/executor/validate_unstored_text_test.go`", "insight": "**Split by what is lost: refuse when the MODEL would be wrong, warn when only WORDS are lost.** throw/types/association write a different model, so they are visitor errors with the working spelling in the message; doc comments on values and index names leave a correct model, and were used in 56 places in our own examples, so they are check warnings. **Refuse from the parse tree, not by deleting the grammar alternative**: `float`, `currency`, `date` and `throw` are also keywords-as-identifiers, so removing `| FLOAT_TYPE` from dataType would let `Amount: float` re-parse as an enumeration reference named float — the same silence again. A listener on EnterDataType covers every place a type is written (attribute, parameter, return, declare, constant, service rules) in one method; checking only the direct child token keeps `M.Currency` and `enumeration(M.Float)` legal (the control). To find these: any `if … ; return ast.DataType{Kind: …}` fall-through default in a builder is a silent-substitution site, and a grammar alternative whose sub-rules differ from its sibling's (`associationOption` vs `associationOptions`) needs its own read. Sweep: run old vs new `mxcli check` over `git ls-files '*.mdl'` and diff exit codes — it found every example that relied on the dropped forms (5 files)"}
{"date": "2026-09-27", "area": "mdl/visitor", "symptom": "A member path after `*`, `div`, `mod` (`$a/X * $b/Y`) is stored as division: `describe` shows `$a/X * $b / Y`, and inside a list `filter` `$b / $currentObject/Y`. `mxcli check`, `mx check` and the build are all green; Mendix evaluates a different expression.", "cause": "`/` shares a precedence level with `*`, `div`, `mod` in `multiplicativeExpression` (MDLSettings.g4), so the left-associative chain parses `$a/X * $b/Y` as `(($a/X) * $b) / Y`. `buildMultiplicativeExpression` only turned `/ Member` into an AttributePathExpr when the WHOLE left side was a variable or path; here `$b` was already the right operand of the product, so the `/` stayed a BinaryExpr. The serializer then printed a spaced `/` and the filter's `qualifyIteratorAttributes` read the stranded `Y` as a bare attribute.", "file": "mdl/visitor/visitor_microflow_expression.go (attachPathToLastOperand)", "fix": "When a `/ Member` step cannot attach to the whole left side, attach it to the last operand of the preceding multiplicative BinaryExpr (`*`, `div`, `mod`, `%`, `:`): `(L op $b) / Y` becomes `L op ($b/Y)`. Mendix has no `/` division, so `/` followed by a member name is always navigation. Division by a parenthesised expression is left alone (control test).", "insight": "Fixing it where the AST is BUILT fixes every consumer at once (serializer, iterator qualification, MDL045's slash-division check), where the earlier #52 fix only taught MDL045 to tolerate the mis-nested tree. Test: `TestMemberPathAfterMultiplicativeOperatorIsStoredAsWritten` (flow builder, stored strings for sum, filter, declare, div chain) plus a control `TestSlashAfterNonPathIsNotMadeAMemberPath`. Revert check: stubbing the fix brings back all four wrong strings. A unary-minus branch was dropped because removing it failed nothing (`2 * -$b/Y` was already stored correctly).", "refs": ["#768", "#52"], "rules": []}
2 changes: 1 addition & 1 deletion .claude/skills/mendix/cheatsheet-errors/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +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 |
| 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: nanoflow M.NF($P = $value)`), or nest the widget in a data container of the parameter's type. `check -p` reports both |
| 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: 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 |
| CE1876 | Single outcome must not contain activities | Single-outcome user task with a nested activity flow — `check` flags MDL-WF02 |
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/mendix/check-syntax/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ Before writing any MDL, verify these requirements:
- `retrieve $Var from Module.Entity [where condition];`
- `$Result = call microflow Module.Name (Param = $value);` (NOT `set $Result = ...`)
- `$Result = call nanoflow Module.Name (Param = $value);`
- `show page Module.PageName ($Param = $value);`
- `show page Module.PageName (Param = $value);`
- `close page;`
- `validation feedback $entity/attribute message 'message';`
- `log info|warning|error [node 'name'] 'message';`
Expand Down
26 changes: 12 additions & 14 deletions .claude/skills/mendix/create-page/reference/widgets.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,24 +123,22 @@ describe icon collection Atlas_Core.Atlas_Filled -- every icon + its reference
- `action: close_page` - Close the page
- `action: delete` - Delete object
- `action: microflow Module.MicroflowName` - Call microflow
- `action: microflow Module.MicroflowName(Param: $value)` - Call microflow with parameters
- `action: microflow Module.MicroflowName(Param = $value)` - Call microflow with parameters
- `action: nanoflow Module.NanoflowName` - Call nanoflow (client-side)
- `action: nanoflow Module.NanoflowName(Param: $value)` - Call nanoflow with parameters
- `action: nanoflow Module.NanoflowName($Param = $value)` - Also accepted (microflow-style)
- `action: nanoflow Module.NanoflowName(Param = $value)` - Call nanoflow with parameters
- `action: open_link 'https://example.com'` - Open a fixed web address
- `action: open_link $currentObject/URL` - Open the address held in an attribute of the enclosing data container's object (inside a data container only; not over an association)
- **Every parameter needs an argument, or an enclosing data container of its
type.** A flow called with a parameter nothing fills is **CE1571**; `mxcli
check -p` reports it. This is the same on every widget that takes an action,
a clickable `container` included.
- `action: show_page Module.PageName` - Navigate to page
- `action: show_page Module.PageName(Param: $value)` - Navigate with parameters
- `action: show_page Module.PageName($Param = $value)` - Also accepted (microflow-style)
- `action: show_page Module.PageName(Param = $value)` - Navigate with parameters
- `action: create_object Module.Entity then show_page Module.PageName` - Create and navigate
- **A `show_page` argument must be the context object, and there has to BE one.**
Mendix takes the page argument from the enclosing data widget, so the only
spellings that mean anything are `$currentObject` or the name of the variable
that widget is bound to (`datasource: $Customer` → `(Customer: $Customer)` is
that widget is bound to (`datasource: $Customer` → `(Customer = $Customer)` is
fine). Naming any other variable is refused as **MDL-PAGEARG01** — it used to be
accepted and silently opened the page with the context object anyway.
- **Outside a data widget the same rule leaves nothing at all**, so a button sitting
Expand All @@ -150,7 +148,7 @@ describe icon collection Atlas_Core.Atlas_Filled -- every icon + its reference
mxbuild reports **CE1571** per parameter of the target page, and a page whose
parameters are optional would simply show the wrong data. MDL-PAGEARG01 refuses
that too (mendixlabs/mxcli#1029). To open a parameterised page from such a
button, call a microflow that does `show page Module.Page(Param: $value)` —
button, call a microflow that does `show page Module.Page(Param = $value)` —
that path wires the arguments properly.
- **The list above is the whole vocabulary, and a keyword without its argument is
not in it.** `action: open_link` with no URL, `action: show_page` with no page,
Expand All @@ -173,16 +171,16 @@ describe icon collection Atlas_Core.Atlas_Filled -- every icon + its reference
actionbutton btnSave (caption: 'Save', action: save_changes, buttonstyle: primary)

-- Navigate with parameter (inside DATAVIEW)
actionbutton btnEdit (caption: 'Edit', action: show_page Module.EditPage(Product: $Product))
actionbutton btnEdit (caption: 'Edit', action: show_page Module.EditPage(Product = $Product))

-- Navigate with $currentObject (inside DATAGRID column)
actionbutton btnEdit (caption: 'Edit', action: show_page Module.EditPage(Product: $currentObject))
actionbutton btnEdit (caption: 'Edit', action: show_page Module.EditPage(Product = $currentObject))

-- Call microflow with page/dataview parameter
actionbutton btnProcess (caption: 'Process', action: microflow Module.ACT_Process(Order: $Order), buttonstyle: success)
actionbutton btnProcess (caption: 'Process', action: microflow Module.ACT_Process(Order = $Order), buttonstyle: success)

-- Call microflow with $currentObject (inside DATAGRID/LISTVIEW column)
actionbutton btnDelete (caption: 'Delete', action: microflow Module.ACT_Delete(Target: $currentObject), buttonstyle: danger)
actionbutton btnDelete (caption: 'Delete', action: microflow Module.ACT_Delete(Target = $currentObject), buttonstyle: danger)

-- Create object and show page
actionbutton btnNew (caption: 'New', action: create_object Module.Product then show_page Module.Product_Edit, buttonstyle: primary)
Expand Down Expand Up @@ -645,7 +643,7 @@ Create a menu with action items:

```sql
navigationlist navName {
item itemEdit (caption: 'Edit', action: show_page Module.EditPage(entity: $EntityParameter))
item itemEdit (caption: 'Edit', action: show_page Module.EditPage(entity = $EntityParameter))
item itemDelete (caption: 'Delete', action: delete)
item itemBack (caption: 'Back', action: close_page)
}
Expand Down Expand Up @@ -947,7 +945,7 @@ as an `actionbutton`'s `action:`:

```sql
-- Rich, parameterised trigger: a card that opens the object it represents
container tileCard (OnClick: microflow MyModule.ACT_Open(Item: $currentObject), class: 'tile') {
container tileCard (OnClick: microflow MyModule.ACT_Open(Item = $currentObject), class: 'tile') {
dynamictext tileValue (content: '4')
dynamictext tileLabel (content: '4 LEFT', class: 'tile-label')
}
Expand Down Expand Up @@ -1013,7 +1011,7 @@ datagrid dgMaterials (
-- Not row-scoped: pass the selection explicitly.
container cUnlink (
class: 'command',
action: nanoflow Module.ACT_UnLink ($Material = $dgMaterials)
action: nanoflow Module.ACT_UnLink (Material = $dgMaterials)
) {
actionbutton btnUnlink (caption: 'Unlink')
}
Expand Down
Loading
Loading