Skip to content

Commit 335901a

Browse files
committed
fix(widgets): read attrChoice "linked" when a filter carries an Attributes list
MDL-WIDGET10 evaluated a filter widget's hide rule against the hand-written definition's fixed attrChoice "auto", while the writer forces "linked" as soon as an attribute list is written. The result was a false warning that the list "will be ignored" on a Gallery filter that is stored correctly (the same shape as WorkflowCommons.MyInitiatedWorkflows, Studio Pro-authored). Field run: ProcureFlow (Mendix 11.14.0) scratch copy, TaskInbox-style gallery with textfilter Attributes: [Name] and datefilter Attributes: [StartTime]: check now 0 warnings; stored BSON attrChoice linked + attribute refs; mx check 0 errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D5dJ4raEjbm8bFiKeYtchS
1 parent 9509176 commit 335901a

4 files changed

Lines changed: 63 additions & 0 deletions

File tree

‎.claude/skills/fix-issue/findings/mdl-executor.jsonl‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -713,3 +713,4 @@
713713
{"area": "mdl/executor", "date": "2026-09-25", "symptom": "`textbox t (Attribute: FullName)` at the top of a page (CREATE PAGE/SNIPPET, a plain container, or ALTER PAGE … INSERT at page level) passed plain `mxcli check`, `exec --no-check`/ALTER reported success, and `bson dump` showed `AttributeRef: null` — mxbuild 11.13.0: CE0544 \"This widget can only function inside a data context\" + CE7005 (textbox/textarea/datepicker/checkbox/radiobuttons/dropdown), CE0402 (dynamictext Attribute:), CE0642 (combobox). Qualified `Mod.Ent.Attr` there is stored and fails CE0544/CE2421/CE1365/CE7247 \"Move this widget into a data container\" + CE7006. `Attribute: $P/Attr` / `$currentObject/Attr` dropped even INSIDE a data view.", "cause": "resolveAttributePath returns the bare name when entityContext is \"\", and attributeRefToGen (and widgetobj setAttributeRefField) write nil for any path with < 2 dots, so the binding vanished between builder and writer; refuseBareAttributeRefs never sees it because no Attribute string is emitted. The only refusal (validatePageContextTree) runs in the --references phase for CREATE PAGE/SNIPPET, so plain check, --no-check and ALTER were unguarded. `$x/Attr` parses via the generic property rule as an *ast.DataSourceV3, so GetAttribute() returns \"\" and every builder skipped it.", "file": "mdl/executor/cmd_pages_input_binding_context.go (inputBindingProblem, checkInputBinding, validateInputBindingContext = MDL-WIDGET34), wired in cmd_pages_builder_v3_widgets.go (6 input builders + buildDynamicTextV3), widget_engine.go (primary Attribute mapping), validate_widgets.go (validateWidgetTreeIn); tests cmd_pages_input_binding_context_test.go; bug-tests input-binding-without-context{,.fail}.mdl", "insight": "Reuse the MDL-PAGEARG01 three-state context (pageArgContext known/present) rather than entityContext==\"\" as the 'outside a data container' signal: entityContext is also empty INSIDE a container whose flow cannot be resolved (excluded ShareFeedback_Logo), where DESCRIBE writes qualified names that must keep building — refusing qualified-on-empty-entity would have broken that round trip. So known-absent context refuses bare AND qualified; unknown context (ALTER) refuses only the bare name the writer provably nulls. Two existing unit tests (OnChangeSurvivesBuilder, DynamicTextV3_AttributeBinds) built inputs with NO entity and passed — the second asserted a bare `Title` AttributeRef counted as 'bound', i.e. it pinned the bug: when a fixture has no entity context, ask what the writer does with its output. The `$P/Attr` drop was found only by dumping the control page, not from the report — print the AST value type with a probe test before assuming a spelling reaches the builder. Evidence: 22 mxbuild errors before on the probe matrix; after, every case refused with nothing written, controls (dataview/listview/gallery/datagrid/snippet dataview/ALTER into dataview) 0 errors, 17/17 stock pages + 4/4 snippets describe→exec round trip.", "refs": ["MDL-WIDGET34"], "ce": ["CE0544", "CE7005", "CE0402", "CE0642", "CE2421", "CE1365", "CE7247", "CE7006"]}
714714
{"area":"mdl/executor","date":"2026-09-25","symptom":"`alter page FeedbackModule.ShareFeedback_Logo { insert after textBox1 { image zzImg (ImageType: imageUrl, ImageUrl: '{1}', ImageUrlParams: [{1} = ImageB64]) } }` passed `check --references`; exec wrote a bare AttributeRef and `mx check` could not LOAD the project (ArgumentNullException setting 'Attribute'). Same at page top level outside any data container; a text box's `Attribute:` there is silently dropped (CE7005).","cause":"ALTER's entity context comes from the STORED document (nearest enclosing data source, or a flow source's return type via resolveDataSourceFlowEntity). With the flow missing (Feedback v4.0.2 ships no DS_FeedbackForm) or no container at all, entityContext is \"\" and resolveAttributePath returns the bare name. CREATE PAGE refused this at check time (relaxExcludedWidgetRefs/unscopedBindings, #678); ALTER's check never opened the document, so nothing could know the scope.","file":"mdl/executor/validate_alter_unscoped.go, mdl/executor/cmd_alter_page.go (alterEntityContext), mdl/executor/validate.go (bindingsWithoutScope)","insight":"For ALTER, scope is a property of the stored document, not the statement: a check-time question about it must open the document (OpenPageForMutation, never Save; validate_alter_set.go already does this) and ask through the SAME function exec uses, so the INSERT/REPLACE entity resolution was lifted into alterEntityContext rather than restated, and the binding walk lifted out of unscopedBindings (bindingsWithoutScope) rather than copied. Controls that keep it from blocking working scripts: skip a target the stored doc lacks (added earlier in the script), a flow the script declares with an entity return (sc.flowParams), DataGrid2 column and list-view-template paths, and documents the script creates. Reproduce with a Studio Pro-authored page whose flow is genuinely absent.","refs":["#678","#685"]}
715715
{"area": "mdl/executor", "date": "2026-09-25", "symptom": "`describe page` on a File Uploader (files mode) emits `DataSource: association …`, and exec of that output fails: \"widget `upFiles` (fileuploader) exposes 2 datasources, so a generic `datasource:` clause is ambiguous — name the one you mean: associatedFiles, associatedImages\"", "cause": "DESCRIBE chose generic vs named by counting CONFIGURED datasources (namedCustomWidgetDataSources drops unset ones, so files mode = 1), while the builder's refuseAmbiguousGenericDataSource counts DECLARED datasource mappings (a generated def maps every top-level datasource = 2). Two sides of one round trip deciding the same question from different evidence.", "file": "mdl/executor/cmd_pages_describe_parse.go", "insight": "When describe and build each decide 'is this ambiguous?', they must count the same set. Fix read the DECLARED count from the stored schema (PropertyTypes with ValueType.Type=DataSource, excluding IsLinked) and excluded widgets with an embedded .def.json — those are hand-written and pick one datasource mapping per mode, so a database-mode ComboBox (two declared) must keep the generic clause. The #956 bug-test script itself authored the refused generic clause on a File Uploader: a bug-test that only runs `mxcli check` without a project can't see an exec-time refusal, so grep bug-tests for the old spelling whenever a builder starts refusing one.", "refs": ["mendixlabs/mxcli#1199", "mendixlabs/mxcli#956", "mendixlabs/mxcli#1109"], "rules": []}
716+
{"area": "mdl/executor", "date": "2026-09-26", "symptom": "`textfilter tf (Attributes: [Name])` / `datefilter` in a Gallery `filter` block warns MDL-WIDGET10 \"property `attributes` is hidden when `attrChoice` is \\\"auto\\\" — the value will be ignored\", yet the stored widget carries attrChoice \"linked\" and the attribute list", "cause": "widgetValueMap resolved attrChoice from the hand-written filter def's fixed primitive value \"auto\", while widgetobj.Builder.SetAttributeObjects forces \"linked\" whenever an attribute list is written. Checker and writer read the same property from different sources.", "file": "mdl/executor/validate_widgets.go", "insight": "A value the WRITER overrides must be overridden in widgetValueMap too, or MDL-WIDGET10 reasons about a configuration that never reaches disk. The false 'will be ignored' is worse than silence: a real project read it as 'linked filter attributes are not writable' and left a Gallery filter row for Studio Pro. Settled by dumping the stored BSON next to a Studio Pro-authored reference (WorkflowCommons.MyInitiatedWorkflows gallery1 textFilter1: attrChoice linked, linkedDs empty, attributes = one WidgetObject per attribute).", "rules": ["MDL-WIDGET10"]}

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
1414

