Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
fe7bfea
docs(proposal): def-driven pluggable widget bodies (mendixlabs/mxcli#…
claude Sep 4, 2026
e95475e
docs(proposal): widgets as first-class MDL, not a second dialect
claude Sep 4, 2026
703a068
fix(widgets): name a remedy that can work, accept (), and emit slot n…
claude Sep 4, 2026
3411797
fix(widgets): drop the premise that mxcli should ship the four missin…
claude Sep 4, 2026
cf11e63
feat(widgets): DESCRIBE WIDGET, so a widget describes in-language too
claude Sep 4, 2026
2373f53
feat(widgets): DESCRIBE WIDGET reports body containers and what is au…
claude Sep 4, 2026
45865d6
feat(widgets): DESCRIBE WIDGET emits an MDL example that parses as wr…
claude Sep 4, 2026
2b5aa2f
feat(widgets): narrow the example's bindings by the widget's own rules
claude Sep 4, 2026
ba342de
docs(proposal): settle open question 1 — the validator needs a slice …
claude Sep 4, 2026
15c7149
feat(check): the validator knows what a widget is (slice 0)
claude Sep 4, 2026
83427b4
fix(check): MDL-WIDGET25 needs a project, and its repro is not a .fai…
claude Sep 5, 2026
cff5f01
feat(catalog): a widget is a reference target (slice 5)
claude Sep 5, 2026
18e0ee9
fix(search): match a reference target whose stored spelling differs i…
claude Sep 5, 2026
476cebc
docs(proposal): record what slice 5 actually did, and where it differs
claude Sep 5, 2026
b2f1160
chore(testdata): ignore the widget docs the fixture regenerates
claude Sep 5, 2026
4a95d3c
docs(proposal): close open questions 5 and 6
claude Sep 5, 2026
997e1d9
fix(check): widget property warnings must not depend on map iteration…
claude Sep 5, 2026
bca5466
feat(page): a widget and its body are named by the definition (slices…
claude Sep 5, 2026
bde10f8
fix(describe): emit the widget's own name, and stop losing its body s…
claude Sep 5, 2026
0e92325
fix(describe): reconstruct a pluggable widget's containers instead of…
claude Sep 5, 2026
a2463b8
fix(describe): emit a widget's property keys verbatim, not PascalCased
claude Sep 5, 2026
76776d0
fix(examples): use a valid interpolation value in the chart examples
claude Sep 5, 2026
0bf2642
fix(check): accept a widget property's documented alias, not only its…
claude Sep 5, 2026
8293b18
fix(check): route a chart series' friendly DataSource by its dataSet …
claude Sep 5, 2026
ae2b2b8
fix(check): stop guessing Decimal for sum() over an unresolvable argu…
claude Sep 5, 2026
5b2f808
fix(check): an object-list item is not a widget, so it cannot be a CE…
claude Sep 5, 2026
64055ca
docs(findings): record the four check false positives found under a p…
claude Sep 5, 2026
668ad9a
fix(check): read a widget's properties from its .mpk, not a hand-writ…
claude Sep 5, 2026
a3e561e
fix(describe): stop emitting properties the example's own configurati…
claude Sep 5, 2026
63d1614
fix(describe): give an object-list item a value the checker accepts
claude Sep 5, 2026
d8294e7
fix(catalog): index the widgets inside an object-list item
claude Sep 5, 2026
a3c3a50
docs(findings): record the four defects the external test projects found
claude Sep 5, 2026
81918e7
fix(test): make the item-literal guard hermetic instead of environmen…
claude Sep 5, 2026
d9c11cd
docs(findings): record the gitignored-fixture-cache test trap
claude Sep 5, 2026
c31e210
docs(site): a reference page for DESCRIBE WIDGET
claude Sep 5, 2026
8eb0553
docs(site): the widget list is not the boundary any more
claude Sep 5, 2026
5952e48
docs(skill): teach the short widget form, not only the id form
claude Sep 5, 2026
d371fab
docs: correct the widget describe command form, and say what it now p…
claude Sep 5, 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
20 changes: 20 additions & 0 deletions .claude/skills/fix-issue/findings/mdl-executor.jsonl

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions .claude/skills/fix-issue/findings/mdl-grammar.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,5 @@
{"area": "mdl/grammar", "date": "2026-08-27", "raw": "| After ako/mxcli#260, ten of the 327 demo-app mappings still describe into MDL that does not parse — an export root printed as `. {`, and a custom-handler parameter printed as `Suggestion: (Value)` | Two leftovers of the same families. (1) `group as` covers a nested entity-less node (#262) but a ROOT has no member name, so it had no spelling at all. (2) `customHandlerParamText` rendered the stored value path raw, so an array-of-primitives parameter leaked Mendix's `(Wrapper)`/`(Value)` markers | `mdl/grammar/domains/MDLDomainModel.g4` (`exportMappingRootElement`), `mdl/executor/cmd_export_mappings.go` (the entity-less-root branch, and the association-less handling), `mdl/executor/mapping_customhandler.go` (`customHandlerParamText`, `buildCustomHandler`) | **Printing the member is only half of it**: emitting `Value` while the builder concatenates it back as `…|(Wrapper)|Value` gives a path that resolves to nothing, so resolve the parameter path THROUGH the schema index (`resolvePathKind(..., true)`) the way a member reference is resolved. Two things only a real build caught, both invisible to `mxcli check`: an entity-less root fell through to the VALUE branch and produced a project mxbuild cannot LOAD (*\"Type ExportValueMappingElement does not contain a constructor with a parameter of type ExportMapping\"*), because the builder decides object-vs-value on `def.Entity != \"\"`; and an element with NO association cannot be `Find` — **CE0224 \"No association selected for obtaining objects.\"** — it is `Parameter`, which is what CapitalConnector.EM_AttachedDataRequest stores on both its elements. Took the corpus from 317/327 parsing to **327/327**. Repro `mdl-examples/bug-tests/mapping-260b-last-parse-failures.mdl` |", "refs": ["#262", "ako/mxcli#260"], "ce": ["CE0224"]}
{"area": "mdl/grammar", "date": "2026-08-28", "raw": "| A JSON structure's ARRAY ITEM element gets a derived name (`LinesItem`, `JsonObject`) that no MDL can change, and every mapping over the structure carries it — `describe` of a Studio Pro mapping then diffs on `ExposedName`. The obvious workaround, `custom name map ('lines\\|(Object)' as 'OrderLine')`, parses, executes and does **nothing** | An array's item is the anonymous `[…]` entry, so it has no JSON key; `customNameMap` is keyed on JSON keys, so the item was unreachable by construction. And an entry matching no key was applied to nothing and reported nothing, so the failed workaround was indistinguishable from success | grammar `mdl/grammar/domains/MDLDomainModel.g4` (`customNameMapping` gains `ITEM OF`), `mdl/ast/ast_jsonstructure.go` (`CustomItemNameMap`), `mdl/visitor/visitor_jsonstructure.go`, `mdl/types/json_utils.go` (`snippetBuilder.itemName`, `SnippetKeys`), `mdl/executor/cmd_jsonstructures.go` (`collectCustomItemNames`, DESCRIBE), `mdl/executor/validate_json_structure_names.go` (`MDL-JSON01`/`MDL-JSON02`) | **Do NOT infer Studio Pro's generation rule from stored documents — they are hand-edited.** Measured across 621 array elements in nine apps: 61% are the generator's `JsonObject`/`Wrapper`[+counter], 35% are a word someone chose, 4% are `<Name>Item`. The fingerprint that settles it is `JSON_AutoConfigResponse`, whose eight arrays read *in document order* `Scope, Wrapper_2..Wrapper_6, Claim, CodeChallengeMethods` — a counter with **gaps where a human renamed**; and `JSON_SensorData`'s `Array → SensorData`, a name with no relation to the array's. A version story fitted to a 9-structure sample (\"10.24 singularises, 11.4 does not\") evaporated at corpus scale. So the fix is **expressiveness, not default-matching**: no default can match a corpus that is a third hand-written, and changing the default would rewrite every stored structure's item names plus every mapping bound to one (ExposedName is a resolution key, #882). Design notes: `item of 'key' as 'Name'` rather than folding it into the existing entry, so naming an item does not require restating the array's name and adding one is a one-line diff; the same clause names a primitive array's **Wrapper**, because that wrapper IS the item; `item of 'Root'` for a root array, which has no key. DESCRIBE needs its own collector — an item's path segment is the marker `(Object)`/`(Wrapper)`, so the existing one skips it, and without it a named item was written on CREATE and silently renamed back by describe → exec. ako/mxcli#272 |", "refs": ["#882", "ako/mxcli#272"]}
{"area": "mdl/grammar", "date": "2026-08-31", "symptom": "`DESCRIBE MICROFLOW` emits `reduce($list, expr)` (or `all(...)` / `any(...)`) and mxcli's own checker then rejects its own output: \"set 'X' calls 'reduce()', which is not a Mendix expression function [MDL044]\". Note the word **set** — the parser did not reject the call, it read the line as a Change Variable whose value happened to be a function call, and MDL044 was right about the rest", "cause": "DESCRIBE rendered an aggregate as `strings.ToLower(storedEnumValue)`, assuming every value of Mendix's `AggregateFunction` was also an MDL keyword. Mendix has eight, the grammar had five. Underneath sat a quieter defect: Mendix stores a Reduce's seed and result type in `ReduceInitialValueExpression` / `ReduceReturnDataType` and the semantic model had no field for either, so a grammar-only fix would have round-tripped the syntax while deleting the fold", "file": "`mdl/grammar/MDLLexer.g4` (REDUCE/ANY/INITIAL + the `keyword` rule so they stay usable as identifiers), `mdl/grammar/domains/MDLMicroflow.g4` (`listAggregateOperation` + `reduceFoldOptions`), `mdl/ast/ast_microflow.go`, `mdl/visitor/visitor_microflow_actions.go`, `mdl/executor/cmd_microflows_builder_actions.go`, `mdl/executor/cmd_microflows_format_action.go` (`mdlAggregateKeyword`), plus all four read/write paths: `sdk/mpr/parser_microflow.go`, `sdk/mpr/writer_microflow_actions.go`, `mdl/backend/modelsdk/microflow_read_actions.go`, `mdl/backend/modelsdk/microflow_write.go`", "insight": "**A renderer that stringifies an enum outgrows its grammar silently** — the sibling `formatListOperation` switches on concrete types and cannot, which is the shape to prefer. The guard is a describe→parse loop over `microflows.AllAggregateFunctions` (`TestDescribedAggregateParsesBack`), so a ninth Mendix function fails a test rather than a user's script. **Get a reference document before believing the vendor docs**: Mendix's reference guide says a return type is \"not applicable\" to All/Any, but Studio Pro writes `ReduceReturnDataType` as Boolean on both, and `Attribute` as `\"\"` when unused — all three activities now re-serialize byte-identically to Studio Pro's. `mx check` is no help here (0 errors before and after); the controls are the origin/main parse (`reduce`/`all`/`any` → Change Variable, with `sum` → aggregate as the positive control) and reverting the write path (`TestReduceFoldReachesStorage` then reports the two keys missing). #1004", "refs": ["#1004"], "rules": ["MDL044"]}
{"area": "mdl/grammar", "date": "2026-09-04", "symptom": "`container c ()` / `dynamictext t ()` / `pluggablewidget 'id' pw ()` are parse errors, reported at the `)` as though the widget were wrong, while bare `container c` and `container c (x: 'y')` both parse.", "cause": "widgetPropertiesV3 was `LPAREN widgetPropertyV3 (COMMA widgetPropertyV3)* RPAREN` — at least one property required.", "file": "mdl/grammar/domains/MDLPage.g4", "insight": "An empty property list is what an LLM writes for a widget that needs no properties, and the error points at the paren rather than the cause. One-character fix (wrap the list in `( … )?`). Found while measuring something else — the first run of a keyword-parse survey used `()` throughout and mis-scored every keyword as rejected, including ones that worked. If a whole measurement comes back uniformly negative, suspect the harness before the subject.", "refs": ["mendixlabs/mxcli#1036"]}
{"area": "mdl/grammar", "date": "2026-09-05", "symptom": "After adding a generic (IDENTIFIER | keyword) alternative to widgetTypeV3, `slot body` parsed as a widget of type `slot` and `placeholder Main { ... }` as a widget named Main. Both still parsed, `mxcli check` still exited 0, and a diff of check output across all 515 mdl-examples scripts showed ZERO difference.", "cause": "pageBodyV3 listed widgetV3 FIRST, before useFragmentRef / placeholderBlockV3 / slotMarkerV3. SLOT, PLACEHOLDER and USE are all inside the `keyword` rule (655 tokens), so the generic widget alternative matched them before the specific alternative could.", "file": "mdl/grammar/domains/MDLPage.g4", "insight": "Put the specific alternatives BEFORE widgetV3 in pageBodyV3, the same ordering fix widgetV3 already applies internally for `template for`. The transferable lesson is about the MEASUREMENT, not the grammar: a corpus diff of `mxcli check` output compares DIAGNOSTICS, so it is blind to a construct that parses into the wrong AST shape. 515 scripts said nothing; two visitor unit tests caught it immediately. When a grammar change could reinterpret existing syntax rather than reject it, assert on the AST, not on the diagnostics. CONTROL: reorder pageBodyV3 back and TestSpecificPageBodyFormsWinOverTheGenericWidget / TestSlotMarkerWinsOverTheGenericWidget fail.", "issue": "mendixlabs/mxcli#1036"}
1 change: 1 addition & 0 deletions .claude/skills/fix-issue/findings/mdl-other.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@
{"area": "mdl/translations", "date": "2026-08-30", "raw": "| `create or modify translations in <Module> for <lang>` reports success (\"Set 212 nl_NL translation(s) across 20 document(s)\") and the app's pages switch language while the **menu does not** | `mdl/translations/outofscope.go` (new), `mdl/executor/cmd_translations.go`, `cmd/mxcli/syntax/features_misc.go` | The **navigation is a project-level document**, not a module one, so `in <Module>` never reaches it. Measured on the reporting project: 151 strings scoped against 546 unscoped, and re-running the same file unscoped landed 65 more across 22 further documents. Nothing warned — the document count was the only tell, and only if you knew what number to expect. A scoped run now names **the file's own entries** it did not reach (`translations.OutOfScope`), not \"the project has other strings\", which is true of every scoped run and would warn forever — the per-module workflow is exactly what the scoping exists to support. Second, load-bearing half: those entries were previously swept into the **drift** warning, whose premise (\"no text has this as its source\") is *false* about them — they matched, out of scope. They are subtracted from it, so \"the text may have been deleted\" is only said where it is true. Controls: an unscoped run of the same file reports nothing new and lands the strings; a key matching nothing anywhere is still reported as drift. Reported as ledger #137 |", "refs": ["#137"]}
{"area": "mdl/catalog", "date": "2026-09-03", "symptom": "`SHOW LANGUAGES` omits a language the project really has (ar_DZ absent from a list of 8 where the project has 9), and `search '<a widget caption>'` returns \"No matches found\" for a string `DESCRIBE TRANSLATIONS` lists. Nothing errors and the catalog builds clean.", "cause": "CATALOG.strings was filled by hand-written per-type extractors reaching five sites (page title, enum caption, three microflow message templates), so a text anywhere else — every widget caption, tooltip, validation message, client template — was never indexed.", "file": "mdl/catalog/builder_strings.go", "insight": "A language present only on an unindexed site is INVISIBLE, not undercounted, so it vanishes from SHOW LANGUAGES entirely and from lint rule QUAL005, which discovers its language set from the same table. The fix is not a sixth case — that is how five was ever the number. Index from the type-agnostic walk DESCRIBE TRANSLATIONS already uses (translations.SitesInUnit over ListRawUnitsByType(\"\")), leaving only non-Texts$Text strings in the typed path (URLs, log nodes, REST paths, documentation, and Microflows$StringTemplate, which holds a plain Text and cannot carry a translation). Derive ObjectType from the unit $Type mechanically rather than via a table. Measured before: 69 of 3265 texts, 8 of 9 languages, 66 en_US of 1045. After: 1496 rows, 9 languages, counts identical to an independent BSON walk. Atlas design templates are ~70% of the corpus and are indexed rather than excluded, because CREATE TRANSLATIONS writes them and a SHOW LANGUAGES that excluded them would reopen the same split. CONTROL: stub the walk and the run reports `strings: 3` with SHOW LANGUAGES reporting nothing at all.", "refs": ["#250"]}
{"area": "mdl/linter", "date": "2026-09-03", "symptom": "Lint rule QUAL005 reports no missing translation for an enumeration where only one value is translated (11 real gaps unreported), and likewise for a page's sibling action buttons.", "cause": "The rule grouped by (QualifiedName, StringContext) while ElementId sat unused in the strings table, so every sibling element of one type collapsed into one group and a single translated value made the set look complete.", "file": "mdl/linter/rules/missing_translations.go", "insight": "Add ElementId to the SELECT, the ORDER BY and the elementKey struct. No test caught it because the harness synthesized ElementId from QualifiedName+StringContext, giving every sibling the same value and reproducing the defect inside the fixture — a fixture that encodes the bug cannot detect it. CONTROL: with every sibling translated the run must stay at 0 violations, or the new violation is an artifact of splitting the group rather than the missing translation.", "refs": ["#250"]}
{"area": "mdl/catalog", "date": "2026-09-05", "symptom": "A widget inside a DataGrid2 column, gallery item or chart series was absent from CATALOG.WIDGETS, so a page holding 19 chart sparklines in datagrid columns never appeared under \"which pages use VegaChart?\" while the grid around them did.", "cause": "extractWidgetsRecursive walked a pluggable widget's Object.Properties[].Value.Widgets (a child slot) but not Value.Objects[] (an object list), whose items are themselves property bags holding widgets.", "file": "mdl/catalog/builder_pages.go", "insight": "Wider than the widget edge: CATALOG.REFS is a projection of this table, so an entity or microflow used ONLY inside a column template reported zero references — anything using reference counts to decide 'unused, safe to delete' would delete a document in active use (#940's failure mode, fixed for List View templates and left open for object lists). Recurse (an item is a property bag) rather than special-casing a depth. TWO measurement traps: the unfiltered ref count GREW when the widget edge landed, and growth looks like progress — only a query filtered to the widget actually asked about shows the gap; and .mxcli/catalog.db must be DELETED between runs, since a stale cache made the fixed binary look unfixed and produced a wrong conclusion before the rebuild was forced.", "issue": "mendixlabs/mxcli#1036"}
6 changes: 3 additions & 3 deletions .claude/skills/mendix/create-page/reference/widgets.md
Original file line number Diff line number Diff line change
Expand Up @@ -865,9 +865,9 @@ create page Sales.Dashboard (Title: 'Revenue', Layout: Atlas_Core.Atlas_Default)
series sRevenue (
dataSet: static,
DataSource: database from Sales.ByRegion, -- or: staticDataSource: database from Sales.ByRegion
StaticXAttribute: Region,
StaticYAttribute: Total,
StaticName: 'Revenue'
staticXAttribute: Region,
staticYAttribute: Total,
staticName: 'Revenue'
)
}
}
Expand Down
Loading
Loading