Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
8b11d04
fix(describe): DESCRIBE FRAGMENT FROM PAGE/SNIPPET found no widget, ever
ako Sep 26, 2026
9436742
fix(search): warn when the source index was never built
ako Sep 26, 2026
a7c2377
fix(structure): show microflow and nanoflow counts at depth 1
ako Sep 26, 2026
11ff6f2
fix(catalog): reference edges to attributes, enumerations, workflows …
ako Sep 26, 2026
493932f
fix(refs): one row per reference, impact counts elements, and an empt…
ako Sep 26, 2026
fd677e3
fix(context): related entities come from associations, and callers ar…
ako Sep 26, 2026
6096300
fix(describe): trim a stored expression's outer whitespace when rende…
ako Sep 26, 2026
6da8270
test: round-trip Studio Pro-authored documents through describe -> exec
ako Sep 26, 2026
b70bcdb
test(refs): MDL repro for the reference-graph gaps, and findings
ako Sep 26, 2026
8db1dce
fix(cli): keep --json stdout pure across the query commands
ako Sep 26, 2026
a37ccdf
docs(findings): record the --json purity recurrence
ako Sep 26, 2026
56aa7a7
fix(describe): association, java action and module role emit create o…
ako Sep 26, 2026
f9307f0
fix(java actions): carry ExportLevel and ActionDefaultReturnName; sto…
ako Sep 26, 2026
9559446
fix(snippets): carry the stored Type, ExportLevel and canvas; write T…
ako Sep 26, 2026
8dfdede
fix(nanoflows): write annotation flows; carry export level, list-by-r…
ako Sep 26, 2026
be9d9e4
fix(pages): keep every translation through describe -> exec
ako Sep 26, 2026
7cdb568
fix(java actions): read the body DESCRIBE prints, and keep the import…
ako Sep 26, 2026
dc6222d
docs: record the #705 round-trip findings and a bug-test script
ako Sep 26, 2026
6688f78
fix(associations): create or modify keeps the stored storage when the…
ako Sep 27, 2026
8d15e1e
fix(java actions): a rewrite carries each parameter's description and…
ako Sep 27, 2026
56fbd0b
fix(java actions): marker search folds ASCII only, keeping byte offsets
ako Sep 27, 2026
3dfa674
Merge remote-tracking branch 'origin/main' into chain-715
ako Sep 27, 2026
1e3e130
Merge branch 'chain-715' into chain-716
ako Sep 27, 2026
ba06295
Merge branch 'chain-716' into chain-717
ako Sep 27, 2026
796ab8f
Merge branch 'chain-717' into chain-718
ako Sep 27, 2026
35bc2a4
roundtrip: strike putget for SUB_Feedback_PostToAppInsights, fixed by…
ako Sep 27, 2026
e51bb12
Merge branch 'chain-718' into chain-720
ako Sep 27, 2026
f679b3e
Merge branch 'chain-720' into chain-719
ako Sep 27, 2026
034185f
merge: drop the diagnostics() helper duplicated by #716 and #719
ako Sep 27, 2026
85b8ee4
Merge branch 'chain-719' into chain-729
ako Sep 27, 2026
2f748ad
roundtrip: strike the entries #705/#729 fixes (plain create, java act…
ako Sep 27, 2026
af0dbb2
test: #720 made the no-references message more specific; match its pr…
ako Sep 27, 2026
4fd54e7
Merge branch 'chain-719' into chain-729
ako Sep 27, 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
1 change: 1 addition & 0 deletions .claude/skills/fix-issue/findings/cmd-mxcli.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,4 @@
{"area": "cmd/mxcli", "date": "2026-09-25", "symptom": "Shipped Starlark rules CONV009, CONV010, QUAL001, QUAL003, QUAL004 and CUSTOM002 report nanoflows and rules as microflows: \"Microflow 'NF_Foo' has 30 activities\", and documentType \"Microflow\" in the JSON and report output.", "cause": "microflows() yields all three flow flavours (one catalog table, MicroflowType MICROFLOW/NANOFLOW/RULE), and each rule hardcoded document_type=\"Microflow\" and a \"Microflow '...'\" message. The Go rules had already been fixed with Microflow.DocumentNoun() after MPR002 called a rule a microflow, but that method was never exposed to Starlark, so every Starlark rule had to re-derive it and none did.", "file": "mdl/linter/starlark.go, .claude/lint-rules/{conv009_max_microflow_objects,conv010_act_microflow_content,example_microflow,long_microflows,mccabe_complexity,orphaned_elements}.star, mdl/linter/starlark_flow_noun_test.go", "insight": "When a Go-side fix lives in a method, check whether the Starlark projection can reach it; a fix Starlark cannot call is a fix for half the rules. Exposed as document_noun / document_noun_title on the microflow struct, and the test runs every shipped rule over a fixture with one flow per flavour, requiring a finding on each (else vacuous) plus a guard that any new `for x in microflows()` rule joins the list. Skip the wrong turn of excluding nanoflows/rules from QUAL004: rule calls from decisions ARE emitted as 'call' refs (builder_references.go collectRuleCalls), so only the label was wrong.", "refs": "mendixlabs/mxcli#1178"}
{"area": "cmd/mxcli", "date": "2026-09-25", "symptom": "Starlark rule API docs name microflow_type values the linter never returns: the write-lint-rules skill said \"microflow\"/\"nanoflow\", example_microflow.star (copied into every project by mxcli init) said \"Microflow\"/\"Nanoflow\", only mccabe_complexity.star had \"MICROFLOW\"/\"NANOFLOW\", and none listed \"RULE\". The skill's entity table also omitted has_created_date/has_changed_date/has_owner/has_changed_by.", "cause": "LintContext.Microflows() passes the catalog's MicroflowType through raw (MICROFLOW/NANOFLOW/RULE) while Entities() CASE-normalizes EntityType to TitleCase, so the two adjacent iterators have opposite conventions and each doc author guessed. Nothing tied the documented literals to what the API emits, so two passes over the same skill file (59db6e7b, #1165) fixed instances and left this one.", "file": ".claude/skills/mendix/write-lint-rules/SKILL.md, .claude/lint-rules/example_microflow.star, .claude/lint-rules/mccabe_complexity.star, mdl/linter/starlark_documented_values_test.go", "insight": "Fix the class, not the row: observe the emitted field names and enum values by running a Starlark rule (dir(e), e.entity_type) over a fixture holding every stored kind, then assert the skill tables, the shipped rules' `# .field - ...` headers, and every `.entity_type/.microflow_type ==` comparison in shipped rules against that observed set. Equality, not subset, for the docs -- an undocumented value (RULE) is a flavour a rule silently mistreats. Normalizing MicroflowType instead would have broken every user rule already comparing \"MICROFLOW\" correctly.", "refs": "mendixlabs/mxcli#1178, mendixlabs/mxcli#1164"}
{"area": "cmd/mxcli", "date": "2026-09-26", "symptom": "The write-lint-rules skill, which Starlark rules are written from, omitted seven query functions (java_actions, documents, documentable_elements, navigation_targets, queues, module_cycles, database_connections) plus get_option() and struct(), and fields on scheduled_event (repeat, on_overlap, time_zone) and project_security (anonymous_user_role). Four of the missing functions are used by shipped rules.", "cause": "Each builtin and field was added in code with its own PR and a docs-site paragraph at most; nothing compared the skill to buildPredeclared() or to the struct keys, so every API addition drifted from the skill by default. #1178's test pinned only the entity and microflow tables.", "file": ".claude/skills/mendix/write-lint-rules/SKILL.md, mdl/linter/starlark_skill_coverage_test.go", "insight": "Cover the whole surface at once rather than the table that was reported: an internal test ranges over (&StarlarkRule{}).buildPredeclared() for builtins and go/ast-parses every starlarkstruct.FromStringDict(starlark.String(name), dict) call in the package for struct fields (literal dicts and locally-built ones like ppDict), then requires each struct's skill table to match exactly. Structs documented elsewhere are named in explicit maps (helper params, inline struct{...} in a function row, shared permission table) so an exemption is a visible decision. The quick one-off Python diff over-reported (nested password_policy, permissions_for's entity_name) -- the test encodes those layouts instead of guessing.", "refs": "mendixlabs/mxcli#1178"}
{"area": "cmd/mxcli", "date": "2026-09-26", "symptom": "`--json` output did not parse. `mxcli refs|callers|callees|impact|structure|show|search … --json` and `mxcli -c \"select … from CATALOG.x\" --json` printed \"Connected to:\", catalog load/build progress (`✓ Table: N`, 40+ lines on a cold cache), a \"References to X\" header and a \"Found N\" count ahead of the array; an empty answer was the sentence \"(no references found)\" / \"(no results)\" instead of []; `context` ignored --json and printed markdown; `check --format json` wrote its document to STDERR (one per phase), leaving only executor chatter on stdout; `show widgets|data transformers|import/export mappings|catalog status` had no JSON path for empty or at all; `diff --json` silently printed a text diff. Found by an agent-orientation audit on a 140MB app (Evora Factory Management)", "cause": "The executor has ONE writer, ctx.Output, and it carries both the payload and every status line. The #904 fix (progressSink) only works when the payload is written by the COMMAND (lint/report format their own result to os.Stdout, so the executor's writer can be pointed at stderr); for every query subcommand the payload is written by the executor itself, through the same ctx.Output as the chatter, so no choice of writer at the cmd layer can separate them. The split has to be inside the executor, per line, by what the line is", "file": "`mdl/executor/exec_context.go` (`Diagnostics` field + `diagnostics()`, identical to ako/mxcli#716, plus `progress()`: Output in text mode, diagnostics() in JSON mode), `mdl/executor/format.go` (`writeEmptyResult`), `mdl/executor/cmd_catalog.go` (connect/load/build/warn/count lines, `writeCatalogStatusJSON`), `mdl/executor/cmd_search.go`, `cmd_context.go`, `cmd_widgets.go`, `executor_connect.go`, `reference_target.go`; `cmd/mxcli/cmd_check.go` (one structured document on stdout via `finish`), `cmd/mxcli/cmd_query.go` (search: `--json` canonical, `--format json` deprecated alias sets the executor format), `cmd/mxcli/output_stream.go` (`refuseJSONFlag`, used by diff/diff-local); tests `cmd/mxcli/json_output_purity_test.go`, `mdl/executor/progress_stream_test.go`", "insight": "**Classify each line by what it IS, not where it is printed**: an answer goes to Output, commentary about the run goes to `ctx.progress()`. Text mode is byte-for-byte unchanged because progress() IS Output there — assert that too (`TestTextModeKeepsProgressOnStdout`), or the next fix 'cleans' interactive output by sending it all to stderr. **The empty case is where JSON contracts break**: every handler that early-returns a sentence on zero rows must go through writeEmptyResult (or the existing `&& ctx.Format != FormatJSON` idiom) — the sweep found 8 such sites and most list handlers already had the idiom, so grep for the ones that do not. **--json is a ROOT persistent flag**, so every subcommand accepts it; a command with no JSON output must refuse it (stderr, exit 2), since ignoring it hands text to a parser. **Test in a child process** (reuse TestRunMainHelper): stdout and stderr are then genuinely separate, error paths ending in os.Exit are observable, and no cobra flag state leaks between table rows. Proof: forcing progress() to return Output fails 18/18 --json rows, the search alias and the error test; reverting cmd_check.go fails all five check rows. Left alone: the 10,000-line output guard counts pretty-printed JSON lines, so `show widgets --json` on Evora emits the complete document and then exits 1 'output line limit exceeded'", "refs": ["#904", "ako/mxcli#716"]}
2 changes: 2 additions & 0 deletions .claude/skills/fix-issue/findings/mdl-backend.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,5 @@
{"area":"mdl/backend","date":"2026-09-25","symptom":"A data view with `DataSource: nanoflow Module.NF` (e.g. describe → exec of Feedback v4.0.2's FeedbackModule.ShareFeedback) passes `mxcli check` and exec, then mxbuild 11.13.0 reports CE2633 \"No nanoflow configured for the data source of this data view\". Same result through `alter page … set DataSource = nanoflow X on dv`","cause":"Both writers nested the name in a `Forms$NanoflowSettings` child (ParameterMappings marker 3) by analogy with `Forms$MicroflowSource`, which really does nest `Forms$MicroflowSettings`. Studio Pro's `Forms$NanoflowSource` is FLAT: ForceFullObjects, Nanoflow, ParameterMappings (marker 2) directly on the source — exactly gen's shape. mxbuild found no Nanoflow key. The raw nested builder also never read d.ParameterMappings, so a parameterized source nanoflow lost its arguments. On the read side, the ALTER PAGE flow-context lookup (`flowFromDataSourceDoc`) and describe's argument reader (`flowSourceArgs`) only knew the nested shape, so Studio Pro-authored nanoflow sources yielded no entity context / no arguments","file":"`mdl/backend/modelsdk/widget_write_legacy_gaps.go` (`nanoflowSourceToGen` via gen + `Forms$NanoflowSource` TypeDefaults in `widget_write.go`), `mdl/backend/pagemutator/mutator.go` (`serializeDataSourceBson`, `flowFromDataSourceDoc`), `mdl/executor/cmd_pages_describe_datasource.go` (`flowSourceArgs`)","insight":"**The code comment asserted the wrong shape as a measured fact** (\"Studio Pro nests it in a Forms$NanoflowSettings child … Legacy's shape is the one with a working project behind it\") and a unit test pinned it — both were parity-with-legacy, never measured. `Forms$NanoflowSettings` is not a type in modelsdk/gen or generated/metamodel: **when gen and a hand-rolled builder disagree about a type's shape, grep gen for the type the builder invents before trusting the builder**. What settled it in one step: a 60-line scanner that `bson.Unmarshal`s every mprcontents unit and prints the key-set (with list markers) of each `$Type` instance — 5 of 5 flat nanoflow sources, and 4 of 4 microflow sources nested as gen says, so the microflow path needed nothing. **Enumerate every writer of the type, not just the reported one**: the ALTER PAGE setter had its own copy of the same wrong literal, and the read-side lookups keyed on the wrong shape meant Studio Pro pages were the ones silently mis-read. Readers keep the nested fallback for pages written before the fix. Verified: exec + `mx check` 11.13.0 CE2633 → 0 errors for CREATE PAGE (ShareFeedback round trip, repro script) and ALTER PAGE; ShareFeedback's dataView5 DataSource ndsl now matches Studio Pro exactly. Control: implementation reverted → the 5 new tests fail with the nested key set. Repro `mdl-examples/bug-tests/dataview-nanoflow-source-ce2633.mdl`","refs":[],"ce":["CE2633"]}
{"area": "mdl/backend", "date": "2026-09-25", "symptom": "`returns list of pEntity` for a declared type parameter produced mx check CE1613 \"The selected entity '.pEntity' no longer exists.\" on the action and on a `list of pEntity` parameter; a Studio Pro \"List of <type parameter>\" read back as a bare `List` (DESCRIBE and catalog) and a rewrite of it would serialize a list of an unnamed entity.", "cause": "types.ListType carried only Entity. The reader handled only a ConcreteEntityType list element, the writer always emitted one, and CREATE sent `list of T` down the entity path (Module \"\" + \".\" + T).", "file": "mdl/types/javaaction_types.go (ListType.TypeParameterID), mdl/backend/modelsdk/java_read.go (listTypeFromGen), java_write.go (codeActionListTypeToGen), javascript_read.go, mdl/executor/cmd_javaactions.go (listOfTypeParameter)", "insight": "The Model SDK is the arbiter for which element a slot accepts: `ParameterizedEntityType.createInListTypeUnderParameter` (metamodel 7.21.0+) settles that a list element may be a type parameter, so no version gate. No fixture had a Studio Pro-authored instance, so the evidence is mx check on 11.6.6: previous build CE1613 x2, fixed build 0 errors, clean baseline 0. The JavaScript writer reuses the Java converter, so one write fix covers both; the JS reader is separate raw-map code and needed its own case.", "refs": ["mendixlabs/mxcli#1183"]}
{"area": "mdl/backend", "date": "2026-09-26", "symptom": "`ALTER PAGE … SET ImageUrl = '…' ON img1` (pluggable Image) prints \"Altered page\" and changes nothing: DESCRIBE still shows the old URL. Same silent success for SET on any pluggable widget property of kind Expression, Image, Icon, Action, DataSource, Attribute or Widgets", "cause": "setPluggableWidgetPropertyMut (mdl/backend/pagemutator/mutator.go) wrote every value to Value.PrimitiveValue. imageUrl is a TextTemplate; readers take Value.TextTemplate. The DataGrid 2 column setter had the identical defect fixed on 2026-08-18 (columnValueField) — the widget-level setter sitting next to it was never given the same schema dispatch.", "file": "mdl/backend/pagemutator/mutator.go (setPluggableWidgetPropertyMut, buildPropKindMap), test pluggable_property_kind_test.go, example mdl-examples/bug-tests/1201-alter-page-set-pluggable-texttemplate.mdl", "insight": "When a fix lands on one setter for 'write the field the schema declares', grep the sibling setters in the same file for the same always-PrimitiveValue write — a WidgetValue carries every variant field, so the wrong write never errors and every signal stays green. The live control is cheap without mxbuild: copy testdata/expr-checker, create an image with URL A, ALTER to B, describe (pre-fix binary shows A). A null TextTemplate means the slot is hidden (#574); ALTER does not re-run visibility, so refuse rather than build an envelope there. Not fixed here: a pluggable boolean SET stores \"yes\"/\"no\" — check what CREATE stores before changing it.", "refs": ["mendixlabs/mxcli#1201", "mendixlabs/mxcli#1069", "mendixlabs/mxcli#750", "mendixlabs/mxcli#574"], "rules": []}
{"area": "mdl/backend", "date": "2026-09-26", "symptom": "describe → exec of a snippet turns its Type 'Web' into '' (and would reset a non-default ExportLevel or canvas size)", "cause": "snippetToGen wrote Type, ExportLevel, CanvasWidth and CanvasHeight as constants on every write; the Type constant was \"\"", "file": "mdl/backend/modelsdk/snippet_write.go", "insight": "The snippet twin of #541 (carryStoredPageHeader). \"\" is not a member of PagesType (Native | Web), so every snippet mxcli created was also invalid. Carry the stored header on UpdateSnippet (width-agnostic canvas read — Studio Pro stores int64); a new snippet gets Web/Hidden/800×600, measured on all four Blank-template snippets", "refs": ["ako/mxcli#705", "#541"]}
{"area": "mdl/backend", "date": "2026-09-26", "symptom": "describe → exec of a nanoflow detaches every annotation from its activity (AnnotationFlows 3 → 0, so the next describe prints the notes as free-floating) and deletes ExportLevel, UseListParameterByReference and ReturnVariableName; MarkAsUsed is cleared. Rules drop their annotation flows too", "cause": "nanoflowToGen and ruleToGen never wrote ObjectCollection.AnnotationFlows (microflowToGen did), and nanoflowToGen never set the three header keys; nanoflowFromGen did not read ReturnVariableName or MarkAsUsed and the builder hardcoded MarkAsUsed false", "file": "mdl/backend/modelsdk/nanoflow_write.go, rule_write.go, microflow.go; mdl/executor/cmd_microflows_build.go", "insight": "Three flow flavours share the object/flow converters but each has its own top-level writer, and each dropped a different subset — grep every *ToGen for the same list loop, not just the reported one. For keys with no MDL spelling and unknown version floors (Nanoflow.ExportLevel, UseListParameterByReference), carry ONLY the keys the stored unit has: nothing was ever written on create, Studio Pro fills an absent key, and an undeclared one makes the unit unopenable", "refs": ["ako/mxcli#705"]}
Loading
Loading