1515
### Fixed
1616

17+
- **A filter's `Attributes: [...]` list was reported as "ignored" when it is written** — `textfilter tf (Attributes: [Name])` in a Gallery `filter` block or a Data grid 2 column is stored with `attrChoice` "linked" (Studio Pro's "Custom") and the attribute list, the same shape as Studio Pro's own WorkflowCommons.MyInitiatedWorkflows gallery filters. MDL-WIDGET10 judged it against the definition's fixed `attrChoice: auto` instead and warned the list "will be ignored", which sent a project to Studio Pro for a filter mxcli had written correctly. The checker now reads the `linked` value the writer stores.
1718
- **Keyword operators were fused in some stored expressions** — a page action's microflow argument `Flag: $a and $b` was stored as `$aand$b`, `if $x then 'a' else 'b'` as `if$xthen'a'else'b'`; the same in `contentparams` values, `send rest request … with (…)` parameters and a dynamic `execute database query`. Those four places took the expression's text without its whitespace; literals, `+` and `$currentObject` were unaffected, which is why it went unnoticed, and `check --references` passed. Measured by decoding the stored units on a Mendix 11.14.0 project. The expression is now stored as written, with MDL comments removed, as the microflow expression sites already did.
1819
- **An input bound to an attribute outside any data container was written with no binding** — `textbox t (Attribute: FullName)` at the top of a page (or of a snippet, or inside a plain container) has no entity to qualify the name with, and the writer stored it as `AttributeRef: null`. Plain `check` passed, `exec --no-check` and `alter page … insert` at page level said success, and mxbuild 11.13.0 failed the page with CE0544 "This widget can only function inside a data context" + CE7005 (text box, text area, date picker, check box, radio buttons, drop-down), CE0402 (dynamic text `Attribute:`) or CE0642 (combo box). A qualified attribute there is stored and fails the same way (CE0544 / CE2421 / CE1365 / CE7247 "Move this widget into a data container"). `Attribute: $P/Name` and `Attribute: $currentObject/Name` never parsed as an attribute path and were dropped even inside a data view. `check` now reports all three as **MDL-WIDGET34** (no project needed), and the page builder refuses them with the widget named, so nothing is written; `alter page` refuses a bare name it has no entity for. Place the widget in a data view, list view, gallery or data grid and bind the attribute by name.
1920
- **An expression property written in brackets was silently dropped** (mendixlabs/mxcli#750) — `dynamicclasses: [ if $currentObject/Featured then 'a' else 'b' ]`, the spelling #750 proposes, parsed as a list that no writer reads: `check` was clean, `exec` said `Created page`, and the widget was stored with no dynamic class. `alter page … set DynamicClasses = [ … ]` said `Altered page` and changed nothing, and a column's `DynamicCellClass` stored the list's text — tokens fused, `[if$x/Ythen'a'else'b']` — as its expression. Measured on a copy of a Mendix 11.14.0 project with the pre-fix binary. `mxcli check` now reports **MDL-WIDGET32** for `DynamicClasses` and `DynamicCellClass` written as a list (no project needed), and ALTER refuses it, so `check -p` reports that too. Write the expression quoted.

‎mdl/executor/validate_widgets.go‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,19 @@ func widgetValueMap(w *ast.WidgetV3, def *WidgetDefinition) (values map[string]s
505505
values[key] = val
506506
}
507507
}
508+
// An explicit attribute list forces a filter's attrChoice to "linked" on
509+
// write (widgetobj.Builder.SetAttributeObjects), whatever the definition's
510+
// fixed value says. Read the value the writer stores, or MDL-WIDGET10 judges
511+
// "attributes is hidden when attrChoice is auto" against a configuration that
512+
// is never written — and tells the author a list that IS stored "will be
513+
// ignored".
514+
for _, m := range mappings {
515+
if m.Operation == "attributeObjects" && explicit[strings.ToLower(m.PropertyKey)] {
516+
if _, ok := values["attrchoice"]; ok {
517+
values["attrchoice"] = "linked"
518+
}
519+
}
520+
}
508521
// Direct MDL properties named after a widget key (not covered by a mapping).
509522
for k, raw := range w.Properties {
510523
lk := strings.ToLower(k)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
// A data-grid filter widget given an explicit `Attributes: [...]` list is written
4+
// with attrChoice "linked" ("Custom" in Studio Pro) — widgetobj.Builder's
5+
// SetAttributeObjects forces it, because "auto" beside a populated list is
6+
// definition drift. The checker must read the same value the writer stores.
7+
//
8+
// It did not: the hand-written filter definitions map attrChoice to the fixed
9+
// value "auto", so MDL-WIDGET10 evaluated editorConfig's "attributes is hidden
10+
// when attrChoice is auto" and told the author the list "will be ignored" — on a
11+
// widget whose stored BSON carries attrChoice "linked" and the attribute list,
12+
// the exact shape of Studio Pro's WorkflowCommons.MyInitiatedWorkflows gallery
13+
// filters. The false warning is what sent a real project to Studio Pro for a
14+
// Gallery text/date filter mxcli had been writing correctly all along.
15+
package executor
16+
17+
import (
18+
"testing"
19+
20+
"github.com/mendixlabs/mxcli/mdl/ast"
21+
)
22+
23+
func TestWidgetValueMap_FilterAttributesListSelectsLinked(t *testing.T) {
24+
reg, err := NewWidgetRegistry()
25+
if err != nil {
26+
t.Fatalf("registry: %v", err)
27+
}
28+
for _, name := range []string{"TEXTFILTER", "DATEFILTER", "NUMBERFILTER"} {
29+
def, ok := reg.Get(name)
30+
if !ok {
31+
t.Fatalf("%s: no definition", name)
32+
}
33+
w := &ast.WidgetV3{Type: name, Name: "f", Properties: map[string]any{
34+
"Attributes": []string{"Name"},
35+
}}
36+
values, _ := widgetValueMap(w, def)
37+
if got := values["attrchoice"]; got != "linked" {
38+
t.Errorf("%s with an Attributes list: attrChoice resolved to %q; the writer stores \"linked\", so MDL-WIDGET10 judges a configuration that is never written", name, got)
39+
}
40+
41+
// Control: without a list the writer leaves the definition's value alone.
42+
bare := &ast.WidgetV3{Type: name, Name: "f", Properties: map[string]any{}}
43+
values, _ = widgetValueMap(bare, def)
44+
if got := values["attrchoice"]; got == "linked" {
45+
t.Errorf("%s without an Attributes list: attrChoice resolved to \"linked\"; only a populated list forces it", name)
46+
}
47+
}
48+
}

0 commit comments

Comments
 (0)