Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ three commits past it), and a bug report can name a release instead of a sha nob
Sections dated before 2026-09-19 predate the cycle and stay as they are.

## Unreleased
- learn(bug-logs): **7 new drafts, 3 addenda and one feature-ask note from a module-rename field run, each re-checked on v0.24.0 where a probe was cheap.** New: `rename module` leaves XPath naming the old module; `rename page` renames a same-named folder instead (reproduced; root cause is a name match with no type check); a case-only `move` leaves an empty folder twin; a quoted DataGrid 2 association path passes `check` and fails CE1613; `check --references` ignores an in-script `rename module`; `diff-local` misses new (untracked) units; and `diff-local` prints a UUID for the module. Addenda: BUG-62 (no `rename snippet`), BUG-63 (`microflow_type` case, SQL-vs-Starlark entity types, `activity_count` scope; title widened), BUG-102 (legacy grid XPath has no offline route). Paste-ready upstream drafts for four, not filed. — field report from a colleague's naming-conventions project (mxcli v0.23.0, Mendix 11.12.4)
- learn(skills/learned-mdl-preflight.md): **STOP row 26: DataGrid 2 column and `sort by` paths over an association.** Write the column path unquoted, and do not sort over an association on ≤ v0.24.0. Both wrong forms pass `check --references` and fail CE1613 at build. — field report from a colleague's naming-conventions project (mxcli v0.23.0, Mendix 11.12.4)
- learn(skills/lint-that-actually-runs.md): **three more lint vocabulary facts, verified on v0.24.0, with a before/after rule.** `microflow_type` is `MICROFLOW`/`NANOFLOW`/`RULE`, and `microflows()` yields all three. SQL `PERSISTENT` is Starlark `Persistent`. `activity_count` counts top-level objects (splits, loops, annotations), not actions, and `activities_for()` does not see loop bodies. — field report from a colleague's naming-conventions project (mxcli v0.23.0, Mendix 11.12.4)
- learn(skills/learned-microflow-patterns.md): **expression functions take positional arguments only — `toString(from: $X)` is not a syntax error, it's a silent misparse.** MDL's expression grammar has no named-argument form for function calls; `label: value` parses as `label` (a bare unresolved identifier) COLON-divided by `value` — COLON being the OQL division operator. Verified on mxcli v0.24.0 / Mendix 11.12.1, scratch copy of a PoC model: `toString(from: $Temperature)` and `formatDateTime($D, pattern: 'yyyy-MM-dd')` both pass `mxcli check --references` silently (exit 0) because the collapsed argument count still matches the function's arity; `substring(from: $S, index: 0, length: 3)` fails, but with a misleading `E006` ("expects 2 to 3 argument(s), got 1") that never names labelled arguments as the cause. The likely source: labelled-colon syntax is genuine MDL elsewhere (page `Attribute:`/`Action: MICROFLOW Name(Param: val)`, `@anchor(from: bottom, to: top)`) — just never inside an expression — workshop feedback, Sep 2026
- learn(skills/learned-file-upload-widget.md): **a file upload mxcli can author, with proof that it uploads.** The Mendix File Uploader 2.5.0 bound to a `System.FileDocument` specialisation: the MDL shape (entities, grants, create/delete microflows, advanced formats), which upload widgets mxcli cannot author (classic FileManager, PDS uploader), the two traps with workarounds (a simple-mode `allowedfileformat` passes exec and fails `mx check` with CE0463; an uploader DESCRIBE will not re-exec, `exposes 2 datasources`), and the six-step upload instrument. Field run on stock v0.24.0: the section-4 MDL taken verbatim from the skill gave `mx check` 0 errors, 2/2 files stored, 2/2 downloads sha256-equal, `.csv` rejected with 0 rows; both traps reproduce unchanged on v0.24.0 — a Mendix app-rebuild project
- new(skills/mendix-best-practices-index.md): **one row per Mendix best-practice area: the Mendix docs page, the bundled `assess-quality` section, the toolkit skill that applies it before the write, and the `mxcli lint` rule that catches it after exec.** An index, not a copy — the practice text stays on the Mendix pages (17 URLs verified HTTP 200 on 2026-09-25) and in the mxcli-bundled skill; Mendix's own Best Practice Recommender rules (MXP001–016) anchor the performance rows, and four rows say out loud that no lint rule exists and the preflight checklist is the only check. Routed `all` agents, stages 3/5/6, group reference — Maurits Visser
Expand Down
200 changes: 199 additions & 1 deletion bug-logs/mxcli-bugs.md

