diff --git a/.claude/skills/fix-issue.md b/.claude/skills/fix-issue.md index 84668066a..ff77d8abf 100644 --- a/.claude/skills/fix-issue.md +++ b/.claude/skills/fix-issue.md @@ -759,3 +759,4 @@ extracting `OffsetExpression`/`LimitExpression`. | After `mxcli test … --local`, an app another `mxcli run --local` is serving goes blank while still answering HTTP 200 (~1.7 KB, the Mendix SPA shell); the runtime log shows `Connector: 404 - file not found for file: dist%2Findex.js` and `deployment/web/dist` is gone | `cmd/mxcli/testrunner/localapp_options.go`, `cmd/mxcli/testrunner/runner_local.go` (`localTestDeployDir`), `cmd/mxcli/testrunner/runner.go` (`checkScratchDeploymentExists`) | A local test run already used its own ports and its own `_test` database — the code comment says why, verbatim — but shared the **deployment directory**, which is the one the *browser* reads. A headless test boot does not bundle the web client, so its build left the running app serving the shell over a 404: tests pass, run keeps running, app is blank, nothing reported at either end. **Detection was not the fix**: the two processes use different ports by design, so no port check can see it, and a lock file would only turn a silent blanking into a refusal. The test boot now builds into `/.mxcli/deployment-test/` — gitignored, already where the test runtime log lives — which makes the collision impossible. Note booting a runtime against the shared directory damages it even **without** a rebuild (the packaging step removes the bundle — FINDINGS §35, `ReportLostWebClientBundle`), so "reuse the dev loop's tree read-only" is not an alternative. Consequence to wire: `--skip-build` used to mean "reuse deployment/" and now has nothing until tests have run once, so it is refused with the reason rather than failing inside the runtime boot against a path the user never chose. Reported as mxcli-formula1 FINDINGS §62 | | A widget keyword the grammar accepts is absent from `mxcli syntax page widgets`, so it is concluded not to exist and worked around at length (reported for `tabcontainer`, which cost two days and five hand-rolled pages) | `cmd/mxcli/syntax/features_page.go`, `cmd/mxcli/syntax/widget_keywords_drift_test.go` | The lesson the reporter drew — "absence from the documentation is not absence from the grammar" — is true and is a bad thing for the docs to require. `TestEveryWidgetKeywordIsInAPageSyntaxTopic` makes it false instead: it reads the `widgetTypeV3` rule out of the **committed** `.g4` (only the *generated parser* is uncommitted, and the grammar is the authority the reporter was told to consult) and fails when a keyword appears in no `page.*` topic. It found **18**, not one. Exemptions go in `documentedElsewhere` **with the topic that owns them** — layout constructs (`scrollcontainer`, `region`, `navigationtree`, `menubar`, `placeholder`) and pluggable-widget object-list keywords (`group`, `series`, `marker`, …) are not page widgets; an entry with no home is the same defect. The guard carries its own vacuity control: a keyword that does not exist must not match, and one that does must. **Do not document a keyword without running it** — probing all 18 on 11.13 found four the parser accepts and the *default engine refuses* (`statictext`, `staticimage`, `dynamicimage`, `dropdown` → "widget *pages.X not yet supported by the modelsdk engine"), two refused on both engines (`referenceselector`, `legacydatagrid`), and one whose bare form emits **CE0463** (`image`). Reported as mxcli-formula1 FINDINGS §69 | | Every open PR goes red at once on `build-and-test` with a failure in a package none of them touched — `--- FAIL: TestSessionLog_PersistAndPrune`, "after reload+prune: 0 records, want 1" — and the same test fails on a clean checkout of `main` | A **time bomb in the test**, not a regression: the fixture pinned `base := time.Date(2026, 8, 1, ...)` against a 30-day retention window, and `NewSessionLogFile` prunes inside `load()` — *before* the test can assign `log2.now`, so the reload prune runs on the real `time.Now()` whatever clock is injected afterwards. It passed for 30 days and then failed permanently, on every branch simultaneously | `cmd/mxcli/tunnelhub/sessions_test.go` (`TestSessionLog_PersistAndPrune`), `cmd/mxcli/tunnelhub/sessions.go` (`load` → `pruneLocked` → `clock`) | **First establish it is not yours**: run the failing test on a clean `origin/main`. Several PRs failing on one unrelated test is the signature. Then make the fixture relative — `base := time.Now().UTC()` — so the record ages, not the calendar, decide the outcome; the other tests in the file keep their fixed base legitimately, because they use `NewSessionLog` and inject the clock before recording. **A date fixture is only safe where no code path reads the real clock**; the moment a constructor prunes, expires or compares against `time.Now()` before the seam is in place, an absolute date has a fuse on it. Control the repair: stub `pruneLocked` to a no-op and confirm the test still fails (2 records, want 1), or the fix is just a test that stopped testing | +| Every page menu item in an **mxcli-authored navigation profile** loses the page's own title, and `mx check` reports one **CW0263 "Empty template"** warning per item while errors stay at 0 (16 of them on a real 11.12.3 app) | `Forms$FormSettings.TitleOverride` written as an **empty** `Microflows$TextTemplate` instead of `null` — the #812 defect above, in the three writers #812 did not touch. An empty template is an override to `""`, not the absence of one. Unlike the ShowPage/button paths there is nothing to preserve: `types.NavMenuItemSpec` has no title field, so MDL cannot author a navigation title override and the value is unconditionally null | `sdk/mpr/writer_navigation.go` (`buildFormSettingsBson`), `mdl/backend/modelsdk/navigation_write.go` (`navFormSettingsBson`), `modelsdk/mpr/nav_patch.go` (`navpBuildFormSettingsBson`) | Emit `{Key: "TitleOverride", Value: nil}` in all three. **Grep the builder's callers before concluding a navigation fix is menu-only** — each of the three is also the profile's **login page** builder (`writer_navigation.go:116`, `navigation_write.go:127`, `nav_patch.go:122`, plus `navigation_profile_add.go:108`), so one edit per engine covers both `Forms$FormAction` and `LoginPageSettings`, and a fix aimed only at menu items would have missed half the emitters. Verify against a **Studio Pro document already in the project**, not against the warning count: a blank app's own navigation unit stores `TitleOverride = null` on both the login settings and the home menu item. Read it with `f=$(grep -ral NavigationDocument app/mprcontents \| head -1)` — `grep -a` is required, a `.mxunit` is raw BSON and plain `grep -rl` skips it as binary — then `strings -a "$f" \| grep -c TextTemplate`: **4 before (3 page items + login page), 0 after**, with `grep -c TitleOverride` staying at 4 so the key is still written. Measured on 11.12.0 on both engines. These three paths are raw `bson.D` → `bson.Marshal` → `UpdateRawUnit`, so #812's second trap (a `codec.RegisterTypeDefaults` `NullFields` entry clobbered by another registration for the same `$Type`) cannot apply — which also means a shape assertion on the builder is the only unit-level guard there is. Repro `mdl-examples/bug-tests/989-navigation-title-override.mdl`. PR #989 | diff --git a/mdl-examples/bug-tests/989-navigation-title-override.mdl b/mdl-examples/bug-tests/989-navigation-title-override.mdl new file mode 100644 index 000000000..f21ec53dd --- /dev/null +++ b/mdl-examples/bug-tests/989-navigation-title-override.mdl @@ -0,0 +1,153 @@ +-- PR #989: navigation page actions got an empty TitleOverride instead of null, +-- blanking the page title on every mxcli-authored menu item. +-- +-- The same defect as #812, in the writers #812 did not touch. All three +-- navigation writers built their Forms$FormSettings with +-- +-- TitleOverride: { $Type: "Microflows$TextTemplate", Text: {...}, Parameters: [] } +-- +-- where Studio Pro writes `null`. An empty template is not the absence of an +-- override -- it IS an override, to the empty string -- so every page menu item +-- mxcli authored raised CW0263 "Empty template" and could render without the +-- page's own title. +-- +-- Unlike the ShowPage / button paths there is nothing to preserve here: +-- types.NavMenuItemSpec has no title field, so MDL cannot author a navigation +-- title override at all and the value is unconditionally null. +-- +-- The builder is shared with the profile's LOGIN PAGE (buildFormSettingsBson is +-- called for LoginPageSettings as well as for each menu item's Forms$FormAction), +-- so this script exercises both, plus a nested sub-menu -- sub-items are built by +-- the same recursive call. +-- +-- THE REFERENCE IS IN THE PROJECT. A blank Mendix app's own navigation document +-- already contains both shapes, so the fix can be checked against a Studio +-- Pro-authored document rather than against a warning count. On a blank 11.12 +-- app, before this script runs: +-- +-- LoginPageSettings +-- Form = "" +-- ParameterMappings [marker 2, 0 items] +-- TitleOverride = null <-- the shape mxcli must write +-- Menu.Items[0].Action.FormSettings +-- Form = MyFirstModule.Home_Web +-- ParameterMappings [marker 2, 0 items] +-- TitleOverride = null +-- +-- Verify without Studio Pro. `mxcli bson dump` has no navigation type, so read +-- the unit directly (MPR v2). Note `grep -a`: a .mxunit is raw BSON and plain +-- `grep -rl` skips it as binary. +-- +-- mxcli exec 989-navigation-title-override.mdl -p app.mpr +-- f=$(grep -ral NavigationDocument app/mprcontents | head -1) +-- strings -a "$f" | grep -c TextTemplate +-- +-- Measured on a real 11.12.0 v2 app, both engines (MXCLI_ENGINE=legacy selects +-- sdk/mpr/writer_navigation.go, the default selects +-- mdl/backend/modelsdk/navigation_write.go): +-- +-- before the fix: 4 -- one per Forms$FormSettings: 3 page menu items + the +-- login page. This is the control: revert TitleOverride +-- to navEmptyTextTemplate() and the 4 come back. +-- after the fix: 0 -- while `strings -a "$f" | grep -c TitleOverride` stays +-- at 4, so the key is still written, now as null. +-- +-- And at the layer the symptom was reported in: +-- +-- mxcli docker check -p app.mpr +-- -> 0 errors before and after; CW0263 warnings drop by one per +-- Forms$FormSettings written. Measured on a real 11.12.3 app in the +-- PR: 199 -> 183. +-- +-- Two things this script also makes visible, both OUT OF SCOPE for #989 and +-- neither introduced by it: +-- * `describe navigation Responsive` prints no `login page` line even though +-- the document carries Form = Issue989.NavLogin, so a describe -> exec +-- round-trip drops the login page. The reader type-asserts +-- *genNav.NavigationProfileLoginFormSettings (property `LoginPage`), and +-- both Studio Pro and mxcli store Forms$FormSettings (property `Form`). +-- * every list these writers emit carries typed-array marker 1 -- HomeItems, +-- ParameterMappings, PagesForSpecializations, Menu.Items, Caption.Items -- +-- where the same blank app's own document has 2 or 3 for those fields. +-- Marker 1 is NOT invalid: Mendix writes it for by-name reference and string +-- lists (CustomWidgets$WidgetValueType.AllowedTypes, 212k occurrences in a +-- Marketplace package mxcli has never touched), so this is a per-field +-- mismatch rather than corruption. Fixed separately. + + +create module Issue989; +create module role Issue989.User; + +-- Distinct titles, so a blanked one is visible rather than merely absent. +create page Issue989.NavHome ( + title: 'Home Page Title', + Layout: Atlas_Core.Atlas_Default +) +{ + container c1 { } +} +/ + +create page Issue989.NavReports ( + title: 'Reports Page Title', + Layout: Atlas_Core.Atlas_Default +) +{ + container c1 { } +} +/ + +create page Issue989.NavSettings ( + title: 'Settings Page Title', + Layout: Atlas_Core.Atlas_Default +) +{ + container c1 { } +} +/ + +create page Issue989.NavLogin ( + title: 'Sign In Page Title', + Layout: Atlas_Core.Atlas_Default +) +{ + container c1 { } +} +/ + +-- Three page menu items (one of them nested, to cover the recursive sub-item +-- build) and a login page: four Forms$FormSettings, four TitleOverrides that +-- must all be null. +create or replace navigation Responsive + home page Issue989.NavHome + login page Issue989.NavLogin + menu ( + menu item 'Home' page Issue989.NavHome; + menu 'Admin' ( + menu item 'Reports' page Issue989.NavReports; + menu item 'Settings' page Issue989.NavSettings; + ); + ); + +-- A microflow action and a sub-menu container write Forms$MicroflowAction / +-- Forms$NoAction, which carry no FormSettings -- included so the TextTemplate +-- count above is attributable to the page items alone. +create microflow Issue989.DoNothing () +begin + return; +end; +/ + +create or replace navigation Responsive + home page Issue989.NavHome + login page Issue989.NavLogin + menu ( + menu item 'Home' page Issue989.NavHome; + menu item 'Run' microflow Issue989.DoNothing; + menu 'Admin' ( + menu item 'Reports' page Issue989.NavReports; + menu item 'Settings' page Issue989.NavSettings; + ); + ); + +describe navigation Responsive;