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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-11-11", "refs": ["ako/mxcli#2107", "ako/mxcli#826", "ako/mxcli#833", "mendixlabs/mxcli#1591"], "ce": ["CE7247"], "symptom": "describe page -> exec of six FactoryManagement pages (Mendix 11.16) failed: a markdown viewer in an inner data view was refused \"`GenAICommons.Span.Output` is an attribute of GenAICommons.Span, not of ConversationalUI.CopyHelper … CE7247\" (and Span_TextGeneration_SlideIn's markdownViewer3 failed \"failed to resolve mapping for stringAttribute\"); an attribute-based visibility condition failed \"ConversationalUI.TraceView has no attribute HasError\".", "file": "mdl/executor/cmd_pages_parameter_binding.go (namedObjectSourceName, isNamedObjectAttributeText), cmd_pages_describe_pluggable.go and cmd_pages_describe_parse.go (print $dv.Attr / $dv/Assoc/Attr), cmd_pages_builder_v3.go (namedObjectVariable, used by resolveRootedAssociationPath), widget_engine.go (parameterBinding takes a data view; rootedAttributeBinding; BuildContext.AttributeSteps), widget_expression_value.go and validate_widget_expression_value.go (rooted data-source form on an attribute property), cmd_pages_builder_visible_when.go, cmd_pages_input_binding_context.go (MDL-WIDGET34 accepts an enclosing data view), mdl/backend (WidgetObjectBuilder.SetAttributeWithSteps in widgetobj and mcp). Tests: cmd_pages_outer_dataview_binding_2107_test.go", "cause": "Studio Pro stores both bindings with the data-view pair SourceVariable {Widget: <outer data view>, PageParameter: …} (markdownViewer3 also with association steps). parameterSourceName returns \"\" when Widget is set, so describe fell back to the qualified name and the rewrite resolved it against the nearest container. The `$dataView1.Attr` spelling existed for built-in inputs only (#826); the pluggable engine, Visible-by-attribute and MDL-WIDGET34 accepted a parameter only, and no pluggable attribute property could store association steps at all.", "insight": "**When a describe falls back to a qualified name, look for a SourceVariable the reader ignored.** Every one of these bindings carried the pair, and the existing spelling for it just had not been wired into every reader and writer: grep the parameter-only helpers (parameterSourceName, parameterVariable) for their callers, and widen them in ONE helper (namedObjectVariable, namedObjectSourceName) so check, describe and exec agree. Also note that `$x/Assoc/Attr` parses as an association data source, not an expression: a fix tested only through the expression form passes its unit test and still fails the real page (MDL-WIDGET42) - feed the engine the parsed shape. Controls: reverting each edit fails its test; the old binary refuses all six pages, the new one reports Unchanged page on all six; a fresh page with $dvOuter.Output, $dvOuter/Span_Trace/SystemPrompt and Visible: $dvOuter.Flag in (true) builds with mx check 0 errors on 11.16."}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-11-11", "refs": ["ako/mxcli#2109", "mendixlabs/mxcli#1324", "mendixlabs/mxcli#122", "ako/mxcli#2095"], "ce": ["CE0117", "CE0115"], "symptom": "describe page -> exec of four FactoryManagement pages (Mendix 11.16) was refused by check on its own output: \"`actionButton1` reads `$grid1` …, but `grid1` is the data container the widget sits in directly\" (MDL-BUTTON02) and \"$grid1 cannot be stored — … an explicit one is rejected as CE0115\" (MDL-PAGEARG01). The stored pages build (untouched app: 4 unrelated errors).", "file": "mdl/executor/validate_page_button_context.go (checkButtonContextTree: control bar nearest \"\", nearestSelectable; isObjectContextContainer + combobox; checkOwnContainerName skips a show page in a selectable grid's rows), mdl/executor/cmd_pages_showpage_args.go (pageArgContext.rowSelectionOf, namesRowSelection, selectableDataGrid, enteringDataWidgetOf shared by argContextForChildren and both engine sites), mdl/executor/widget_engine.go. Tests: validate_own_container_slots_2109_test.go; updated validate_page_own_container_name_test.go (btnCtlBar) and cmd_pages_showpage_selection_arg_122_test.go", "cause": "Three slots open or move an object context that the walks modelled wrong. A selectable data grid's name is its selection in its rows as well as its control bar (Studio Pro stores Variable {Widget: grid1} from column content). A control bar takes its object from above the grid but sits INSIDE it, so the container above is an outer one, not the nearest. A combo box's option content is its own item context. And the pluggable engine entered a data widget's argument context with its own constructor, so a fix in argContextForChildren alone left exec refusing what check accepted.", "insight": "**Dump the stored slot before deciding which side is wrong, and find the property path, not just the widget.** A small walker mapping each CustomWidget TypePointer to its PropertyKey showed `Datagrid:grid1 > [columns] > [content]`, `[filtersPlaceholder]` (printed as controlbar) and `Combobox > [optionsSourceAssociationCustomContent]` - three different reasons in one rule. Then **re-measure an old 'measured' verdict with today's writer before trusting it**: btnCtlBar's CE0117 on 11.14 was an error in an EXPRESSION `$dvP`; since #2095 the argument is written as the variable pair, and a probe on 11.16 builds it - and even a button DIRECTLY in dvP passing $dvP - with 0 errors (positive control in the same build: CE1365), so MDL-BUTTON02's premise for action arguments may be stale beyond this fix. Controls: reverting each of the five edits fails its test; old binary refuses all four pages, new one reports Unchanged page on the three that MDL-WIDGET03 (#2110) does not block."}
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Fixed