Large diffs are not rendered by default.

41 changes: 41 additions & 0 deletions bug-logs/pending-github-issues/dg2-association-path-bindings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
**Repo:** `mendixlabs/mxcli`
**Source:** `bug-logs/mxcli-bugs.md`, `## BUG-DRAFT-dg2-association-path-bindings` — found 2026-09-25 (field report on v0.23.0; `check` half re-verified on v0.24.0)
**Status:** NOT YET FILED
**Suggested labels:** bug, pages, datagrid, check
**Duplicate check:** searched 2026-09-25 (`datagrid column quoted association path CE1613`, `sort by association attribute datagrid`). Related, closed: #830 (unquoted association column), #1152 (sort over association round-trip); fe5a7408e (unreleased) lets a sort column name its association. No issue found for the **quoted** column path. Before filing, check whether fe5a7408e also covers the DataGrid 2 `sort by` form; if so, file the column half only.

---

**Title:** DataGrid 2: a quoted association path in a column `Attribute:` is stored as one attribute named `Assoc/Attr`; `check` passes, the build fails with CE1613

**Body:**

## Summary

`Attribute: "Assoc_A_B/Name"` (quoted) on a DataGrid 2 column is stored as a single attribute whose name contains a slash. The unquoted form is stored as a path. `check --references` passes both, and the quoted one fails at build with CE1613. The generated project `CLAUDE.md` tells agents to "always quote identifiers … always safe", so agents that follow it hit this on every association column. The same applies to `sort by Assoc/Attr` on the grid's database source: it passes `check` and is written as a plain attribute of the grid entity (CE1613).

**Version:** field run on mxcli v0.23.0 / Mendix 11.12.4 (build results); `check` behaviour re-verified on v0.24.0.

## Repro

```
create page ModuleA.EntityA_Grid (Title: 'Grid', Layout: Atlas_Core.Atlas_Default) {
datagrid dg1 (DataSource: database ModuleA.EntityA sort by "Assoc_A_B/Name" asc) {
column colQ (Attribute: "Assoc_A_B/Name", Caption: 'Quoted')
column colU (Attribute: Assoc_A_B/Name, Caption: 'Unquoted')
}
};
```

## Measured

- v0.24.0 `check --references`: `Check passed!`, with an MDL-WIDGET16 info line deriving the column names as `colQ → "Assoc_A_B/Name", colU → Assoc_A_B/Name`. The quotes survive into the stored name.
- v0.23.0 build (field): the quoted column gives CE1613, and the unquoted column builds. `sort by Assoc/Attr`, unquoted or quoted, gives CE1613. The fully qualified `Module.Assoc/Module.Entity/Attr` sort form does not parse.

## Expected

A quoted identifier that contains `/` is either split into a path or rejected by `check`. A sort over an association is either authored as a path or rejected by `check`.

## Workaround

Write association paths unquoted. Sort by a local attribute, or use a microflow datasource.
47 changes: 47 additions & 0 deletions bug-logs/pending-github-issues/diff-local-misses-new-units.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
**Repo:** `mendixlabs/mxcli`
**Source:** `bug-logs/mxcli-bugs.md`, `## BUG-DRAFT-diff-local-misses-new-units` — found 2026-09-25 (field report on v0.23.0, reproduced on v0.24.0)
**Status:** NOT YET FILED
**Suggested labels:** bug, diff-local
**Duplicate check:** searched 2026-09-25 (`diff-local does not show new untracked units`). No match. #1038 and #424 are different diff-local bugs; 0dce31214 (unreleased) fixes other diff-local defects but not this one. Consider filing the domain-model UUID rendering (`BUG-DRAFT-diff-local-module-uuid`) as a second issue or a comment here.

---

**Title:** `diff-local` does not report new documents: untracked `mprcontents` units are invisible

