Skip to content

fix(pages): read a pluggable widget's flow datasource, and stop dropping children silently - #59

Merged
ako merged 3 commits into
mainfrom
claude/pages-datasource-describe-795
Jul 30, 2026
Merged

fix(pages): read a pluggable widget's flow datasource, and stop dropping children silently#59
ako merged 3 commits into
mainfrom
claude/pages-datasource-describe-795

Conversation

@ako

@ako ako commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Fixes mendixlabs/mxcli#795. Split out of #57.

(Issue numbers refer to mendixlabs/mxcli, not this fork.)

585f5f0 — read the microflow datasource from MicroflowSettings (mendixlabs#795)

A DataGrid2 bound to a microflow described as datagrid g1 { with no DataSource, so re-applying the describe output recreated the grid unbound. A database from grid described correctly, which made the loss look selective.

The name lives in the nested Forms$MicroflowSettings; extractDataGrid2DataSource read a top-level Microflow key, got "", and returned no datasource. The describe formatter was correct all along — purely a read bug.

Four readers each had their own copy of this lookup and disagreed. They now share microflowSourceRef / nanoflowSourceRef, which read the nested settings and fall back to the legacy top-level key.

Two further gaps the regression tests turned up:

  • a Gallery using the legacy flat shape was unreadable too
  • Forms$NanoflowSource was missing entirely from the DataGrid2 and Gallery switches

Also corrects the create-page skill, which documented datasource: microflow Module.GetData() — the trailing () does not parse.

78699b8 — fail the write when a child is dropped

A construct the codec engine cannot represent (a nanoflow datasource on a DataGrid2 is the reachable case) was logged and discarded while mxcli exec still printed Created page.

The converter does return an error; it had nowhere to go. widgetobj.ChildSerializer returns BSON with no error channel — the interface change flagged by the existing TODO(shared-types) in builder.go. Until that lands, the failure is recorded and drained by the page/snippet write entry points, so the statement fails instead of the write succeeding with data missing. ADR-0004 already required this.

Verified the error's own advice is true: MXCLI_ENGINE=legacy does write a nanoflow datasource correctly. That surfaced one more gap — the describe formatter had only database/microflow/parameter cases, so a legacy-written nanoflow grid still described with no DataSource. Added the nanoflow case.

Trade-off: the accumulator is package-level, so any new write entry point building pluggable widgets must drain too. Recorded in the symptom table.

6e59e0e — describe a flow datasource's context as the entity it returns

widget.EntityContext = widget.DataSource.Reference is right for a database source and wrong for a flow source:

-- Context: $currentObject (MyFirstModule.Bucket)     ← was (…DBG_ListBucketObjects)

More than cosmetic: EntityContext is passed to the column and child-widget readers, which resolve attributes against it. Unresolvable flows and scalar returns keep the reference, so nothing regresses.

Resolution goes through ListMicroflows/ListNanoflows + the container hierarchy rather than GetRawUnitByName, which is unimplemented on the modelsdk engine — the default. The obvious-looking approach would have silently done nothing.

Testing

make build, make test, make lint, make check-mdl pass on this branch alone.

The describeexecdescribe round-trip the issue is about now produces byte-identical output. New tests in cmd_pages_describe_flowsource_test.go, cmd_pages_describe_flowcontext_test.go and widget_child_error_test.go (all verified failing pre-fix — the flowsource table also caught the two gaps I had not predicted), plus fixtures 795-datagrid-microflow-datasource-describe.mdl and 795b-flow-datasource-context-entity.mdl.

Out of scope

A nanoflow datasource on a pluggable widget still cannot be authored by the modelsdk engine. modelsdk/gen/pages has no NanoflowSettings type, the reflection data is not in the tree so it cannot be regenerated, and there is no Studio Pro sample to verify the field set against — hand-writing a guessed shape is how CE0463-class bugs are made. Authoring needs --engine legacy; it now fails loudly rather than quietly.

🤖 Generated with Claude Code

https://claude.ai/code/session_012XR649rKk68z6gBpngu6MA


Generated by Claude Code

claude added 3 commits July 30, 2026 22:23
…flowSettings (mendixlabs#795)

DESCRIBE PAGE emitted `datagrid g1 {` with no DataSource for a DataGrid2
bound to a microflow, so re-applying the describe output recreated the grid
with no data source at all. A `database from` source described correctly,
which made the loss look selective.

A Forms$MicroflowSource stores the microflow name in the nested
Forms$MicroflowSettings ("MicroflowSettings" -> "Microflow") — what the write
path emits and what Studio Pro stores. extractDataGrid2DataSource looked up a
top-level "Microflow" key, got "", and returned no datasource. The describe
formatter's `case "microflow"` branch was correct all along, so nothing was
ever wrong with the stored model or the write path: purely a read bug.

Four readers had their own copy of this lookup and disagreed — two read the
nested settings, one read the top-level key, one did both. They now share
microflowSourceRef / nanoflowSourceRef, which read the nested settings and
fall back to the legacy top-level key so older files still round-trip.

Two gaps the regression tests found while covering this:

- a Gallery whose source used the legacy flat shape was unreadable too: the
  correct reader it delegates to handles only the nested form, and the flat
  fallback in its own switch is not reached for a CustomWidget gallery
- Forms$NanoflowSource was missing entirely from the DataGrid2 and Gallery
  switches, so a Studio-Pro-authored nanoflow datasource described as nothing

Left alone deliberately: CustomWidgets$CustomWidgetNanoflowSource, a different
metamodel type whose Nanoflow key really is top-level, and the
Forms$MicroflowAction reads, which are actions rather than datasources.

Also corrects the datasource table in the create-page skill: it documented
`datasource: microflow Module.GetData()`, and the trailing `()` does not parse.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XR649rKk68z6gBpngu6MA
A construct the codec engine cannot represent — a nanoflow datasource on a
DataGrid2 is the reachable case — was logged and discarded while `mxcli exec`
still printed `Created page`. The grid landed with no data source at all.

The converter does return an error; it just had nowhere to go. Child content
is serialized through widgetobj.ChildSerializer, whose methods return BSON
with no error channel — the interface change flagged by the TODO in
mdl/backend/widgetobj/builder.go. Until that lands, the failure is recorded
in the modelsdk backend and drained by the page/snippet write entry points,
so the statement fails with the actionable message instead of the write
succeeding with data missing. ADR-0004 already required this: where the codec
path cannot reproduce a construct the backend refuses the op rather than
dropping it.

Authoring a pluggable nanoflow datasource still needs MXCLI_ENGINE=legacy,
which writes it correctly — verified, since the error message says so.

Also adds the missing `nanoflow` case to the DataGrid2 and Gallery describe
formatters. Reading such a source works after mendixlabs#795, but the formatter had
only database/microflow/parameter branches, so a legacy-written or
Studio-Pro-authored nanoflow grid still described with no DataSource.

Any new write entry point that builds pluggable widgets must drain too; the
symptom table records that.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XR649rKk68z6gBpngu6MA
…urns

A data container bound to a microflow or nanoflow reported the *flow* as its
context entity:

  datagrid g1 (DataSource: microflow Module.GetOrders) {
    -- Context: $currentObject (Module.GetOrders)   <- the flow, not the entity

The datasource reference was used verbatim. That is correct for a database
source, where the reference is the entity, and wrong for a flow source, where
it is the flow's own qualified name. Besides the misleading comment, the
context is passed down to the column and child-widget readers, which resolve
attributes against it.

The flow's return type is resolved instead, taking the entity from an Object
or List return. When the flow cannot be resolved, or returns a scalar, the
reference is kept — the previous behaviour, so nothing gets worse.

Resolution goes through ListMicroflows/ListNanoflows plus the container
hierarchy rather than GetRawUnitByName, which the flow builder's fast path
uses: that method is unimplemented on the modelsdk engine, which is the
default, so the list path is the one that actually resolves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XR649rKk68z6gBpngu6MA
@ako
ako merged commit 55d9b84 into main Jul 30, 2026
3 checks passed
ako pushed a commit that referenced this pull request Aug 1, 2026
Every bug fix touches .claude/skills/fix-issue.md, and new rows went in directly
under the table header. Two branches fixing unrelated bugs therefore inserted at
the same line, which git cannot merge — a conflict by construction rather than by
bad luck. It cost five separate resolution rounds in one week (#59-#62 three
times, then #68 and #69), and each round risks silently dropping a row: one
earlier round was "resolved" by deleting a PR's rows entirely and restoring them
in a follow-up.

Appending puts each branch's insert at a different offset, which git merges
without help. The table is unordered — it is looked up by matching a symptom, not
read top to bottom — so position carries no meaning and appending costs nothing.

Recorded in both places a contributor might look: the skill's How to Use, with
the reasoning so it is not "fixed" back, and the PR checklist item in CLAUDE.md.

Existing rows are deliberately left where they are. Reordering them would
conflict with every open branch at once, which is the problem this change exists
to avoid.

Folded into this branch rather than opened separately: it edits the same How to
Use block, so a sixth branch would have manufactured exactly the conflict it is
meant to prevent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012XR649rKk68z6gBpngu6MA
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants