diff --git a/.claude/skills/fix-issue/findings/mdl-backend.jsonl b/.claude/skills/fix-issue/findings/mdl-backend.jsonl index c213aee76..71727447b 100644 --- a/.claude/skills/fix-issue/findings/mdl-backend.jsonl +++ b/.claude/skills/fix-issue/findings/mdl-backend.jsonl @@ -94,3 +94,4 @@ {"area": "mdl/backend", "date": "2026-09-12", "symptom": "Four plain widget keywords were refused by the DEFAULT engine with \"widget *pages.X not yet supported by the modelsdk engine — rerun with MXCLI_ENGINE=legacy\": statictext, dropdown, staticimage, dynamicimage (plus NanoflowSource as a list data source). Nothing in mdl-examples/ or in any skill used those keywords, so the doctype gate ran green the whole time. Then, closing the gap: `statictext` on EITHER engine produced a project that could not be loaded at all — `mx check` aborts with TypeCacheUnknownTypeException for Forms$Text before running any validation, and Studio Pro fails the same way", "cause": "Two unrelated things. (1) The modelsdk widget dispatch simply had no case arms for those five types — reachable set measured at five, not the twenty-three a name scan suggests: nineteen pages.* widget structs are constructed by nothing, and EntityPathSource / ShowHomePageClientAction are written by NEITHER engine, so their fallback named a path legacy could not take either. (2) `Forms$Text` is not a Mendix type. It is absent from modelsdk/gen, from generated/metamodel, and from all 3,257 Studio Pro Texts$Text holders in ako/TestApp; `buildTextWidgetV3` minted it for both `text` and `statictext`. Writing it is worse than a build error — the project is unopenable and unfixable in the modeler", "file": "`mdl/backend/modelsdk/widget_write_legacy_gaps.go` (new), `mdl/backend/modelsdk/widget_write.go`, `sdk/mpr/writer_widgets_display.go`, `mdl/executor/validate_widget_retired.go` (new), `mdl/executor/cmd_pages_builder_v3_widgets.go`", "insight": "**\"Parity with the other engine\" is the wrong bar until you have checked whether the other engine is right.** That was the starting assumption here and measuring overturned it: ako/TestApp carries three Studio-Pro-authored Forms$StaticImageViewer widgets (FeedbackModule) and legacy disagreed with all three — it omitted AlternativeText (non-omitempty in generated/metamodel) and wrote BSON null for the unset Image. Legacy's dynamic image was worse: a hand-rolled AlternativeText carrying a FallbackValue key that Forms$ClientTemplate does not have, four lines after a comment in the SHARED serializer saying exactly that (\"Must be Fallback object, not FallbackValue string\"). sdk/mpr was corrected to match rather than pinned as truth. **Look for a Studio Pro reference inside the fixture before concluding there is none** — `bson.Unmarshal` every mprcontents unit and count instances of the $Type; the marketplace modules a blank app ships are Studio Pro output. The same 40-line scanner settles value-shape questions no doc answers: measured 0 nulls against 4,400+ empty strings for by-name references, so an unset QualifiedName is \"\", never null; and marker 3 in 3,257 of 3,257 Texts$Text lists, so legacy's marker 2 for a non-empty one is the wrong side of that divergence, not modelsdk's 3. **A widget keyword the example corpus never uses is a keyword nothing tests**, whatever the coverage numbers say. **Check that the project still LOADS, not just that it builds** — a load failure aborts `mx check` before the error list, so grepping for CE-codes finds nothing and reads like success", "refs": []} {"area": "mdl/backend", "date": "2026-09-12", "symptom": "The modelsdk (default) engine leaves 19 of FullBackend's 276 methods to the errUnimplemented stub, which tells the user to \"rerun with MXCLI_ENGINE=legacy\" — the engine being retired. Nineteen unported methods reads as nineteen reasons legacy has to stay shipped and tested", "cause": "Seventeen of the nineteen cannot be reached at all: they are interface surface that only the MPR backend's own delegation and callers holding a concrete *mpr.Reader / *mpr.Writer (api/, examples/, cmd/mxcli commands that open a reader directly) ever touch, so the stub can never fire. The two that ARE reachable — GetRawUnitByName and ParseMicroflowBSON, four call sites in mdl/executor/cmd_microflows_builder.go — were invisible because each is a fast path with a working slow-path fallback: the stub errored, the code fell through to an O(n) module walk, and the result was identical", "file": "`mdl/backend/modelsdk/raw_lookup.go` (new), `mdl/backend/modelsdk/unimplemented_reachability_test.go` (new), `scripts/backend-reachability.sh` (new)", "insight": "**Grep cannot answer \"is this interface method reachable\" and the compiler can.** `b.reader.GetRawUnitByName(...)` inside the MPR backend and `ctx.Backend.GetRawUnitByName(...)` in the executor are indistinguishable to a regex, and receiver names vary, so a grep-based count said all nineteen had callers. Deleting one method from its interface at a time and rebuilding gives a yes/no per method with no judgement involved: 17 DEAD, 2 LIVE. Slow (one `go build ./...` each, minutes for the set) but decisive — script it, commit it, and pin its OUTPUT in a test rather than re-running it in CI. **A fallback hides an unimplemented method completely.** Nothing was broken and no test failed, because the fast path's failure was indistinguishable from a cache miss; the only symptom was work being done twice. Look for `if x, err := …; err == nil` fast paths when auditing what an engine cannot do. **Measure the speedup before claiming one**: restoring the fast path made no measurable difference (620ms vs 608ms over three runs on a 138-microflow project, within noise), because `mxcli exec` runs `check` first and check's own helpers load ListMicroflows anyway — so the cache the slow path builds is already warm. The value here is the removal of the errUnimplemented cliff, not speed", "refs": []} {"area": "mdl/backend", "date": "2026-09-11", "symptom": "`ALTER PAGE REPLACE`/`INSERT` inside a data view bound `datasource: selection ` re-scopes the new widget's attribute binding to the OUTER data view's entity (**CE1613** \"The selected attribute 'Mod.Outer.Attr' no longer exists\"), and inside a Gallery/DataGrid 2 sourced by a **microflow/nanoflow** drops it entirely (**CE0402** \"No value specified\", `describe` shows `ContentParams: [{1} = ]`). `mxcli check --references` and `exec` both report success; `CREATE PAGE` binds the same widget in the same position correctly", "cause": "The mutator resolved a widget's scope in TWO walks that each knew a different subset of the ten Forms$*Source kinds. `Forms$ListenTargetSource` carries no EntityRef at all \u2014 only the listen target's NAME \u2014 so the entity walk saw no source on the selection data view and left the context at the enclosing one. The flow walk (`findNearestDataSourceDoc`) read only a widget's TOP-LEVEL `DataSource` key, so a pluggable list \u2014 whose source sits at `Object.Properties[datasource].Value.DataSource` \u2014 contributed nothing, and its `Objects[].Properties[].Value.Widgets` descent (the one the entity walk gained in #935) was missing too", "file": "`mdl/backend/pagemutator/mutator.go` (`resolveSourceScope`/`resolveSourceScopeVia`, `listenTargetDataSource`, `widgetOwnDataSourceDoc`, `pluggableDataSourceDoc`; `EnclosingEntity`/`EnclosingEntityForChildren`/`EnclosingDataSourceFlow` now share the one walk `findNearestDataSourceDoc`, and `findEnclosingEntityContext` + its two helpers are deleted)", "insight": "**Count the source kinds before fixing one.** `generated/metamodel/types.go`'s `DataSource is implemented by` list closes the set at ten, and they divide exactly three ways \u2014 seven carry an EntityRef, two are flows, one (ListenTarget) borrows the scope of the widget it names \u2014 so one resolver can be complete, where three successive per-kind patches (FINDINGS #55 association+flow, #935 pluggable, this one) each left a hole. **A nearer source that resolves to no entity must SHADOW the outer one**: inheriting is what wrote the wrong entity, and it also mis-scoped a flow-sourced list nested in an entity-bound data view \u2014 a case the report did not name and the old code got wrong. The listen target is found by a shape-independent search for \"a document with this Name that has a data source\", which is what makes it work when the target is a pluggable widget keeping its source three levels inside its Object; a visited-set guards a hand-written listen cycle. **Measurement trap: a CE1613 SUPPRESSES the CE0402s in the same `mx check` run** \u2014 the first reading said mxbuild tolerated the unbound parameter, and the CE0402s only appeared once the re-scoped binding was fixed, so count bindings in `describe`, not errors. The issue's own second repro (a Gallery over a DATABASE source) no longer reproduced \u2014 #935 had fixed it \u2014 and the live defect was its flow-sourced variant, so re-measure a report against main before trusting its class. Tests `mdl/backend/pagemutator/mutator_selection_source_test.go` (7, incl. dangling/cyclic listen targets and the shadowing control); repro `mdl-examples/bug-tests/1076-alter-page-selection-and-flow-scope.mdl` \u2014 2 \u00d7 CE1613 + 3 unbound before, 0 errors after, on mxbuild 11.10.0. Each half proven load-bearing by stubbing it alone and rebuilding the CLI", "refs": ["mendixlabs/mxcli#1076", "#55", "#935"], "ce": ["CE0402", "CE1613"]} +{"area": "mdl/backend", "date": "2026-09-15", "symptom": "On the default engine, `describe workflow` printed `wait for notification x;` without its boundary events (and their handler flows); the legacy engine described them. A describe -> exec round trip lost them.", "cause": "workflowActivityFromGen's typed switch had no case for *genWf.WaitForNotificationActivity, so it fell to workflowSimpleActivityFromGen, which reads only Name and Caption from raw BSON. That path's comment said the wait activities 'have no genWf struct' — true once, stale by the time gen gained WaitForNotificationActivity with BoundaryEventsItems().", "fix": "Typed case reading boundaryEventsFromGen(a.BoundaryEventsItems()), like the five other activities that carry boundary events.", "file": "mdl/backend/modelsdk/workflow_read.go", "insight": "A fallback path documented as 'for types gen does not model' silently keeps catching a type after gen starts modelling it; the reader then under-reads without any error. When gen is re-vendored, grep the typed switches for types that newly have structs. The bug surfaced only because a probe described what it had just written — reading back your own write, per activity type, is the cheap check."} diff --git a/.claude/skills/fix-issue/findings/mdl-executor.jsonl b/.claude/skills/fix-issue/findings/mdl-executor.jsonl index d490dadc9..d4d727be6 100644 --- a/.claude/skills/fix-issue/findings/mdl-executor.jsonl +++ b/.claude/skills/fix-issue/findings/mdl-executor.jsonl @@ -613,3 +613,7 @@ {"area":"mdl-executor","date":"2026-09-14","symptom":"DESCRIBE flattens a microflow whose branches cross into nested ifs that mean something else, and warns MDL-FLOW01 'must not be re-executed' instead of describing it","cause":"Nested if/then/else is single-entry/single-exit. A merge reached by branches of two different splits has no place in a tree, so the describer walked past it and emitted the conjunction along one path.","file":"mdl/executor/cmd_microflows_show_crossed.go","fix":"Mode 2: label the shared-suffix entry AND the split's post-dominator, emit `join