**Body:**

## Summary

On an MPR v2 project under git, a document created since the ref is a new, **untracked** `mprcontents/**.mxunit` file. `diff-local` builds its change list from `git diff --name-status <ref> -- <contentsDir>` (`mdl/executor/cmd_diff_local.go`), which never lists untracked files, so new microflows, pages and entities are missing from the output and the summary.

**Version:** mxcli v0.24.0 (also v0.23.0), Mendix 11.12.1, MPR v2.

## Repro

In a clean git checkout of an MPR v2 app:

```
create microflow ModuleA.SUB_New () begin log info node 'P' 'x'; end;
/
create persistent entity ModuleA.Thing (Code: String(20));
```

```
mxcli exec repro.mdl -p App.mpr
git status --short # new mprcontents/... directories are untracked
mxcli diff-local -p App.mpr
```

## Measured (v0.24.0)

- Only new units (new module + entity + microflow + page): `No local changes found in mxunit files.`
- New entity in an existing module + new microflow: `Summary: 0 new, 1 modified, 0 deleted`. The domain model is listed; the microflow is not.
- After `git add -N <new dirs>`: `1 new`, but the new microflow renders as `create or modify microflow .SUB_New ()`, with an empty module qualifier.

## Expected

New units are listed as `new`, rendered with their module, and counted in the summary.

## Workaround

Build the list from `git diff --name-only <ref> -- mprcontents/` plus `git ls-files --others --exclude-standard mprcontents/` and resolve unit ids yourself.
61 changes: 61 additions & 0 deletions bug-logs/pending-github-issues/rename-module-leaves-xpath.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
**Repo:** `mendixlabs/mxcli`
**Source:** `bug-logs/mxcli-bugs.md`, `## BUG-DRAFT-rename-module-leaves-xpath` — found 2026-09-25 (field report on v0.23.0, retested on v0.24.0)
**Status:** NOT YET FILED
**Suggested labels:** bug, rename, xpath
**Duplicate check:** searched 2026-09-25 (`rename module does not rewrite XPath constraints`). No match. Related but different: #910 / 35efa0207 (XPath rewrite on **attribute** rename, unreleased), #1049 (dangling XPath member passes `check --references`), #473 and #426 (rename module implementation).

---

**Title:** `rename module` reports its references updated but leaves XPath constraints naming the old module (CE1613 at build)

**Body:**

## Summary

`mxcli rename module A B` rewrites the structural references and reports a count, but a qualified name inside a retrieve's XPath constraint still names the old module. In a field run on a larger app this gave "407 references updated" and then 34 CE1613 from 10 microflows plus one legacy data grid's XPath.

**Version:** mxcli v0.24.0 (also v0.23.0), Mendix 11.12.1 / 11.12.4, MPR v2.

## Repro

```
create persistent entity RenA.Owner (Label: String(50));
create association RenA.Item_Owner from RenA.Item to RenA.Owner;
create microflow RenA.SUB_Assoc ()
returns Boolean as $Ok
begin
declare $Ok Boolean = false;
retrieve $Items from RenA.Item
where RenA.Item_Owner/RenA.Owner/Label = 'x';
if $Items != empty then
set $Ok = true;
end if;
return $Ok;
end;
/
```

```
mxcli exec repro.mdl -p App.mpr
mxcli rename -p App.mpr module RenA RenB
mxcli -p App.mpr -c "describe microflow RenB.SUB_Assoc"
```

## Measured (v0.24.0)

```
Renamed module: RenA → RenB
Updated 6 reference(s) in 4 document(s)
...
where RenA.Item_Owner/RenA.Owner/Label = 'x';
```

A simpler `where RenA.Item/Name = 'x'` in another microflow is left unchanged as well. `SEARCH 'RenA.'` returns "No matches found.", so the stale names cannot be found in-tool either. `grep -rl RenA mprcontents/` finds the units.

## Expected

Every module-qualified name in XPath constraints (retrieves, widget datasources, access rules) and in expressions follows the rename, as #910 now does for attribute renames.

## Workaround