- **`check` accepts the data-container names Studio Pro stores in a grid row, a control bar and a combo box option** (ako/mxcli#2109) — `describe page` → `exec` of four FactoryManagement pages was refused on its own output (MDL-BUTTON02, MDL-PAGEARG01): a show page in a selectable data grid's column passing the grid's selection (`$grid1`), a control-bar button passing the data view around its grid, and a combo box's option content reading the data view around the combo box. All three build in Studio Pro's own pages. `check` and `exec` now accept them and write what Studio Pro stores, and the pages report `Unchanged page`.
- **A binding to an outer data view keeps that data view** (ako/mxcli#2107) — a markdown viewer or an attribute-based visibility condition inside a nested data view, bound to the outer one's object, was described as a bare qualified name and rewritten against the nearest data view: CE7247, or "has no attribute". `$dvOuter.Attr` and `$dvOuter/Assoc/Attr` now work on a pluggable widget's attribute property and in `Visible: … in (…)`, as on input widgets, and `describe` prints them; the six FactoryManagement pages report `Unchanged page`.
- **`describe page` → `exec` no longer rewrites a page over formatting and stored keys MDL cannot state** (ako/mxcli#2106) — on FactoryManagement, 8 pages reported `Replaced page` with nothing changed: a dynamic-classes expression's trailing newlines, an XPath constraint's line breaks and spacing, the order of an enumeration condition's values, an association source's null `SourceVariable` key, and an inherited `createdDate` binding requalified to the specialization. The stored value is now kept where only its form differs, the writer stores the key Studio Pro stores, and a system member resolves to the entity that stores it. A full sweep of the app's 229 pages turns those 8 `Unchanged` and changes no other.
- **`describe page` → `exec` keeps a check box's label position, a radio button group's orientation and a show page action's close count** (ako/mxcli#2106) — none had an MDL spelling, so a round trip of a Studio Pro page reset a check box with its label after the control to the default, turned every vertical radio button group horizontal, and stopped a show page button from closing the page it came from, with `mx check` green. `checkbox` takes `LabelPosition: Default|BeforeControl|AfterControl`, `radiobuttons` takes `RenderHorizontal: false`, and `show page` takes `with (NumberOfPagesToClose: n)`; `describe` prints each when it differs from the default.
- **`mxcli test --local --watch` keeps applying changes on macOS and Linux mxbuild** (mendixlabs/mxcli#1787, refs mendixlabs/mxcli#1786) — the test runner's rebuild called mxbuild's serve process directly, without the recovery `run --local --watch` has, so the second build of a serve process failed on regenerated pages and every later change reported "could not be applied". The rebuild now goes through the same recovery, which also recognises mxbuild's resource-key form of that failure (`IO_PathNotFound_Path`, as macOS and Linux report it) and skips the web-bundle check, which only matters when a browser is served.
Expand Down
17 changes: 16 additions & 1 deletion docs-site/src/reference/page/create-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,14 @@ DATAGRID dgOrders (DataSource: DATABASE FROM Sales.Order, Selection: Single) {

A `SHOW PAGE` in the control bar takes the selection the same way —
`Action: SHOW PAGE Sales.Order_Detail(Order = $dgOrders)` opens the page with
the selected row, and the button is disabled until a row is selected.
the selected row, and the button is disabled until a row is selected. In a grid
with `Selection`, a `SHOW PAGE` in a column's custom content may pass the
grid's selection by name as well, which is what Studio Pro stores there;
`$currentObject` is the row.

A control bar takes its object from above its grid, but sits inside the grid,
so the data view around the grid is an outer one there: `$dvOrder` from the
control bar of a grid in data view `dvOrder` is valid.

`mxcli check -p <app.mpr>` reports a missing argument before mxbuild does.

Expand Down Expand Up @@ -288,6 +295,14 @@ attribute property set by its own key too, such as a Custom chart's
attribute (`Module.Other.Attr`) is refused, because stored without the
parameter it is mx check CE7247.

An **enclosing data view** is named the same way: inside a data view nested in
`dvOuter`, `$dvOuter.Attr` reads the outer data view's object, and
`$dvOuter/Assoc/Attr` reads over an association from it. This works on input
widgets, on a pluggable widget's attribute property
(`markdown md (stringAttribute: $dvOuter.Output)`) and in an attribute-based
visibility condition (`Visible: $dvOuter.HasError in (true)`). It is the form
Studio Pro stores for these bindings.

## Parameters

`module.Name`
Expand Down
51 changes: 51 additions & 0 deletions mdl-examples/bug-tests/2107-binding-through-outer-data-view.mdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
mdl 1;
-- ============================================================================
-- Bug ako/mxcli#2107: a binding to an outer data view resolved against the nearest
-- ============================================================================
--
-- Symptom (before fix): describe page -> exec of six FactoryManagement pages
-- (Mendix 11.16) failed. Studio Pro stores the binding with the data-view pair
-- SourceVariable {Widget: <outer data view>, PageParameter: …}; describe printed
-- the bare qualified name and the rewrite read it against the NEAREST data view:
-- "`GenAICommons.Span.Output` is an attribute of GenAICommons.Span, not of
-- ConversationalUI.CopyHelper … CE7247" (markdown viewer)
-- "ConversationalUI.TraceView has no attribute HasError" (Visible by attribute)
--
-- After fix: `$dvOuter.Attr`, and the rooted `$dvOuter/Assoc/Attr`, on a
-- pluggable widget's attribute property and in `Visible: … in (…)`, store the
-- data-view pair (with the association steps); describe prints them so, and
-- the six pages report `Unchanged page`. mx check 0 errors on Mendix 11.16.0
-- for this script (needs the Markdown widget, as Conversational UI ships it).
--
-- Usage:
-- mxcli exec mdl-examples/bug-tests/2107-binding-through-outer-data-view.mdl -p app.mpr
-- mxcli docker check -p app.mpr -- expect 0 errors
-- ============================================================================

create module BugTest2107;
create module role BugTest2107.User;

create persistent entity BugTest2107.Span (Output: String(unlimited), Flag: Boolean default false);
create persistent entity BugTest2107.Trace (SystemPrompt: String(unlimited));
create association BugTest2107.Span_Trace from BugTest2107.Span to BugTest2107.Trace;
create non-persistent entity BugTest2107.Helper (Note: String(100));

create microflow BugTest2107.DS_Helper () returns BugTest2107.Helper
begin
$Helper = create BugTest2107.Helper (Note = 'x');
return $Helper;
end;
grant execute on microflow BugTest2107.DS_Helper to BugTest2107.User;

create page BugTest2107.Span_Details (Params: ($Span: BugTest2107.Span), Title: 'Span', Layout: Atlas_Core.PopupLayout) {
dataview dvOuter (DataSource: $Span) {
dataview dvInner (DataSource: microflow BugTest2107.DS_Helper()) {
markdown mdOutput (stringAttribute: $dvOuter.Output)
markdown mdPrompt (stringAttribute: $dvOuter/Span_Trace/SystemPrompt)
container cFlag (Visible: $dvOuter.Flag in (true)) {
dynamictext txtFlag (Content: 'flagged')
}
}
}
};
grant view on page BugTest2107.Span_Details to BugTest2107.User;
64 changes: 64 additions & 0 deletions mdl-examples/bug-tests/2109-own-container-name-in-item-slots.mdl
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
mdl 1;
-- ============================================================================
-- Bug ako/mxcli#2109: check refused Studio Pro's own data-container names
-- ============================================================================
--
-- Symptom (before fix): describe page -> exec of four FactoryManagement pages
-- (Mendix 11.16) was refused by check on its own output:
-- "`actionButton1` reads `$grid1` …, but `grid1` is the data container the
-- widget sits in directly — its name is a variable only inside a data
-- container nested below it (CE0117)" [MDL-BUTTON02]
-- "$grid1 cannot be stored — … an explicit one is rejected as CE0115" [MDL-PAGEARG01]
-- The stored pages build. Measured shapes:
-- - a button in a SELECTABLE data grid's column passing `$grid1`: Studio Pro
-- stores Variable {Widget: grid1}, the grid's selection, as from its
-- control bar (MxModelReflection.MxObjectType_Select);
-- - `$dataView3` from the filter bar (control bar) of a grid inside dataView3
-- (OIDC.OIDC_Client_NewEdit): the bar sits inside the grid, so dataView3 is
-- an outer container there;
-- - `$dataView2` in a combo box's option content inside dataView2
-- (AgentCommons.Version_NewEdit): the option is the combo box's own context.
--
-- After fix: check accepts all three; exec writes the variable forms Studio
-- Pro stores; mx check 0 errors on Mendix 11.16.0 for this script.
--
-- Usage:
-- mxcli exec mdl-examples/bug-tests/2109-own-container-name-in-item-slots.mdl -p app.mpr
-- mxcli docker check -p app.mpr -- expect 0 errors
-- ============================================================================

create module BugTest2109;
create module role BugTest2109.User;

create persistent entity BugTest2109.Gate (Name: String(100));
create persistent entity BugTest2109.Item (Name: String(100));

create microflow BugTest2109.ACT_Open ($Gate: BugTest2109.Gate) returns Boolean
begin
return true;
end;
grant execute on microflow BugTest2109.ACT_Open to BugTest2109.User;

create page BugTest2109.Item_Details (Params: ($Item: BugTest2109.Item), Title: 'Item', Layout: Atlas_Core.PopupLayout) {
dataview dvItem (DataSource: $Item) {
dynamictext txtName (Content: '{1}', ContentParams: ({1} = Name))
}
};
grant view on page BugTest2109.Item_Details to BugTest2109.User;

create page BugTest2109.Overview (Params: ($Gate: BugTest2109.Gate), Title: 'Overview', Layout: Atlas_Core.PopupLayout) {
dataview dvGate (DataSource: $Gate) {
datagrid dgGates (DataSource: database from BugTest2109.Gate) {
controlbar {
actionbutton btnOpen (Caption: 'Open', Action: call microflow BugTest2109.ACT_Open(Gate = $dvGate))
}
column (Attribute: Name)
}
}
datagrid dgItems (DataSource: database from BugTest2109.Item, Selection: Single) {
column (Attribute: Name, ShowContentAs: customContent) {
actionbutton btnView (Caption: 'View', Action: show page BugTest2109.Item_Details(Item = $dgItems))
}
}
};
grant view on page BugTest2109.Overview to BugTest2109.User;
11 changes: 11 additions & 0 deletions mdl/backend/mcp/widget.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,17 @@ func (w *mcpWidgetBuilder) SetAttribute(propertyKey, attributePath string) {
}
}

// SetAttributeWithSteps: the MCP payload's attribute has no association
// steps, so a path over associations is reported unsupported rather than sent
// as a direct attribute.
func (w *mcpWidgetBuilder) SetAttributeWithSteps(propertyKey, attributePath string, steps []pages.AttributeRefStep) {
if len(steps) > 0 {
w.note("SetAttributeWithSteps(" + propertyKey + ")")
return
}
w.SetAttribute(propertyKey, attributePath)
}

// SetSourceVariable: the MCP widget payload has no slot for a property's
// parameter source, so a `$Param.Attr` binding is reported unsupported rather
// than sent as one read from the enclosing object.
Expand Down
4 changes: 4 additions & 0 deletions mdl/backend/mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,10 @@ type WidgetObjectBuilder interface {
// and deferred to Finalize, which returns the aggregate result.

SetAttribute(propertyKey string, attributePath string)
// SetAttributeWithSteps is SetAttribute for an attribute reached over
// associations: the AttributeRef's EntityRef holds one step per hop, as
// a rooted path `$Name/Assoc/Attr` stores it beside its SourceVariable.
SetAttributeWithSteps(propertyKey string, attributePath string, steps []pages.AttributeRefStep)
SetAssociation(propertyKey string, assocPath string, entityName string)
// SetSourceVariable names the object an attribute or association property
// reads, when that is not the enclosing data context: a page or snippet
Expand Down
9 changes: 9 additions & 0 deletions mdl/backend/widgetobj/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@ func (ob *Builder) SetAttribute(propertyKey string, attributePath string) {
})
}

func (ob *Builder) SetAttributeWithSteps(propertyKey string, attributePath string, steps []pages.AttributeRefStep) {
if attributePath == "" {
return
}
ob.object = updateWidgetPropertyValue(ob.object, ob.propertyTypeIDs, propertyKey, func(val bson.D) bson.D {
return setAttributeRefField(val, attributePath, steps)
})
}

func (ob *Builder) SetAssociation(propertyKey string, assocPath string, entityName string) {
if assocPath == "" {
return
Expand Down
30 changes: 23 additions & 7 deletions mdl/executor/cmd_pages_builder_v3.go
Original file line number Diff line number Diff line change
Expand Up @@ -2573,13 +2573,8 @@ func (pb *pageBuilder) resolveRootedAssociationPath(attrRef string) (wv pages.Wi
if !ok {
return pages.WidgetVariable{}, "", nil, false
}
var start string
if v, entity, isParam := pb.parameterVariable(root); isParam {
wv, start = v, entity
} else if v, isDV := pb.dataViewVariables[root]; isDV {
wv, start = v, pb.paramEntityNames[root]
}
if start == "" {
wv, start, named := pb.namedObjectVariable(root)
if !named {
return pages.WidgetVariable{}, "", nil, false
}
finalQN, steps, ok = pb.resolveAssociationPathFrom(path, start)
Expand Down Expand Up @@ -3017,6 +3012,27 @@ func (pb *pageBuilder) parameterVariable(name string) (pages.WidgetVariable, str
return pages.WidgetVariable{Variable: name, Kind: pb.parameterSlotKind(name)}, entity, true
}

// namedObjectVariable is parameterVariable widened to an enclosing data view:
// the `$name` of a `$name.Attr` binding read from a page or snippet parameter,
// or else from a data view the widget sits inside, whose SourceVariable is the
// data-view pair {Widget: name, PageParameter: …} (ako/mxcli#826, #2107). It
// returns the SourceVariable to store and the object's entity.
func (pb *pageBuilder) namedObjectVariable(name string) (pages.WidgetVariable, string, bool) {
if wv, entity, ok := pb.parameterVariable(name); ok {
return wv, entity, true
}
name = strings.TrimPrefix(name, "$")
wv, known := pb.dataViewVariables[name]
if !known || pb.isDeclaredParameter(name) {
return pages.WidgetVariable{}, "", false
}
entity := pb.paramEntityNames[name]
if entity == "" {
return pages.WidgetVariable{}, "", false
}
return wv, entity, true
}

// documentKind names what is being built, for messages.
func (pb *pageBuilder) documentKind() string {
if pb.isSnippet {
Expand Down
Loading
Loading