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
8 changes: 8 additions & 0 deletions .claude/skills/fix-issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ to the symptom table below, so the next similar issue costs fewer reads.
| `mxcli check` rejects a **valid** microflow: **MDL045** ("`/` is division") on `round($a div $obj/Attr * 100)` — division whose divisor is an association-attribute path — but `mx check` → 0 errors | The MDL grammar parses `div`/`*`/`/` at one precedence level, so `$a div $obj/Attr` mis-nests as `($a div $obj) / Attr`; MDL045 saw the `/ Attr` as division. But `Attr` is a bare member name — Mendix has no `/` division operator and re-parses the raw `$obj/Attr` as a path (serialized output preserves the `/`, so the build is clean) | `mdl/executor/validate_microflow.go` (`exprHasSlashDivision`) | Don't flag a `/` BinaryExpr whose RIGHT operand is a bare `IdentifierExpr` (member navigation); real division has a numeric/paren/variable divisor. Test `TestValidateMicroflow_SlashDivision` (div-by-assoc cases); repro `mdl-examples/bug-tests/52-53-microflow-check-false-positives.mdl`. FINDINGS #52 |
| `describe microflow` prints `-- Empty action` for a `set task outcome` / `open user task` / `notify workflow` statement (default engine); a describe→drop→exec round-trip silently drops it. Legacy engine (`MXCLI_ENGINE=legacy`) describes it fine | The modelsdk read path (`actionFromGen`) had no case for the workflow microflow actions, so they read back as nil → "Empty action". The write path + DESCRIBE formatter already handled them; only the modelsdk read case was missing | `mdl/backend/modelsdk/microflow_read_actions.go` (`actionFromGen`) | Add cases for `genMf.SetTaskOutcomeAction` / `OpenUserTaskAction` / `NotifyWorkflowAction`, mirroring the legacy parsers. Test `TestActionFromGen_WorkflowActions`; repro `mdl-examples/bug-tests/54-describe-set-task-outcome.mdl`. FINDINGS #54 |
| `create association X …` errors "association already exists" on re-run and aborts the script | Correct SQL-shaped semantics (like `CREATE TABLE`) — `create` is not idempotent. The idempotent form is `create or modify association`, but it was undiscoverable from the bare error | `mdl/executor/cmd_associations.go` (the `NewAlreadyExists("association", …)` sites) | Not a code bug in the write path — improve the error to name `create or modify association …` and `drop association …`. Repro `mdl-examples/bug-tests/51-create-or-modify-association.mdl`. FINDINGS #51 |
| `dynamictext x (Content: '')` builds with **CE0720** "Place holder index 1 is greater than 0" — `mxcli check` ✓, describe shows `Content: '{1}'` with no params | The builder unconditionally defaulted empty content to the template `{1}`, creating a placeholder with no matching parameter (orphaned) | `mdl/executor/cmd_pages_builder_v3_widgets.go` (`buildDynamicTextV3`, final `content == ""` guard) | Only default to `{1}` when there IS a parameter (`autoGeneratedParams`/`explicitParams`); empty content with no params is a literal empty template. Test `TestBuildDynamicTextV3_EmptyContent`; repro `mdl-examples/bug-tests/traceops-9-10-17-dynamictext-listview.mdl`. traceops #9 |
| `dynamictext s (Content: '$318')` builds with **CE0402/CE1613** ("attribute '$318' no longer exists") — the literal was turned into an unbound `{1}` param | The auto-bind check treated ANY `$`-prefixed content as a variable; `$318` (dollar + digits) is not a valid Mendix variable | `mdl/executor/cmd_pages_builder_v3_widgets.go` (`isDynamicTextVariableRef` / `dynamicTextVariableRe`) | Treat `$` as a variable ONLY when followed by a letter/underscore (`^\$[A-Za-z_]`); `$318` stays literal content. Tests `TestBuildDynamicTextV3_DollarDigitLiteral`, `TestIsDynamicTextVariableRef`. traceops #10 |
| `listview lv (… PageSize: 200)` always pages at 20 — `mxcli check` ✓, `mx check` ✓, describe shows no PageSize | The property parsed into the AST but three layers ignored it: `buildListViewV3` hardcoded `PageSize: 20`, the describe parse never read it, and the listview describe formatter never emitted it | `mdl/executor/cmd_pages_builder_v3_widgets.go` (`buildListViewV3`) + `cmd_pages_describe_parse.go` (Forms$ListView case) + `cmd_pages_describe_output.go` (listview case) | Read `w.GetIntProp("PageSize")` on write; read `w["PageSize"]` on describe; emit a non-default PageSize in the listview formatter. Test `TestBuildListViewV3_PageSize`. traceops #17 |
| A `/** … */` doc comment between `alter entity … add attribute` clauses is a **parse error** (`no viable alternative at input '/**'`) | `alterEntityAction` accepted a doc comment only INSIDE an `attributeDefinition` (after the ADD ATTRIBUTE keyword), not between clauses. `--` line comments are NOT an equivalent workaround — they are discarded, whereas a `/** */` doc comment is persisted as the attribute's Mendix documentation | `mdl/grammar/domains/MDLDomainModel.g4` (`alterEntityAction`) + `mdl/visitor/visitor_entity.go` (`ExitAlterEntityAction` ADD branch) | Add `docComment?` before `ADD ATTRIBUTE`/`ADD COLUMN`; the visitor attaches it as the added attribute's documentation when the attributeDefinition has none. `make grammar` regenerates the parser (not committed). Test `TestAlterEntityAddAttributeDocComment`; repro `mdl-examples/bug-tests/traceops-27-doc-comment-between-clauses.mdl`. traceops #27 |
| `combobox (Association: Mod.Ref, …)` drops the binding — `mxcli check` ✓ but MxBuild fails **CE0642** "Property 'Attribute' is required" | The widget engine's `Association` source read the reference only from the `attribute:` keyword (`w.GetAttribute()`), so an explicit `Association:` keyword was ignored and the widget fell back to enumeration mode | `mdl/executor/widget_engine.go` (`case "Association"`) + `mdl/executor/validate_widgets.go` (`validateComboBoxAssociation`) | Read the reference from `Association:` OR `attribute:`; and add MDL-WIDGET16 flagging an association combobox that lacks the required `datasource:` (option list). A complete association combobox needs reference + `datasource:` + `CaptionAttribute:`. Tests `TestValidateComboBoxAssociation`; repro `mdl-examples/bug-tests/traceops-23-combobox-association.mdl`. traceops #23 |
| A bare MDL keyword used as a WIDGET name (`container body`, `dynamictext content`) is a parse error (`mismatched input 'body' expecting {IDENTIFIER, QUOTED_IDENTIFIER}`) | `widgetV3`'s name only accepted `IDENTIFIER \| QUOTED_IDENTIFIER`, not `keyword` — unlike `attributeName`/placeholder names | `mdl/grammar/domains/MDLPage.g4` (`widgetV3`) + `mdl/visitor/visitor_page_v3.go` (`buildWidgetV3` name extraction) | Add `keyword` to the widget-name alternatives; the visitor reads `wCtx.Keyword()` too. `make grammar` regenerates the parser. Test `TestKeywordWidgetName`; repro `mdl-examples/bug-tests/traceops-11-12-16-strings-names.mdl`. traceops #12 |
| A `'…'` string literal spanning multiple lines fails to parse (`missing END at '…'`) — the newline terminated the token | `STRING_LITERAL` excluded `\r\n` (`~['\r\n\\]`) | `mdl/grammar/MDLLexer.g4` (`STRING_LITERAL`) | Drop `\r\n` from the exclusion (`~['\\]`); mxbuild accepts a multi-line String value (verified). A missing close-quote now spans lines — the standard multi-line-string trade-off. Test `TestMultiLineStringLiteral`. traceops #11 |
| `alter entity … add <name>: <type>` (missing the `attribute` keyword) fails with an opaque `no viable alternative at input 'add<Name>'` | ALTER ENTITY requires the `attribute` keyword (SQL-shaped); the raw ANTLR error doesn't say so | `mdl/visitor/visitor.go` (`enhanceErrorMessage` / `addMissingAttributeRe`) | Source-aware hint: when the offending line is `add <word>:` and `<word>` isn't a real clause keyword (attribute/column/index/event/value/…), append the correct `add attribute <name>: <type>` form. Gated to the primary "no viable alternative" error. Test `TestAlterEntityMissingAttributeKeywordHint`. traceops #16 |
| CE0066 "Entity access is out of date" | MemberAccess added to wrong entity | `sdk/mpr/writer_domainmodel.go` | MemberAccess must only be on the FROM entity (`ParentPointer`), not the TO entity — see CLAUDE.md association semantics |
| `grant view on page` / `grant execute on microflow\|nanoflow` / `grant access on odata\|published rest service` to a role from **another module** passes `mxcli check`/`exec` but fails the Mendix build with **CE0148 "reselect roles"** — the own-module role works | Document access (page/microflow/nanoflow/service `AllowedModuleRoles`) may only reference the document's **own** module roles; Studio Pro's picker only offers those. The grant path wrote `role.Module + "." + role.Name` verbatim with no same-module check (only `validateModuleRole` = role-exists-in-its-module), so a cross-module reference reached the model. The MOVE path already guarded this (`remapDocumentAccessRoles`) — GRANT didn't | `mdl/executor/cmd_security_defaults.go` (`checkDocumentAccessRolesSameModule`) + the 5 grant handlers in `mdl/executor/cmd_security_write.go` | Pre-check each grant: reject when any `role.Module != docModule` with an actionable message (name the doc's module + suggest the own-module role). Reject (don't silently remap) — a GRANT is explicit, so a wrong role/doc shouldn't be substituted. Wired into page/microflow/nanoflow/OData/published-REST grants. Repro `mdl-examples/bug-tests/ce0148-cross-module-grant.mdl` |
| CE0463 "widget definition changed" | Object property structure doesn't match Type PropertyTypes | `sdk/widgets/templates/` | Re-extract template from Studio Pro; see `sdk/widgets/templates/README.md` |
Expand Down
8 changes: 6 additions & 2 deletions .claude/skills/mendix/create-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -553,8 +553,12 @@ datepicker dpCreated (label: 'Created Date', attribute: CreatedDate)
-- Enumeration mode (attribute is an enum type):
combobox cbCountry (label: 'Country', attribute: Country)

-- Association mode (Attribute = association, DataSource = target entity, CaptionAttribute = display attr):
combobox cmbCustomer (label: 'Customer', attribute: Order_Customer, datasource: database MyModule.Customer, CaptionAttribute: Name)
-- Association mode: bind a reference. Requires the option DataSource (the target
-- entity whose objects fill the dropdown) AND a CaptionAttribute (display value).
-- The reference can be given as `Association:` or, equivalently, `attribute:`.
combobox cmbCustomer (label: 'Customer', Association: Order_Customer, datasource: database MyModule.Customer, CaptionAttribute: Name)
-- WRONG: `combobox (Association: X)` with no datasource — mxcli check errors
-- MDL-WIDGET16 (Mendix would otherwise drop the binding → CE0642).
```

### DataView with Form Layout
Expand Down
48 changes: 48 additions & 0 deletions mdl-examples/bug-tests/traceops-11-12-16-strings-names.mdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
-- ============================================================================
-- TraceOps #11, #12, #16: string/identifier ergonomics
-- ============================================================================
--
-- #11 — MDL string literals may now span multiple lines. Before, a newline
-- inside '...' terminated the token ("missing END"). Verified: mxbuild
-- accepts a multi-line String value.
--
-- #12 — A bare MDL keyword (body, content, search, as, …) may be used unquoted
-- as a WIDGET name (matching attribute/placeholder names). Before, the
-- parser rejected it ("mismatched input 'body'"). Names that collide with
-- a keyword are stored fine and round-trip (DESCRIBE quotes them).
--
-- #16 — (parse-error, not shown here) `alter entity … add Name: type` without
-- the `attribute` keyword now yields an actionable hint pointing at
-- `add attribute Name: type`, instead of the opaque "no viable
-- alternative at input 'addName'". The correct form is used below.
--
-- This script builds clean (`mx check` → 0 errors).
-- ============================================================================

create entity MyFirstModule.Note ( Name: String );
/

-- #16: the correct ALTER ENTITY form (with the `attribute` keyword).
alter entity MyFirstModule.Note
add attribute Body: string(200);
/

-- #12: keyword widget names (body, content) used unquoted.
create page MyFirstModule.NoteView ( Title: 'Note', Layout: Atlas_Core.Atlas_Default ) {
container body {
container content {
dynamictext search (Content: 'Type to search')
}
}
}
/

-- #11: a multi-line string literal in a microflow.
create microflow MyFirstModule.Banner () returns String as $out
begin
declare $S String = 'Line one
Line two
Line three';
return $S;
end;
/
44 changes: 44 additions & 0 deletions mdl-examples/bug-tests/traceops-23-combobox-association.mdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
-- ============================================================================
-- TraceOps #23: combobox could not bind an association via `Association:`
-- ============================================================================
--
-- Symptom: `combobox (Association: Mod.Ref, …)` — the parser accepted it, but
-- the writer only read the reference from the `attribute:` keyword, so
-- `Association:` was silently dropped and the build failed
-- [error] [CE0642] "Property 'Attribute' is required."
--
-- Fixes:
-- 1) The widget engine now reads the reference from EITHER `Association:` or
-- `attribute:` (association-source), so the intuitive `Association:` keyword
-- works. Verified: the complete form below `mx check`s with 0 errors.
-- 2) An incomplete association combobox (a `Association:` with no `datasource:`)
-- is caught at check time with MDL-WIDGET16 and the full working syntax,
-- instead of silently dropping the binding and failing MxBuild with CE0642.
--
-- A ComboBox in association mode needs THREE things: the reference
-- (`Association:`/`attribute:`), the option list (`datasource:` = the target
-- entity), and the display value (`CaptionAttribute:`).
-- ============================================================================

create entity MyFirstModule.Customer ( Name: String );
create entity MyFirstModule.Order ( Number: String );
create association MyFirstModule.Order_Customer
from MyFirstModule.Order to MyFirstModule.Customer;
/

create or replace page MyFirstModule.OrderEdit
( Title: 'Order', Layout: Atlas_Core.Atlas_Default, Params: { $Order: MyFirstModule.Order } )
{
dataview dv (datasource: $Order) {
-- Complete association combobox — builds clean.
combobox cmbCustomer (
label: 'Customer',
Association: MyFirstModule.Order_Customer,
datasource: database MyFirstModule.Customer,
CaptionAttribute: Name
)
}
}
/

describe page MyFirstModule.OrderEdit;
34 changes: 34 additions & 0 deletions mdl-examples/bug-tests/traceops-27-doc-comment-between-clauses.mdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
-- ============================================================================
-- TraceOps #27: a /** … */ doc comment between `alter entity … add attribute`
-- clauses was a parse error
-- ============================================================================
--
-- Symptom: placing a doc comment between two add-attribute clauses failed with
-- line 4:2 no viable alternative at input '/** … */add'
-- The alterEntityAction list accepted a doc comment only INSIDE an
-- attributeDefinition (after the ADD ATTRIBUTE keyword), not between clauses.
--
-- Why `--` line comments are NOT an acceptable workaround: `--` comments are
-- discarded by the lexer, so they never reach the Mendix model. A `/** … */`
-- doc comment, by contrast, is PERSISTED as the attribute's Mendix
-- documentation (visible in Studio Pro). Rewriting to `--` silently drops the
-- documentation the author intended to store.
--
-- Fix: alterEntityAction now accepts an optional leading docComment on ADD
-- ATTRIBUTE / ADD COLUMN, and the visitor attaches it as the added attribute's
-- documentation (mirroring a CREATE ENTITY attribute doc comment).
--
-- After the fix this builds clean (`mx check` → 0 errors) and the doc comment is
-- stored on Zz2 (DESCRIBE ENTITY round-trips it).
-- ============================================================================

create entity MyFirstModule.Requirement ( Name: String );
/

alter entity MyFirstModule.Requirement
add attribute Zz1: string(10)
/** Documentation for the second column, persisted into the Mendix model */
add attribute Zz2: string(10);
/

describe entity MyFirstModule.Requirement;
37 changes: 37 additions & 0 deletions mdl-examples/bug-tests/traceops-9-10-17-dynamictext-listview.mdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
-- ============================================================================
-- TraceOps findings #9, #10, #17: DYNAMICTEXT content + ListView PageSize
-- silently produced broken or dropped output
-- ============================================================================
--
-- #9 — `dynamictext x (Content: '')` persisted as `Content: '{1}'` with NO
-- parameters: an orphaned placeholder. mxcli check passed, but MxBuild
-- failed CE0720 "Place holder index 1 is greater than 0". Fix: empty
-- content with no params is a literal EMPTY template — no synthetic {1}.
--
-- #10 — `dynamictext s (Content: '$318')` was treated as a variable reference:
-- persisted as `Content: '{1}', ContentParams: [{1} = $318]` (unbound) →
-- CE0402/CE1613 "attribute '$318' no longer exists". A `$` is a variable
-- ONLY when followed by a letter/underscore; `$318` is literal content.
--
-- #17 — `listview (... PageSize: 200)` parsed cleanly but the writer hardcoded
-- PageSize=20 and dropped the value; describe showed no PageSize. Fix:
-- honor the parsed PageSize on write and round-trip it through describe.
--
-- All three build clean (`mx check` → 0 errors) after the fix, and describe
-- round-trips: `x1` (empty), `s4v (Content: '$318')`, and `PageSize: 200`.
-- ============================================================================

create entity MyFirstModule.Item ( Label: String );
/

create or replace page MyFirstModule.TraceOpsWidgets ( Title: 'W', Layout: Atlas_Core.Atlas_Default )
{
listview lvTree (datasource: database MyFirstModule.Item, PageSize: 200) {
dynamictext x1 (Content: '') -- #9: empty → literal empty template
dynamictext s4v (Content: '$318') -- #10: dollar+digits → literal text
dynamictext lbl (Content: Label) -- control: real attribute still binds
}
}
/

describe page MyFirstModule.TraceOpsWidgets;
Loading
Loading