Describe each affected microflow, correct the module name by hand, and re-create it, then run `mx check`.
55 changes: 55 additions & 0 deletions bug-logs/pending-github-issues/rename-page-renames-folder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
**Repo:** `mendixlabs/mxcli`
**Source:** `bug-logs/mxcli-bugs.md`, `## BUG-DRAFT-rename-page-renames-folder` — found 2026-09-25 (field report on v0.23.0, reproduced on v0.24.0)
**Status:** NOT YET FILED
**Suggested labels:** bug, rename
**Duplicate check:** searched 2026-09-25 (`rename page renames folder with the same name`). No match (#426, #154 are unrelated).

---

**Title:** `rename page` renames a folder with the same name instead of the page, and reports success

**Body:**

## Summary

`RenameDocumentByName` (`mdl/backend/modelsdk/infrastructure_write.go`) walks every unit in the module's container set and rewrites the first unit whose top-level `Name` equals the old name. It never checks `$Type`. Folders are units too, so when a folder with the page's name comes first in unit order, the folder is renamed, the command prints "Renamed page", and callers are repointed to a page name that does not exist (CE1613 at build).

**Version:** mxcli v0.24.0 (also v0.23.0), Mendix 11.12.1, MPR v2. Same code on `main` as of 2026-09-24.

## Repro

The folder must be older than the page:

```
create page RenA.Other (Title: 'Other', Layout: Atlas_Core.Atlas_Default) {
container c1 { dynamictext t1 (Content: 'y') }
};
move page RenA.Other to folder 'Pages/Same';
create page RenA.Same (Title: 'Same', Layout: Atlas_Core.Atlas_Default) {
container c1 { dynamictext t1 (Content: 'x') }
};
```

```
mxcli exec repro.mdl -p App.mpr
mxcli rename -p App.mpr page RenA.Same Same_Overview
mxcli -p App.mpr -c "show pages in RenA"
```

## Measured (v0.24.0)

```
Renamed page: RenA.Same → RenA.Same_Overview
| RenA.Other | RenA | Other | false | Pages/Same_Overview | ...
| RenA.Same | RenA | Same | false | | ...
```

`git status` shows one changed unit, and its `$Type` is `Projects$Folder`. If the folder is created *after* the page, the page is renamed correctly, which is why this looks intermittent.

## Expected

`rename page` only matches `Forms$Page` units (and the other `rename <type>` variants only their own type).

## Workaround

Move the folder's documents out, drop the folder, rename the page, move them back. Always read the model back after a rename.
1 change: 1 addition & 0 deletions skills/learned-mdl-preflight.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Once you've picked a mode per operation, run the STOP table below against every
| 23 | Name any document or entity that **might already exist** — `create or modify` on an entity, or any `create or replace` on a page, layout or microflow | **STOP → run `mxcli diff -p <app>.mpr <script>.mdl --format struct` FIRST.** It is read-only, it takes a second, and it prints exactly what the script would add and what it would REMOVE. Read the `-` lines before you exec, every time. Rules 21 and 22 tell you to write `create or modify` and to give each document one owner; this is the check that the name you chose is actually free. | Confirmed 2026-09-01 (a dashboard-publishing migration, mxcli v0.20.0). A new script declared `create or modify non-persistent entity DashboardPublishing."ViewerContext"` with one attribute, believing the name unused. The entity already existed, owned by an earlier page script, carrying four attributes — `VersionLabel`, `HasContent`, `Html`, `DashboardTitle` — which are the entire datasource of the app's viewing page. `diff --format struct` reported `+ Attribute SharedCount` and **four `- Attribute` lines**, i.e. `create or modify` on an entity does not merge into the attribute set, it **replaces** it. Renaming the new entity took ten seconds; the alternative was a page that renders nothing, on a model that would have passed `mx check` and mxbuild clean, since an entity losing four attributes is a perfectly valid model. **This is the cheapest guard in the file — the whole rule is `diff` before `exec`.** |
| 24 | Put a `DesignProperties: [...]` entry on a widget (Form orientation, Label width, Spacing, …) in an app scaffolded by `mxcli new` (any `--theme`) | **STOP → grep the theme first: `grep -l '<property name>' themesource/*/web/design-properties.json`. If it is not there, use the Atlas class instead (`Class: 'form-vertical'`, `spacing-outer-bottom-large`).** `mxcli new`'s theme layer ships a `design-properties.json` that does not carry Atlas's FormBase entries, so mxbuild rejects the property with CE6083 "not supported by your theme" — after `check --references` passed it. `design-spacing.md` says design properties and classes write the identical model; that holds only when the theme declares the property. | Greenfield pilot 2026-09-04, mxcli v0.20.0, Mendix 11.13.0, signal theme: `DesignProperties: ['Form orientation': 'Vertical']` on a DataView → exec ok, CE6083 at the gate, auto-restored. `Class: 'form-vertical'` built clean — and then changed nothing on screen, because in that theme the form layout is decided by the theme's own dataview rules, not the class (ui-loop.md "which side is wrong", row 1). The look caught it; no gate could. |
| 25 | Write a microflow with any loop, >20 planned activities, or a list built from a list | **STOP → run `microflow-preflight.md` and post its checklist first.** | Lint runs only after exec and counts only top-level activities (a loop body is invisible to CONV009/QUAL003); CONV011 commit-in-loop and MPR008 overlap findings came back on field builds after the write, not before. |
| 26 | Bind a DataGrid 2 column, or a grid `sort by`, to an attribute **over an association** | **STOP → write the column path unquoted, and do not sort over an association.** Use `Attribute: Assoc_A_B/Name`, never `"Assoc_A_B/Name"`. On ≤ v0.24.0 sort by a local attribute or use a microflow datasource. This is the one place a quote-every-identifier convention must break. | A quoted path is stored as one attribute named `Assoc_A_B/Name`, and `sort by Assoc/Attr` is written as a plain attribute of the grid entity. Both pass `check --references` (v0.24.0, re-verified 2026-09-25) and give CE1613 at build (v0.23.0 field run). `BUG-DRAFT-dg2-association-path-bindings` |

**Default to mxcli for:** entities/attributes/enums, associations (after SHOW ASSOCIATIONS check), microflows (without inline assoc-sets), demo users, module roles/grants, navigation.

Expand Down
24 changes: 24 additions & 0 deletions skills/lint-that-actually-runs.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,30 @@ sqlite3 .mxcli/catalog.db "SELECT DISTINCT ActionType FROM activities;"
the same API with different casing and run both. `CONV015` (`"Persistent"`) returned 4;
`ARCH002`/`ARCH003` (`"PERSISTENT"`) returned 0. One command, conclusive.

**Verified on v0.24.0 (2026-09-25, probe rule on a scratch copy of a small PoC model; also found
in the field on v0.23.0).** Three more facts the bundled guide gets wrong or leaves out:

- `mf.microflow_type` is `"MICROFLOW"` / `"NANOFLOW"` / `"RULE"`. The guide says `"microflow"`.
`microflows()` yields all three flavours from non-platform modules, so a microflow-only rule
must filter on the type.
- SQL and Starlark spell entity types differently. `CATALOG.ENTITIES` gives `PERSISTENT` /
`NON_PERSISTENT`, while Starlark `entity_type` gives `Persistent` / `NonPersistent`. A rule
prototyped in SQL and pasted into Starlark goes blind. That happened twice in the field, 0 of 16
and 0 of 28 real hits.
- `mf.activity_count` counts **top-level flow objects**: actions, splits, loops and annotations,
excluding start, end and merge. It does not count actions inside a loop. `activities_for()` has
the same top-level scope. So CONV009 over-reports actions, and nothing counts loop bodies.

```python
# before: reads as "actions", measures objects (annotation + retrieve + if + loop{change} + commit + log → 6)
if mf.microflow_type == "microflow" and mf.activity_count > 15:
# after: matches, and counts actions (→ 3; the change inside the loop is still invisible, say so in the message)
acts = [a for a in activities_for(mf.qualified_name) if a.activity_type == "ActionActivity"]
if mf.microflow_type == "MICROFLOW" and len(acts) > 15:
```

Ledger: BUG-63 (addendum 2026-09-25).

## 2. A rule that matches nothing must say so

Fixing the casing is not the end of it. A rule can reach the model and still inspect nothing,
Expand Down
Loading