Skip to content

fix: preserve stored widget property metadata on ALTER PAGE REPLACE (CE0463) - #797

Open
ront85 wants to merge 1 commit into
mendixlabs:mainfrom
ront85:fix/112-replace-widget-ce0463
Open

fix: preserve stored widget property metadata on ALTER PAGE REPLACE (CE0463)#797
ront85 wants to merge 1 commit into
mendixlabs:mainfrom
ront85:fix/112-replace-widget-ce0463

Conversation

@ront85

@ront85 ront85 commented Jul 28, 2026

Copy link
Copy Markdown

Problem

Any ALTER PAGE … REPLACE of a pluggable widget produces CE0463 ("The definition of this widget has changed…") on the rebuilt widget — even an identity rebuild that changes nothing:

alter page DOC."SalesDocumentType_Select" {
  replace "comboBox1" with {
    combobox "comboBox1" (
      Attribute: "DOC.DocumentTypeSelector_Partner",
      DataSource: database from MasterData."Partner",
      CaptionAttribute: "Name"
    )
  }
}

mxcli reports success and mxcli check --references passes; only a real MxBuild check catches it. This blocks the whole class of "adjust an existing pluggable widget" edits (e.g. adding an XPath constraint to a combobox datasource). Reproduced identically on v0.13.0 and v0.16.0 against a Mendix 11.12.2 MPRv2 project with Combobox v2.8.1.

Root cause

The rebuild emits CustomWidgets$WidgetPropertyType metadata generated from the embedded template + installed .mpk, while untouched sibling widgets in the same page unit carry metadata from whichever (older) widget version authored them in Studio Pro. A byte-level diff of the page unit before/after an identity rebuild showed exactly two divergences, both per-property display metadata:

Property Stored by Studio Pro Emitted by mxcli
filterInputDebounceIntervalCategory Advanced::Filter Events
onChangeEventCaption On selection On change

A rebuilt widget and its neighbours end up described by two different widget versions in one file, and MxBuild rejects the mix.

Note on #112 (closed): its stated root cause — the template having more PropertyTypes than WidgetProperties — is not the defect. That asymmetry is intentional (system properties Label/Visibility/Editability deliberately have no value entry, per the comment in modelsdk/widgets/generate.go), and 13 other shipped templates share it while working fine. The actual defect is the metadata vintage mismatch above.

Fix

Mutator.ReplaceWidget now grafts the stored (old) widget's per-property display metadata — Caption, Category, Description — onto the replacement's freshly generated Type block, so the rebuilt widget stays consistent with the stored model rather than with the toolchain's template:

  • Matched by slash-joined PropertyKey path (/markers/latitude), not flat key, so distinct nested object types that reuse a key keep independent metadata.
  • Applied only when the replacement's WidgetId matches the replaced widget's — including pluggable widgets nested inside container replacements.
  • Leaf display fields only. $IDs and ValueTypes are never copied, so the replacement's ObjectType cross-references (freshly generated IDs) stay intact.
  • Properties the stored widget doesn't define (added in newer widget versions) keep their generated metadata.
  • No-ops safely when the replaced widget isn't a pluggable widget or the replacement is a different widget package.

This is deliberately scoped to REPLACE, where "match the stored model" is unambiguous; the wider template/schema question stays with #529.

Verification

  • 6 new unit tests in mdl/backend/pagemutator (same-widget graft, different-WidgetId untouched, non-pluggable old widget, nested object types, duplicate keys scoped by path, combobox nested in container replacement).
  • Full go test ./... passes; make lint-go clean.
  • End-to-end against a real Mendix 11.12.2 MPRv2 project (Combobox v2.8.1, Studio Pro–authored pages):
    • Before: identity rebuild → docker check → 1 error (CE0463).
    • After: identity rebuild → 0 errors; the stored Caption/Category values are preserved byte-for-byte in the unit.
    • After: REPLACE adding a where [Name != ''] XPath datasource constraint → 0 errors, constraint present in the unit.

…loses mendixlabs#112)

Replacing a pluggable widget emitted Type metadata (per-property
Caption/Category) generated from the embedded template and installed
.mpk, while untouched sibling widgets in the same page unit carried
metadata from whichever widget version authored them in Studio Pro.
MxBuild flags the mixed vintages as CE0463 on the rebuilt widget, even
for an identity rebuild that changes nothing.

