diff --git a/CHANGELOG.md b/CHANGELOG.md index a98926de9..0708d89bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +- **Fixed navigation page actions overriding page titles with an empty template** — `CREATE OR REPLACE NAVIGATION` wrote `FormSettings.TitleOverride` as an empty `Microflows$TextTemplate`. That is an explicit override to an empty string, not “no override”, so every newly authored page menu item added a CW0263 warning and could render without the page title. All three navigation writers now emit the Studio Pro-authored shape, an explicit null, matching the already-correct page-button and show-page writers. + ## [0.20.0] - 2026-08-28 Headline: **Mendix 11.14 and Java 25, and the layer wrapped around a page — layouts, navigation profiles, mappings, translations and themes — becomes something a script can write.** A blank 11.14 project now builds, boots and runs as generated. `CREATE LAYOUT` closes the last document a page depends on that MDL could not write, so an app no longer has to start on an Atlas layout it cannot touch; a census of 327 real mapping documents turned the import/export mapping surface from a guess into a list and this release works through it; translations survive a rewrite and can be authored in bulk; and a project can carry, scaffold and switch between its own themes. Running underneath all of it is one theme: **a statement that reported success now has to have meant something.** Several of the fixes below are round-trips where `describe` printed a document mxcli could not express as one it could, and that output parsed — so the model was rebuilt, valid, and different. diff --git a/mdl/backend/modelsdk/navigation_icon_test.go b/mdl/backend/modelsdk/navigation_icon_test.go index a16988d55..4e796ae9c 100644 --- a/mdl/backend/modelsdk/navigation_icon_test.go +++ b/mdl/backend/modelsdk/navigation_icon_test.go @@ -64,6 +64,19 @@ func TestNavMenuItemBson_CarriesTheIconThrough(t *testing.T) { } } +// Studio Pro stores the absence of a page-title override as an explicit null. +// An empty TextTemplate is a real override to "" and raises CW0263. +func TestNavFormSettingsBson_NoTitleOverrideStaysNull(t *testing.T) { + settings := navFormSettingsBson("M.Dash") + title, present := navIconEntry(settings, "TitleOverride") + if !present { + t.Fatal("TitleOverride key missing; Studio Pro writes an explicit null") + } + if title != nil { + t.Fatalf("TitleOverride = %#v, want nil", title) + } +} + func TestMenuIconOf_NilIconYieldsNothing(t *testing.T) { typeName, image := menuIconOf(nil) if typeName != "" || image != "" { diff --git a/mdl/backend/modelsdk/navigation_write.go b/mdl/backend/modelsdk/navigation_write.go index 8d005e8ad..7afa3f885 100644 --- a/mdl/backend/modelsdk/navigation_write.go +++ b/mdl/backend/modelsdk/navigation_write.go @@ -216,7 +216,9 @@ func navFormSettingsBson(formName string) bson.D { {Key: "$Type", Value: "Forms$FormSettings"}, {Key: "Form", Value: formName}, {Key: "ParameterMappings", Value: bson.A{int32(1)}}, - {Key: "TitleOverride", Value: navEmptyTextTemplate()}, + // No override is an explicit null. An empty template overrides the page + // title with "" and produces CW0263 for every authored menu item (#812). + {Key: "TitleOverride", Value: nil}, } } @@ -295,16 +297,3 @@ func navMenuAction(mi types.NavMenuItemSpec) bson.D { {Key: "$Type", Value: "Forms$NoAction"}, } } - -func navEmptyTextTemplate() bson.D { - return bson.D{ - {Key: "$ID", Value: navID()}, - {Key: "$Type", Value: "Microflows$TextTemplate"}, - {Key: "Parameters", Value: bson.A{int32(2)}}, - {Key: "Text", Value: bson.D{ - {Key: "$ID", Value: navID()}, - {Key: "$Type", Value: "Texts$Text"}, - {Key: "Items", Value: bson.A{int32(2)}}, - }}, - } -} diff --git a/modelsdk/mpr/nav_patch.go b/modelsdk/mpr/nav_patch.go index 2921cb145..61b1f40f3 100644 --- a/modelsdk/mpr/nav_patch.go +++ b/modelsdk/mpr/nav_patch.go @@ -247,7 +247,9 @@ func navpBuildFormSettingsBson(formName string) bson.D { {Key: "$Type", Value: "Forms$FormSettings"}, {Key: "Form", Value: formName}, {Key: "ParameterMappings", Value: bson.A{int32(1)}}, - {Key: "TitleOverride", Value: navpEmptyTextTemplate()}, + // No override is an explicit null. An empty template overrides the page + // title with "" and produces CW0263 for every authored menu item (#812). + {Key: "TitleOverride", Value: nil}, } } @@ -320,21 +322,6 @@ func navpBuildMenuAction(mi types.NavMenuItemSpec) bson.D { } } -// navpEmptyTextTemplate returns an empty Microflows$TextTemplate embedded BSON document. -// Used for TitleOverride on Forms$FormSettings. -func navpEmptyTextTemplate() bson.D { - return bson.D{ - {Key: "$ID", Value: idToBsonBinary(generateUUID())}, - {Key: "$Type", Value: "Microflows$TextTemplate"}, - {Key: "Parameters", Value: bson.A{int32(2)}}, - {Key: "Text", Value: bson.D{ - {Key: "$ID", Value: idToBsonBinary(generateUUID())}, - {Key: "$Type", Value: "Texts$Text"}, - {Key: "Items", Value: bson.A{int32(2)}}, - }}, - } -} - // navpSetBsonField sets a top-level field in a bson.D, adding it if not found. func navpSetBsonField(doc bson.D, key string, value any) bson.D { for i, elem := range doc { diff --git a/modelsdk/mpr/nav_patch_title_test.go b/modelsdk/mpr/nav_patch_title_test.go new file mode 100644 index 000000000..324e71843 --- /dev/null +++ b/modelsdk/mpr/nav_patch_title_test.go @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: Apache-2.0 + +package mpr + +import ( + "testing" + + "go.mongodb.org/mongo-driver/v2/bson" +) + +func navpTestEntry(d bson.D, key string) (interface{}, bool) { + for _, entry := range d { + if entry.Key == key { + return entry.Value, true + } + } + return nil, false +} + +// Studio Pro stores the absence of a page-title override as an explicit null. +// An empty TextTemplate is a real override to "" and raises CW0263. +func TestNavpBuildFormSettingsBson_NoTitleOverrideStaysNull(t *testing.T) { + settings := navpBuildFormSettingsBson("M.Dash") + title, present := navpTestEntry(settings, "TitleOverride") + if !present { + t.Fatal("TitleOverride key missing; Studio Pro writes an explicit null") + } + if title != nil { + t.Fatalf("TitleOverride = %#v, want nil", title) + } +} diff --git a/sdk/mpr/writer_navigation.go b/sdk/mpr/writer_navigation.go index 1458c9d23..db8098464 100644 --- a/sdk/mpr/writer_navigation.go +++ b/sdk/mpr/writer_navigation.go @@ -241,7 +241,9 @@ func buildFormSettingsBson(formName string) bson.D { {Key: "$Type", Value: "Forms$FormSettings"}, {Key: "Form", Value: formName}, {Key: "ParameterMappings", Value: bson.A{int32(1)}}, - {Key: "TitleOverride", Value: emptyTextTemplate()}, + // No override is an explicit null. An empty template overrides the page + // title with "" and produces CW0263 for every authored menu item (#812). + {Key: "TitleOverride", Value: nil}, } } diff --git a/sdk/mpr/writer_navigation_icon_test.go b/sdk/mpr/writer_navigation_icon_test.go index ea4f14f2a..90efc9f59 100644 --- a/sdk/mpr/writer_navigation_icon_test.go +++ b/sdk/mpr/writer_navigation_icon_test.go @@ -125,6 +125,19 @@ func TestBuildMenuItemBson_NoIconStillWritesNull(t *testing.T) { } } +// Studio Pro stores the absence of a page-title override as an explicit null. +// An empty TextTemplate is a real override to "" and raises CW0263. +func TestBuildFormSettingsBson_NoTitleOverrideStaysNull(t *testing.T) { + settings := buildFormSettingsBson("M.Dash") + title, present := navIconEntry(settings, "TitleOverride") + if !present { + t.Fatal("TitleOverride key missing; Studio Pro writes an explicit null") + } + if title != nil { + t.Fatalf("TitleOverride = %#v, want nil", title) + } +} + // The read side has to recognise all three variants, because a project authored // in Studio Pro contains all three. The fixtures are the literal shapes dumped // from ako/mxcli-ledger's navigation document.