Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e15e587
fix(placement): apply a FOLDER clause on CREATE OR MODIFY, for every …
claude Aug 20, 2026
c85ab25
feat(move): accept every top-level document type, and stop the folder…
claude Aug 20, 2026
f2869e3
feat(placement): a FOLDER clause on CREATE for every document type
claude Aug 20, 2026
7adefed
docs(placement): document the folder clause everywhere it is looked up
claude Aug 20, 2026
7595718
fix(test): --require-assertions was a silent no-op on the default runner
claude Aug 20, 2026
48074c4
fix(show): make SHOW ACCESS ON ENTITY parse, and stop SHOW ACCESS ON …
claude Aug 20, 2026
82cacfb
docs(mdl): bug report and proposal for #913 split statement syntax
claude Aug 20, 2026
cc21821
feat(mdl): unify the type split onto the enum split's branch syntax
claude Aug 20, 2026
31e4631
fix: write the page keys Studio Pro writes, and the marker its reader…
claude Aug 20, 2026
c6264e8
fix: let a Mendix array be created, and never write one without its m…
claude Aug 20, 2026
9b8ffc3
fix: flag the hidden widget property that fails the build, not the ha…
claude Aug 20, 2026
050b77e
docs: record the three #931 symptoms and add the reproduction script
claude Aug 20, 2026
e2254cb
Merge pull request #197 from ako/claude/bootstrap-prompt-smaller-37u3fu
ako Aug 20, 2026
0ea6b05
Merge branch 'main' into claude/show-access-on-entity-925
ako Aug 20, 2026
90e29fc
Merge pull request #198 from ako/claude/show-access-on-entity-925
ako Aug 20, 2026
6029d7b
Merge branch 'main' into claude/mxcli-findings-nnl181
ako Aug 20, 2026
b48a9a6
Merge branch 'main' into claude/issue-913-split-syntax
ako Aug 20, 2026
0e3360e
Merge pull request #199 from ako/claude/issue-913-split-syntax
ako Aug 20, 2026
0e02d18
Merge branch 'main' into claude/sudoku-test-issue-46-iyxn30
ako Aug 20, 2026
d5ce8a9
Merge branch 'main' into claude/mxcli-findings-nnl181
ako Aug 20, 2026
3c12a0d
Merge pull request #200 from ako/claude/sudoku-test-issue-46-iyxn30
ako Aug 20, 2026
95de452
Merge branch 'main' into claude/mxcli-findings-nnl181
ako Aug 20, 2026
48114de
Merge pull request #201 from ako/claude/mxcli-findings-nnl181
ako Aug 20, 2026
e50ddac
fix(describe): read and render a widget datasource in one place (#941)
claude Aug 20, 2026
2354f21
docs(fix-issue): record the mx check baseline trap that weakened #941…
claude Aug 20, 2026
5688c6a
fix(describe): emit an XPath constraint as a bracketed group, always
claude Aug 20, 2026
33f8e62
fix(describe): carry a flow datasource's arguments through describe
claude Aug 20, 2026
89f1ed8
fix(catalog): index List View specialization template contents
claude Aug 20, 2026
27e5e57
fix(building-block): rebind a datasource override by widget type, not…
claude Aug 20, 2026
2d422fa
feat(pages): author and read List View specialization templates
claude Aug 20, 2026
d240f2b
Merge pull request #202 from ako/claude/bootstrap-prompt-smaller-37u3fu
ako Aug 20, 2026
04ebf11
feat(pages): ALTER PAGE support for List View specialization templates
claude Aug 20, 2026
2679d7e
Merge remote-tracking branch 'origin/main' into claude/mxcli-findings…
claude Aug 20, 2026
09fd98d
Merge pull request #203 from ako/claude/mxcli-findings-nnl181
ako Aug 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
17 changes: 17 additions & 0 deletions .claude/skills/fix-issue.md

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions .claude/skills/mendix/alter-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,46 @@ For changes that should be applied across **many pages** (e.g., "add `Class='car

## Operations

### List View Specialization Templates

A List View template has no name, so it cannot be reached by a widget ref like
every other target. Adding one reuses `INSERT INTO` with the same
`template for` block `create page` uses — a template has one spelling
everywhere. Removing one has its own form:

```sql
alter page Pages.Vehicle_Overview {
insert into vehicleListView {
template for Pages.Motorcycle {
dynamictext mcLabel (content: 'Motorcycle {1}', contentparams: [{1} = Brand])
}
};
drop template for Pages.SUV in vehicleListView
};
```

Naming the list view in the `drop` is required, not optional: one page can hold
two list views with a template for the same entity.

Most template edits need none of this. The widgets **inside** a template are
ordinary named widgets, so `set content = '…' on busLabel` and
`insert after busLabel { … }` already work and land in the right template. To
replace a whole template, `drop` it and `insert` the new one in the same block —
operations apply in order.

Refused, each naming the problem:

- `insert before` / `insert after` a template — templates are not siblings of the
widgets in the list view's body, so only `insert into` makes sense.
- mixing `template for …` blocks with ordinary widgets in one `insert` — they go
to different places (the Templates array and the default body). Use two inserts.
- a template for an entity that is not the list view's entity or a specialization
of it — it could never match an object the list view shows.
- a second template for an entity that already has one.
- `drop template for` an entity with no template — the error names the ones that
are there, because dropping nothing and reporting success is how a typo becomes
a silent no-op.

### SET - Modify Widget Properties

```sql
Expand Down
34 changes: 34 additions & 0 deletions .claude/skills/mendix/create-page.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,40 @@ actionbutton btnNew (caption: 'New', action: create_object Module.Product then s
**Using `$currentObject`:**
Use `$currentObject` inside DATAGRID, LISTVIEW, or GALLERY columns to reference the current row's object. This is typically used in columns with `ShowContentAs: customContent` for action buttons.

### LISTVIEW Specialization Templates

A List View over a **generalization** can render a different body per
specialization. The template is identified by the entity it renders — it has no
name, which is why the keyword takes `for` and a qualified entity:

```sql
listview vehicleListView (datasource: database from Pages.Vehicle) {
-- the default body: used for an object no template matches
dynamictext defaultVehicle (content: '{1} {2}', contentparams: [{1} = Brand, {2} = Model])

template for Pages.Bus {
dynamictext busLabel (content: 'Bus, capacity {1}', contentparams: [{1} = PassengerCapacity])
}
template for Pages.Truck {
dynamictext truckLabel (content: 'Truck, max load {1} kg', contentparams: [{1} = MaxLoadKg])
}
}
```

Rules:

- The entity must be the list view's entity **or a specialization of it**. A
template for an unrelated entity can never match, so it is refused.
- **At most one template per entity.**
- Templates keep their **source order** — Mendix stores and matches in that
order, so it is authored, not derived, and DESCRIBE emits them as stored.
- Inside a template the context object **is the specialization**, so an
attribute only that specialization has resolves there (`PassengerCapacity` on
`Pages.Bus` above, which `Pages.Vehicle` does not have).

Do not confuse this with a **Gallery's** `template <name>`, which is a named
content slot, not a per-specialization body.

### LINKBUTTON Widget

Similar to ActionButton but rendered as link:
Expand Down
5 changes: 5 additions & 0 deletions .claude/skills/mendix/custom-widgets.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ pluggablewidget 'com.mendix.widget.web.barchart.BarChart' chart1 {
}
```

A series datasource takes any of the usual kinds — `database from …`,
`microflow …`, `nanoflow …`, `$Param`, `selection …` — not just `database`.
(Before #941 `describe page` rendered every series datasource as `database
from`, so a microflow-backed series described back as a missing entity.)

**Pie / HeatMap bind at the WIDGET level** (no series block). Both need `DataSource:`
+ `ValueAttribute:`; Pie also needs a required `SeriesName:`; HeatMap adds `scalecolor` items:

Expand Down
23 changes: 23 additions & 0 deletions .claude/skills/mendix/json-structures-and-mappings.md
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,29 @@ end;

---

## Placing Documents in Folders

Every one of these documents takes a `folder` clause on `create`, straight after
the qualified name. Missing folders in the path are created:

```mdl
create json structure Sales.JSON_Order folder 'Private/JSON structures'
snippet '{"id": 1, "total": 9.99}';

create import mapping Sales.IMM_Order folder 'Private/Import mappings'
with json structure Sales.JSON_Order
{
create Sales.Order { OrderId = id, Total = total }
};
```

On `create or modify` the clause **moves** an existing document. Omitting it
leaves placement alone — it never returns a document to the module root — so
adding a folder to an existing script is safe and removing one is a no-op.
`describe` emits the clause, so a description replays into the same folder.

See `organize-project.md` for `move` and the full folder story.

## Common Mistakes

| Mistake | Fix |
Expand Down
3 changes: 2 additions & 1 deletion .claude/skills/mendix/manage-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ show demo users;
-- Access on specific elements
show access on microflow MyModule.ProcessOrder;
show access on page MyModule.CustomerOverview;
show access on MyModule.Customer;
show access on entity MyModule.Customer;
show access on MyModule.Customer; -- a bare name means the entity

-- Full security matrix
show security matrix;
Expand Down
71 changes: 52 additions & 19 deletions .claude/skills/mendix/organize-project.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,25 +202,58 @@ move page OldModule.CustomerPage to NewModule;

## Supported Document Types

| Document Type | FOLDER on Create | MOVE Command |
|---------------|-----------------|--------------|
| Page | `folder: 'path'` (property) | `move page ...` |
| Microflow | `folder 'path'` (keyword) | `move microflow ...` |
| Nanoflow | `folder 'path'` (keyword) | `move nanoflow ...` |
| Snippet | `folder: 'path'` (property) | `move snippet ...` |
| Enumeration | N/A | `move enumeration ...` |
| Constant | N/A | `move constant ...` |
| Database connection | N/A | `move database connection ...` |
| Java action | N/A | `move java action ...` |
| OData service (published) | N/A | `move odata service ...` |
| Entity | N/A | `move entity ...` (module only, no folders) |

**Java actions and published OData services have no folder clause on `create`**, so
`move` is the only way to place them — before this they were stuck at the module
root forever. Both are plain document units, so the move is model-level only: it
changes containment and nothing else.

**Note:** Pages and snippets use property syntax (`folder: 'path'` inside parentheses). Microflows and nanoflows use keyword syntax (`folder 'path'` before `begin`). Entities are embedded in domain models and can only be moved to a different module (no folder support).
`move` accepts **every top-level document type**, spelled the way `describe`
spells it:

| Group | Types |
|-------|-------|
| Pages | `page`, `snippet`, `building block`, `layout`, `menu` |
| Logic | `microflow`, `nanoflow`, `workflow`, `queue`, `scheduled event` |
| Domain | `enumeration`, `constant`, `regular expression` |
| Mappings | `json structure`, `import mapping`, `export mapping` |
| Code | `java action`, `javascript action`, `database connection`, `data transformer` |
| Resources | `image collection`, `icon collection` |
| Integration | `rest client`, `published rest service`, `odata client`, `odata service`, `business event service` |
| AI | `model`, `agent`, `knowledge base`, `consumed mcp service` |

`move entity` is the exception: an entity lives inside a domain model, so it
moves between **modules** only, never into a folder.

If the named document turns out to be a different type, the statement is refused
and the error names what it really is — `move queue Mod.JSON_Order` reports that
`Mod.JSON_Order` is a json structure.

### FOLDER on Create

Every document type takes a folder clause on `create`, so a document can be
placed in the statement that creates it rather than in a separate `move`. Where
the clause goes depends on the statement's shape:

| Document Type | FOLDER on Create |
|---------------|-----------------|
| Page, Snippet | `folder: 'path'` — a property, inside the parentheses |
| Microflow, Nanoflow | `folder 'path'` — a keyword, before `begin` |
| Enumeration, Constant | `folder 'path'` — a keyword, after the definition |
| Everything else | `folder 'path'` — a keyword, straight after the qualified name |

```mdl
create import mapping CRM.IMM_Order folder 'Private/Import mappings'
with json structure CRM.JSON_Order { create CRM.Order { Id = id } };

create queue CRM.Q_Orders folder 'Private/Queues' ( Parallelism: 3 );

create java action CRM.JA_Sync folder 'Private/Java' () returns string
as $$return null;$$;
```

**A folder clause on `create or modify` moves an existing document.** It used to
be silently ignored: the statement reported success, the folder was created, and
the document stayed where it was (#932). Omitting the clause leaves placement
alone — it never returns a document to the module root — so adding a folder to a
script is safe and removing it is a no-op.

`describe` emits the clause, so a description replays into the same folder
rather than into the module root.

## Example: Reorganize a Module

Expand Down
14 changes: 14 additions & 0 deletions .claude/skills/mendix/scheduled-events-and-queues.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,20 @@ Core.userActionCall("Ops.RefreshData")

Use `Core.microflowCall(...)` when the unit of work really is a microflow.

## Placing Them in Folders

Both take a `folder` clause on `create`, straight after the qualified name:

```mdl
create scheduled event Ops.SE_Nightly folder 'Private/Scheduled events'
( Microflow: Ops.ACT_Nightly, Repeat: Day, StartDateTime: '2026-01-01T02:00:00Z' );

create queue Ops.Q_Imports folder 'Private/Queues' ( Parallelism: 3 );
```

On `create or modify` the clause moves an existing document; omitting it leaves
placement alone. See `organize-project.md`.

## Validation Checklist

Before presenting a script:
Expand Down
37 changes: 28 additions & 9 deletions .claude/skills/mendix/write-microflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -571,29 +571,48 @@ end case;
Use `split type` when a microflow branches on an object's runtime specialization.
Use `cast` inside a type branch to create the specialized variable used by the branch body.

Branches are `when <Entity> then`, the same as an enumeration split — one
statement, two subjects.

```mdl
declare $IsSpecialized boolean = false;
split type $Input
case Sample.SpecializedInput
cast $SpecificInput;
set $IsSpecialized = true;
case Sample.BaseInput
when Sample.SpecializedInput then
cast $SpecificInput;
set $IsSpecialized = true;
when Sample.BaseInput then
when (empty) then
end split;
return $IsSpecialized;
```

`case` values are qualified entity names.
Branch values are qualified entity names.

> **`when (empty) then` is the null-object branch, not a default.** It is
> Mendix's `(empty)` outgoing flow, taken when the split variable is empty. It
> does **not** cover types you did not name — see the CE0090 note below.
>
> **The older spelling still works.** `case Sample.SpecializedInput` (no `then`)
> and `else` for the empty branch parse and build the identical flow, but warn
> **MDL065**. `case` introduced a *branch* here while introducing the *subject*
> in `case $x when V then` and in expressions, so the word meant two things
> (mxcli #913); `else` read as a default and never was one.

> **Every type needs a branch — including the base entity.** An object-type
> decision gets one outgoing flow per listed type, and a type with no flow fails
> the build with **CE0090** *"The 'X' value should be configured for an outgoing
> flow."* The base entity (the split variable's own type) counts: `case
> Sample.BaseInput` above is what covers "it is not any of the specializations".
>
> **`else` does not stand in for the base-type case.** It is accepted — it
> serializes as `Microflows$NoCase` — but it does not satisfy coverage, so
> `case Spec` + `else` still fails CE0090. Once every type has a branch, `else`
> is redundant. Verified on Mendix 11.6.6 and 11.13.0.
> **The `(empty)` branch does not stand in for the base-type case.** It is
> accepted — it serializes as `Microflows$NoCase` — but it does not satisfy
> coverage, so one named type plus `when (empty) then` still fails CE0090.
> Measured on 11.13.0: `when Zoo.Dog then` + an empty branch gives CE0090 for
> `Zoo.Cat` **and** `Zoo.Animal`; branch on every type, keep the empty branch,
> and it is 0 errors. Verified on Mendix 11.6.6 and 11.13.0.
>
> You cannot drop the empty branch either — that is **CE0089**. mxcli emits the
> flow unconditionally for that reason.
>
> **The split needs somewhere to go afterwards.** Branch bodies converge on a
> merge that continues to the microflow's end event, so a non-void microflow
Expand Down
Loading
Loading