ReplaceWidget now grafts the stored widget's per-property display
metadata (Caption, Category, Description) onto the replacement's
freshly generated Type block:

- matched by slash-joined PropertyKey path, not flat key, so distinct
  nested object types that reuse a key (e.g. markers/latitude vs
  dynamicMarkers/latitude) stay independent
- only onto replacements of the same WidgetId, including pluggable
  widgets nested inside container replacements
- leaf display fields only; $IDs and ValueTypes are never copied, so
  the new widget's Object/Type cross-references stay intact

Verified against a Mendix 11.12.2 MPRv2 project with Combobox v2.8.1:
identity rebuild and datasource-XPath replace both now pass docker
check with zero errors (previously CE0463 on both).

Co-Authored-By: Claude Fable 5 <[email protected]>
@ront85

ront85 commented Jul 28, 2026

Copy link
Copy Markdown
Author

Update: I re-tested both repros in this PR against unpatched upstream/main (i.e. without this PR's change) and they now pass with 0 errors — including with --no-update-widgets, which rules out the docker pre-flight remediation as the explanation.

Root cause: #8a14fc2d ("reconcile ComboBox definition drift generically — close #112 CE0463", merged 2026-07-20) made the freshly-emitted CustomWidgetType byte-identical (ID-masked) to mx update-widgets output for the ComboBox drift case. That fix landed after v0.16.0 and after the environment (v0.13.0) I originally reproduced this on, and it appears to close the same underlying drift this PR targets, just via a different mechanism (fixing the .mpk parser's property ordering/assignableTo handling rather than grafting stored metadata during REPLACE).

I no longer have a reproducible failure on current main for the scenarios in this PR's description. I'm going to hold off on merging this until I can find a case 8a14fc2 doesn't cover (if one exists) — will update or close depending on what I find. Apologies for the noise; posting this rather than leaving a possibly-redundant PR silently open.

@ako

ako commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Thanks for digging into this, and for the byte-level diff — that's what made it possible to confirm quickly.

Your follow-up hunch was right, but the credit goes to a different commit than you identified. The metadata drift is already fixed on main:

  • 4934fa2"fix(widgets): reconcile PropertyType metadata from .mpk (within-key CE0463 drift)". This is the relevant one. reconcilePropertyMetadata in modelsdk/widgets/augment.go walks the widget Type and overwrites Category, Caption, Description and the ValueType DefaultValue from the installed .mpk for every matched property key — exactly the filterInputDebounceIntervalCategory and onChangeEventCaption divergences in your analysis.
  • 8a14fc2 — the commit you cited. It fixed ComboBox-specific drift (interleaved <systemProperty> ordering and <returnType assignableTo=...>), which is adjacent to your case but not the display-metadata fix.

It's applied in the template loader (augmentFromMPK, called from GetTemplateBSON / GetTemplateFullBSON), so it covers every pluggable-widget emission path rather than ALTER PAGE REPLACE specifically.

The reason we went with the .mpk as the source of truth rather than grafting from the stored sibling: it's what mx update-widgets itself derives from, and it also covers the case grafting can't — a page with no pre-existing widget of that type to copy metadata from.

That said, your PR identifies one thing that is still a real gap, and it'd be very welcome as a standalone change: the slash-joined PropertyKey path matching (/markers/latitude). The current code flattens the .mpk into a map keyed on the bare key, last-write-wins, and matches on the bare PropertyKey — so a nested child shadows a same-named top-level property. Probing it directly:

key 'attribute' resolved to Caption="Column attribute" Category="Column"
   → nested child shadowed the top-level property

For a widget carrying, say, both a top-level attribute and a column-level attribute with different captions, that stamps the wrong metadata — CE0463 again, from the opposite direction. It's latent rather than a confirmed live failure (I reproduced the shadowing with a synthetic definition; I haven't found a shipped widget with a colliding pair whose metadata actually differs), but the fix is clearly correct either way.

Would you be up for re-scoping this PR to just the path-scoped key matching in mpkPropDefsByKey / reconcilePropertyMetadata, dropping the Mutator.ReplaceWidget grafting? Your nested-object and path-scoped-key tests would carry over more or less as-is.

Before you do — worth re-running your identity-rebuild repro against current main first. It should now come back with 0 errors without your patch, and if it doesn't, that's a more interesting finding than any of the above and we should chase that instead.


Generated by Claude Code

This was referenced Aug 1, 2026
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