Summary
The modelsdk codec engine serializes Mendix Integer properties as BSON int32, but Studio Pro (and the legacy MPR writer) store these as int64. The mismatch is tolerated by mx check today, but it produces type-inconsistent BSON: codec-written pages differ from Studio-Pro-written pages, and within a single page the CREATE path (codec, int32) disagrees with the page-mutator ALTER path (raw BSON, int64).
Surfaced while implementing pop-up dimensions for #661 — the codec write path was the one place that couldn't match Studio Pro's on-disk type.
Evidence
Fresh 11.11.0 project, two pages created via mxcli, dumped with mxcli bson dump --format bson:
| Field |
Studio Pro / legacy writer |
modelsdk codec |
CanvasWidth / CanvasHeight |
int64 |
int32 |
PopupWidth / PopupHeight |
int64 |
int32 |
Both pass mx check (0 errors), so this is correctness/consistency hygiene, not a build break.
Root cause
modelsdk/property/primitive.go — BSONValue() returns Get(), so a Primitive[int32] serializes as int32:
func (p *Primitive[T]) BSONValue() any { return p.Get() }
The gen types bind these fields as int32 (engalar's generator maps reflection Integer → int32), e.g. modelsdk/gen/pages/types.go:
o.canvasWidth = property.NewPrimitive[int32]("CanvasWidth", property.DecodeInt32)
o.popupWidth = property.NewPrimitive[int32]("PopupWidth", property.DecodeInt32)
The read side already knows the truth — property.DecodeInt32's own doc comment:
Mendix/Studio Pro stores small integers (connection indices, canvas positions, …) as BSON int64 — and occasionally as double — not int32, so accept all three numeric encodings.
So the decoder tolerates int64 on read; only the encoder emits the wrong width. This is the same gen storage concern noted previously for canvas/popup.
Scope / blast radius
This affects every Integer-typed gen field across all document types (canvas/popup dimensions, microflow connection indices, positions, etc.) — not just pages. That breadth is exactly why it was kept out of the #661 PR.
Options
- Encoder-level (broadest, cleanest): make
Primitive[int32].BSONValue() emit int64. Aligns all Integer fields with Studio Pro in one place; the decoder already round-trips int64. Needs validation that no consumer/round-trip test asserts int32 on the serialized side.
- Codegen type mapping: map reflection
Integer → an int64-emitting primitive in engalar's generator and regenerate modelsdk/gen/**. More faithful to the metamodel but touches the generator toolchain.
- Per-field override: force int64 for a known list of dimension fields only. Narrowest, but a manually-maintained list (maintenance risk) and leaves other Integer fields inconsistent.
Recommend option 1 or 2 (whole-class fix) over 3.
Acceptance
- modelsdk codec writes Integer fields as BSON int64 (canvas + popup at minimum).
- A codec-created page's
CanvasWidth/PopupWidth BSON type matches a Studio-Pro-authored page byte-for-type.
- CREATE (codec) and ALTER (mutator) produce the same type for popup dimensions.
mx check stays green across doc types; round-trip tests updated for int64.
References
Summary
The
modelsdkcodec engine serializes MendixIntegerproperties as BSON int32, but Studio Pro (and the legacy MPR writer) store these as int64. The mismatch is tolerated bymx checktoday, but it produces type-inconsistent BSON: codec-written pages differ from Studio-Pro-written pages, and within a single page the CREATE path (codec, int32) disagrees with the page-mutator ALTER path (raw BSON, int64).Surfaced while implementing pop-up dimensions for #661 — the codec write path was the one place that couldn't match Studio Pro's on-disk type.
Evidence
Fresh 11.11.0 project, two pages created via mxcli, dumped with
mxcli bson dump --format bson:CanvasWidth/CanvasHeightPopupWidth/PopupHeightBoth pass
mx check(0 errors), so this is correctness/consistency hygiene, not a build break.Root cause
modelsdk/property/primitive.go—BSONValue()returnsGet(), so aPrimitive[int32]serializes as int32:The gen types bind these fields as
int32(engalar's generator maps reflectionInteger→int32), e.g.modelsdk/gen/pages/types.go:The read side already knows the truth —
property.DecodeInt32's own doc comment:So the decoder tolerates int64 on read; only the encoder emits the wrong width. This is the same gen storage concern noted previously for canvas/popup.
Scope / blast radius
This affects every
Integer-typed gen field across all document types (canvas/popup dimensions, microflow connection indices, positions, etc.) — not just pages. That breadth is exactly why it was kept out of the #661 PR.Options
Primitive[int32].BSONValue()emitint64. Aligns all Integer fields with Studio Pro in one place; the decoder already round-trips int64. Needs validation that no consumer/round-trip test asserts int32 on the serialized side.Integer→ an int64-emitting primitive in engalar's generator and regeneratemodelsdk/gen/**. More faithful to the metamodel but touches the generator toolchain.Recommend option 1 or 2 (whole-class fix) over 3.
Acceptance
CanvasWidth/PopupWidthBSON type matches a Studio-Pro-authored page byte-for-type.mx checkstays green across doc types; round-trip tests updated for int64.References
modelsdk/property/primitive.go(BSONValue,DecodeInt32)modelsdk/gen/pages/types.go(canvas/popup bindings).claude/skills/fix-issue.md(symptom-table row for The Pop-up width / Pop-up height of a Page are not managed #661 notes the int32/int64 split)