Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
deb8d19
feat(new): add --container-runtime flag (parity with mxcli init)
kaspergff Jul 9, 2026
b0330c9
fix(init): make devcontainer postCreateCommand robust on Podman-on-Wi…
kaspergff Jul 9, 2026
c8c4040
docs(lint): correct field value formats in write-lint-rules skill
claude Sep 17, 2026
db566ef
feat(lint): expose the user role's security flags
claude Sep 29, 2026
31825ee
docs: bug report — change member through an association (CE1613)
jasonliu2017 Oct 16, 2026
a3ce7ee
fix: refuse change members reached through an association
jasonliu2017 Oct 16, 2026
da56a3b
docs: make the example's working idiom exec-clean
jasonliu2017 Oct 16, 2026
e033904
docs(proposal): accept ALTER NAVIGATION / ALTER MENU item-level edits
claude Oct 20, 2026
7600012
fix(deps): raise klauspost/compress and go-mssqldb to their fixed ver…
claude Oct 20, 2026
5e494af
refactor(navigation): rename AlterNavigationStmt to CreateNavigationStmt
claude Oct 20, 2026
87f61ef
refactor(text): share the page rule for reading and writing a caption
claude Oct 20, 2026
e15a33c
fix(navigation): read menu captions with the page language rule
claude Oct 20, 2026
2f874cb
feat(navigation): menu item addresses and their resolver
claude Oct 20, 2026
916397a
feat(navigation): menu item splice for navigation profiles and menu d…
claude Oct 20, 2026
d912e0a
feat(mdl): parse ALTER NAVIGATION and ALTER MENU
claude Oct 20, 2026
334efba
fix(menu): write Icon: null on every menu document item
claude Oct 20, 2026
e7febff
fix(navigation): splice sets a menu item key the stored item lacks
claude Oct 20, 2026
38baa36
Merge mendixlabs/mxcli#744: fix(init): make devcontainer postCreateCo…
claude Oct 20, 2026
324a4e8
Merge mendixlabs/mxcli#1238: feat(lint): expose the user role's secur…
claude Oct 20, 2026
db4cdb1
Merge mendixlabs/mxcli#1552: docs: make the example's working idiom e…
claude Oct 20, 2026
78b768b
Merge mendixlabs/mxcli#1128: docs(lint): correct field value formats …
claude Oct 20, 2026
83b0b48
Merge mendixlabs/mxcli#740: feat(new): add --container-runtime flag (…
claude Oct 20, 2026
c406661
test(init): pin the tolerant chmod and the file package in the devcon…
claude Oct 20, 2026
37d49e1
test(lint): assert the user-role security flags reach Starlark
claude Oct 20, 2026
8c6de22
fix(check): a script-created module qualifies a change member; test t…
claude Oct 20, 2026
5e27f91
test(new): new takes init's --container-runtime flag
claude Oct 20, 2026
fd29386
docs(skills): regenerate the write-lint-rules section index
claude Oct 20, 2026
f202961
feat(navigation): execute ALTER NAVIGATION and ALTER MENU
claude Oct 20, 2026
af7ac9c
docs(skills): keep write-lint-rules within the 700-line body bound
claude Oct 20, 2026
da1fe49
Merge pull request #1493 from ako/claude/relaxed-goldberg-q2vra6
ako Oct 20, 2026
754de59
docs(navigation): document ALTER NAVIGATION and ALTER MENU
claude Oct 20, 2026
f8a1887
test(navigation): doctype script for ALTER NAVIGATION / ALTER MENU an…
claude Oct 20, 2026
fd57cb6
docs(skills): one row per user_role field; trim a blank line instead
claude Oct 20, 2026
87b4cbc
docs(navigation): fence the navigation synopses as text
claude Oct 20, 2026
a79eff4
Merge pull request #1494 from ako/integrate/wave2-upstream-prs
ako Oct 20, 2026
4c0e494
Merge pull request #1495 from ako/feature/alter-navigation-menu-items
ako Oct 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "cmd/mxcli", "date": "2026-10-20", "symptom": "On Podman-on-Windows, the devcontainer that `mxcli init --container-runtime podman` generates fails postCreateCommand after the mxcli download succeeded: `chmod +x ./mxcli` errors on the workspace bind mount, and every container start re-downloads the binary", "cause": "postCreateCommand ended in an unguarded `chmod +x ./mxcli`, which a mount that does not honour Unix modes rejects, failing the whole command. Its fast path, `file ./mxcli | grep -q Linux`, never matched because the generated image did not install `file`", "file": "`cmd/mxcli/tool_templates.go` (`generateDevcontainerJSON`, `generateDockerfile`); test `cmd/mxcli/tool_templates_test.go` (`TestDevcontainerPostCreate_ToleratesChmodAndHasFile`)", "insight": "A generated shell one-liner has two failure modes that no Linux CI run shows: a step that is fatal only on a foreign filesystem (a Windows bind mount ignores chmod), and a guard that silently never fires because its tool is missing from the image. Assert both on the generated text: every chmod on the mounted workspace is tolerant, and every command a guard runs is installed by the Dockerfile the same generator writes", "refs": ["mendixlabs/mxcli#742", "mendixlabs/mxcli#744"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/backend", "date": "2026-10-20", "symptom": "After `alter menu M.Menu { insert … }`, a describe -> `create or modify menu` of the unchanged menu reported \"Modified menu M.Menu\" (a second round trip then said Unchanged); a fresh `create menu` round-tripped Unchanged. `set ( Icon: … ) on <item>` on an item `create menu` wrote failed with \"menu item <id> has no Icon property\".", "cause": "`create menu` encodes items through the codec, which drops a null element property unless the type registers it; Menus$MenuItem registered only AlternativeText, so an iconless item had no Icon key, where every Studio Pro item (and every item the navigation writer and the ALTER splice build) carries Icon: null. Two writers of the same element disagreed on its property set.", "file": "`mdl/backend/modelsdk/menu_write.go` (init: codec.RegisterTypeDefaults(\"Menus$MenuItem\", NullFields AlternativeText + Icon)); `mdl/backend/menumutator/mutator.go` setItemField inserts a missing key in Studio Pro's alphabetical order instead of failing", "insight": "When two write paths build the same $Type (here the codec's create menu and the hand-built navMenuItemBson the splice reuses), compare their key sets on one document: the disagreement surfaces only when both write into it, as a describe -> exec that is not a fixed point. A splice that sets a property must not assume the key exists — data written by an older or different writer may lack it. Measured with real mx check 11.6.6: 0 errors before and after; the only symptom is the Modified/Unchanged report and the CLAUDE.md rule-4 property set.", "refs": ["mendixlabs/mxcli#1522"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/backend", "date": "2026-10-20", "symptom": "On a multi-language project `describe navigation` / `describe menu` printed a menu item's caption in whichever language was stored first, while `describe page` prints the same kind of text in the project's default language; and an item whose caption read empty and whose action was not a page (an icon-only microflow item) was missing from describe entirely, so a describe -> exec round trip deleted it.", "cause": "The navigation reader had its own text rule, `textOf` = the first non-empty Texts$Translation in document order, instead of the page rule (selectTranslationText: the default language even when empty, else en_US, else the first non-empty); and navMenuItemFromGen returned nil for an item with an empty caption, no page and no sub-items (a legacy-reader filter).", "file": "`mdl/backend/modelsdk/navigation_read.go` (`textOf` now calls `model.SelectTranslation` with `model.AuthoringLanguage()`; the filter in `navMenuItemFromGen` is gone); the executor resolves the language before reading (`describeDefaultLanguage` / `authoringLanguage` in describeNavigation, listNavigationMenu, describeMenu, execCreateNavigation, execCreateMenu)", "insight": "A translatable text has ONE read rule (model.SelectTranslation) and ONE write rule (bsonnav.SetTextsTextTranslation); a per-document copy drifts, and the drift only shows on a project whose default language is not the first one stored. Because the backend reads the language through the process-level model.AuthoringLanguage, every executor path that reads captions must call describeDefaultLanguage(ctx) first — a test that mocks GetNavigation and records the language seen at call time catches a missing call. A reader filter that drops an element is a delete on the next rewrite: compare the elements read with the elements stored.", "refs": ["ako/mxcli#1404", "mendixlabs/mxcli#1522"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-16", "symptom": "`change $a (A_B.Title = 'x')` — setting an attribute of the object reached through association `A_B` — passed `mxcli check` and `exec` reported \"Created microflow\", then failed the build with CE1613 \"The selected association 'A_B.Title' no longer exists.\" at the change activity. Hand-patched-BSON probes proved the deeper fact: NO encoding of a through-association member is accepted by ChangeActionItem — `Attribute: Hop.A_B/Title` and `Hop.A.A_B.Title` are invalid AttributeIdentifiers (unloadable .mpr), `Attribute: Hop.A_B.Title` is CE1613, `Attribute: Hop.B.Title` is CE0226 (mxbuild checks the identifier against the CHANGED entity), so the form is unrepresentable, not mis-spelled", "cause": "Three layers disagreed. check: `checkMembers` (validate_member_refs.go) blanket-skipped every dotted member, trusting exec's FINDINGS #51 refusal to catch bad one-dot names. Writer: that refusal lives inside the `mod != nil` branch of `resolveMemberChange` — it fires only when the pre-dot segment IS a module whose domain model lacks the association. For `A_B.Title` the pre-dot segment is an ASSOCIATION name, `GetModuleByName(\"A_B\")` misses, the guarded block is skipped, and `resolveMemberChangeFallback`'s one-dot heuristic (\"1 dot => Module.Assoc\") wrote `AssociationQualifiedName = \"A_B.Title\"` verbatim. The reader answers (nil, \"module not found\") for an absent module, so the real backend always took the skip path", "file": "mdl/executor/cmd_microflows_builder_actions.go (moduleExists + the one-dot refusal before the domain-model lookup), mdl/executor/validate_member_refs.go (backendRefusesModule; checkMembers reports the same shape), tests cmd_microflows_member_identifier_test.go + validate_member_refs_test.go; example mdl-examples/bug-tests/change-member-through-association.mdl", "insight": "**The trust boundary was drawn one branch too deep.** #51's refusal was real but conditional on the qualifier being a plausible module; check skipped dotted names citing a guard that silently did not apply to the Assoc.Attr shape. The decisive evidence was the probe matrix, not more code reading: six encodings hand-written into the stored BSON, every one rejected with a DIFFERENT error — that spread is what proves 'unrepresentable' rather than 'wrong spelling', and it kills the tempting fix of rewriting the member into some canonical form. To make the refusal fire on the real backend the existence question must go through the module ROSTER (ListModules), not GetModuleByName: the reader's not-found comes back as an error, so `err == nil && mod == nil` never fires in production — the first cut of this fix passed every unit test (mocks answered nil,nil) and still shipped CE1613 end to end. Existing writer mocks only answer GetModuleByName, so the two-tier lookup (name first, roster on error) keeps them honest without editing each one", "ce": ["CE1613", "CE0226"], "refs": ["docs/12-bug-reports/2026-10-16-change-member-through-association.md", "FINDINGS #51", "#830"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "go.mod", "date": "2026-10-20", "symptom": "A second Sonatype Lifecycle scan, run after the v2.9.2 upgrade (ako/mxcli#1372), still blocked the release: github.com/klauspost/compress v1.19.2 Critical:1, go.mongodb.org/mongo-driver v1.17.10 Critical:1, and github.com/microsoft/go-mssqldb v1.10.0. mongo-driver/v2 v2.9.2 had cleared and was gone from the report", "cause": "Three unrelated causes behind one report. (1) klauspost/compress v1.19.2 predates GHSA-fpmh-frq8-7rj8 (zstd DecodeAll crash on crafted input), fixed in v1.20.2 — the previous PR had only raised it to whatever mongo-driver/v2@v2.9.2 declared, which was not a security floor. (2) The v1 mongo-driver line has NO fix for CVE-2026-107325 and never will: v1.17.10 is the last v1 release and the module is marked deprecated. (3) go-mssqldb v1.10.0 was simply behind v1.11.2", "file": "`go.mod` — klauspost/compress v1.19.2 -> v1.20.3 (explicit indirect), go-mssqldb v1.10.0 -> v1.11.2; `sql/drivers_test.go` (new)", "insight": "Measure the vulnerable SYMBOL in the exact version, do not reason from the module's advisory range — v1 and v2 of mongo-driver are separate implementations and behave differently on the same payload. Measured on v1.17.10: `bsoncore.Array.Validate([]byte{0,0,0,0})` panics with `index out of range [-1]` (CVE-2026-107325, unfixed and unfixable on v1), while `bson.Raw.Validate` on the CVE-2026-107324 MaxInt32 payload does NOT panic — that CVE's code path is v2-only, so the v1 report overstates by one. Identical result on v1.17.9, proving the v1.17.10 bump bought nothing for these two and was only CVE-2026-88031 (GridFS). mxcli is not exposed: every `.Validate()` call site in the tree imports v2 (checked by pairing each call site against its file's imports), mxcli imports v1 `x/bsonx/bsoncore` in ZERO files, and `bson.Unmarshal` — the v1 API it actually uses, 112 times on .mpr bytes — does not panic on any malformed payload tried. So the v1 finding is real in the module and unreachable in this program, and no upgrade can ever clear it; only dropping the v1 dependency (107 non-test + 179 test files) can. Second trap, same shape as the first scan's: klauspost/compress still has NO go.sum entry and no `go version -m` line at v1.20.3 — never compiled — yet raising the declared requirement is what clears a graph-reading scanner, so the one-line bump is worth it even though no code changes. A blank-import driver registration (`sql/drivers.go`) had no test at all, so a bump could drop a driver name and only fail at connect time in front of a user: `TestExternalDriversRegistered` now covers it, and fails with `driver \"sqlserver\" is not registered` when the import is removed", "refs": ["CVE-2026-107325", "GHSA-fpmh-frq8-7rj8", "ako/mxcli#1372"]}
1 change: 1 addition & 0 deletions .claude/skills/lint-proposals.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Exactly one of these lowercase values:
|--------|---------|--------|
| `draft` | Early design, not finalised | active |
| `proposed` | Design complete, not yet started | active |
| `accepted` | Design approved by the maintainer; implementation pending or under way | active |
| `partial` | Some phases implemented, more to come | active |
| `done` | Fully implemented | `archive/` |
| `superseded` | Replaced by another proposal | `archive/` |
Expand Down
112 changes: 66 additions & 46 deletions .claude/skills/mendix/manage-navigation/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@ description: "Inspect and change navigation profiles in MDL — home pages, menu

| lines | section |
|---|---|
| 31-40 | When to Use This Skill |
| 41-59 | Navigation Concepts |
| 60-77 | Show Commands (Read-Only) |
| 78-359 | CREATE OR MODIFY NAVIGATION |
| 360-381 | Round-Trip Workflow |
| 382-403 | Catalog Queries |
| 404-453 | Common Patterns |
| 454-505 | Menu Documents (standalone, reusable) |
| 506-576 | Offline Profiles |
| 577-591 | Checklist |
| 592-595 | Offline synchronization (`CREATE NAVIGATION … SYNC (…)`) |
| 596-598 | Menu documents (CREATE OR MODIFY/DESCRIBE/DROP MENU) |
| 32-41 | When to Use This Skill |
| 42-60 | Navigation Concepts |
| 61-78 | Show Commands (Read-Only) |
| 79-360 | CREATE OR MODIFY NAVIGATION |
| 361-399 | Change One Item: ALTER NAVIGATION |
| 400-410 | Round-Trip Workflow |
| 411-432 | Catalog Queries |
| 433-471 | Common Patterns |
| 472-524 | Menu Documents (standalone, reusable) |
| 525-595 | Offline Profiles |
| 596-611 | Checklist |
| 612-615 | Offline synchronization (`CREATE NAVIGATION … SYNC (…)`) |
| 616-618 | Menu documents (CREATE OR MODIFY/DESCRIBE/DROP MENU) |

<!-- /skill-index -->
# Navigation Management Skill
Expand Down Expand Up @@ -77,7 +78,7 @@ list navigation homes;

## CREATE OR MODIFY NAVIGATION

All clauses are optional, and **an omitted clause keeps the stored value** — a statement with only a `{ … }` menu keeps the home page, role home pages, login page and not-found page (mendixlabs/mxcli#1522; before that fix they were cleared, and the missing home page failed the build with CE0527). A stated clause replaces what it names: `{ … }` the whole menu, `sync ( … )` the whole list, `home page X for R` role R's home page only. There is no clause that clears a home, login or not-found page. The output from `describe navigation` can be pasted back directly.
All clauses are optional, and **an omitted clause keeps the stored value** — a statement with only a `{ … }` menu keeps the home page, role home pages, login page and not-found page (mendixlabs/mxcli#1522; before that fix they were cleared, and the missing home page failed the build with CE0527). A stated clause replaces what it names: `{ … }` the whole menu, `sync ( … )` the whole list, `home page X for R` role R's home page only. To change **one** item, or to clear a role's home page, the login page or the not-found page, use [`alter navigation`](#change-one-item-alter-navigation) instead — it edits the stored profile and touches nothing else. The output from `describe navigation` can be pasted back directly.

### Basic: Set Home and Login Page

Expand Down Expand Up @@ -357,26 +358,54 @@ home pages and the `sync ( … )` block only — a `{ }` menu block (the bottom
`login page`, `not found page` and `on sync error` are refused, and describe lists
the bottom bar as comments.

## Round-Trip Workflow
## Change One Item: ALTER NAVIGATION

The DESCRIBE output is directly executable. Use this pattern to inspect, modify, and re-apply:
**Default to `alter navigation` for any edit to an existing menu.** It splices the
operations into the stored profile: items it does not name keep their stored
bytes, an item someone added in Studio Pro meanwhile survives, and a stored item
it does not name is not re-validated (a `create or modify` rewrite re-checks — and
can be refused over — every item it restates). Adding one item no longer means
restating the menu (mendixlabs/mxcli#1522).

```sql
mdl 1;
-- Step 1: Inspect current state
describe navigation Responsive;
list navigation menu Responsive; -- the Address column is what alter takes

alter navigation Responsive {
insert after 'Home' { menu item 'Customers' ( OnClick: show page MyModule.Customer_Overview, Icon: Atlas_Core.Atlas.user ) }
insert into 'Admin' { menu item 'Audit log' ( OnClick: show page MyModule.AuditLog ) }
set ( Caption: 'User accounts' ) on 'Admin' > 'Users';
move 'Log out' after 'Help';
drop 'Old import';
drop not found page;
drop home page for Administrator;
};
```

-- Step 2: Copy the output, modify as needed, paste back
create or modify navigation Responsive
home page MyModule.Home_Web
login page Administration.Login
{
menu item 'Home' ( OnClick: show page MyModule.Home_Web )
menu item 'New Feature' ( OnClick: show page MyModule.NewFeature )
};
- **Address** = the caption `describe` prints, as a path outer → inner:
`'Users'` (anywhere), `'Admin' > 'Users'` (direct child), `'Users'@2` (2nd of
several). An ambiguous or missing address is refused and the error lists the
address to write — copy it; never guess an `@n`.
- Fragments in `{ }` are written exactly as in `create navigation`.
- `insert into` / `move … into` needs a target with no action, and only
`OnClick: nothing` is allowed on an item with sub-items (CE0548).
- No bare `drop home page` (CE0527): `set home page M.P` changes it.
- A refusal anywhere writes nothing. `mxcli check -p … --references` runs the
same resolution against the stored menu.
- Native profiles are refused (their bottom bar is not written by mxcli).

Every operation, the address rules and the refusals: [reference/alter-navigation.md](reference/alter-navigation.md),
or `mxcli syntax navigation.alter`.

## Round-Trip Workflow

Restating the whole profile is for a menu you are designing from scratch. The
DESCRIBE output is directly executable:

-- Step 3: Verify
```sql
mdl 1;
describe navigation Responsive;
-- copy, edit, and run the output as `create or modify navigation …`
```

## Catalog Queries
Expand Down Expand Up @@ -431,24 +460,13 @@ create or modify navigation Responsive

### Adding a New Page to Navigation

After creating a new page, add it to the menu:
After creating a new page, add it to the menu — one statement, nothing else restated:

```sql
mdl 1;
-- First inspect current menu
describe navigation Responsive;

-- Then re-apply with the new item added (copy existing + add new)
create or modify navigation Responsive
home page MyModule.Home_Web
login page Administration.Login
{
menu item 'Home' ( OnClick: show page MyModule.Home_Web )
menu item 'Customers' ( OnClick: show page MyModule.Customer_Overview ) -- new
menu 'Admin' {
menu item 'Users' ( OnClick: show page Administration.Account_Overview )
}
};
alter navigation Responsive {
insert after 'Home' { menu item 'Customers' ( OnClick: show page MyModule.Customer_Overview ) }
};
```

## Menu Documents (standalone, reusable)
Expand Down Expand Up @@ -490,18 +508,19 @@ keep the edit to the items you mean to change, then `describe` it again after

**`or modify` replaces the whole item list.** An omitted item is a removed item,
exactly as with `create or modify navigation`. The document's identity and
export level are preserved, so menu widgets pointing at it keep working.
export level are preserved, so menu widgets pointing at it keep working. To
change one item, use `alter menu Module.Menu { insert … / move … / drop … / set ( … ) on … };`
— the item operations of `alter navigation`, no page clauses.

### Gotchas

- **A menu item cannot open a page that takes a required parameter.** There is
nowhere to supply the argument, and Mendix reports **CE1571** ("No argument has
been selected for parameter …") against `Menu item`. Point the item at a
parameterless page, or call a microflow that opens the page.
- **Only icon-collection icons round-trip.** A glyph icon (numeric code) or an
image icon cannot be written by MDL; `describe` flags those on their own
comment line rather than dropping them silently, so re-running the output
loses that icon visibly.
- **All three icon kinds round-trip** (`Icon: M.Coll.name`, `glyph n`,
`image M.Images.name`). Only an icon `describe` cannot rebuild (a glyph with no
code, an unknown icon type) is flagged on its own comment line.

## Offline Profiles

Expand Down Expand Up @@ -585,6 +604,7 @@ stored. MDL does not author per-entity sync modes — set those in Studio Pro.
- [ ] A menu item's action is `( OnClick: … )` with show page, call microflow, call nanoflow, open link, create object, sign out or nothing — not save / delete / close page
- [ ] `Icon:` is a qualified name (not a string); hyphenated segments are double-quoted
- [ ] The icon exists — check with `describe icon collection Module.Name`, do not guess
- [ ] Editing an existing menu? Use `alter navigation` / `alter menu`, addressing items by the Address `list navigation menu` prints
- [ ] Use `describe navigation` to verify changes after applying
- [ ] For a **menu document**, confirm you want `create menu` and not a profile menu — `list navigation menu` vs `describe menu` tells them apart
- [ ] No menu item targets a page with required parameters (CE1571)
Expand Down
Loading
Loading