Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
c25fda5
fix(entities): reject an unknown type on MODIFY ATTRIBUTE, add DROP D…
claude Aug 17, 2026
e923be7
docs: triage mxcli-banking FINDINGS.md against current main
claude Aug 17, 2026
d895d64
fix(executor): RENAME ATTRIBUTE now updates cross-references (#910)
claude Aug 17, 2026
1b2db94
fix(widgets): serialize a required object-list TextTemplate with its …
claude Aug 17, 2026
646ee29
docs(proposals): record attribute rename as a consumer of expression …
claude Aug 17, 2026
201fd6f
feat(settings): allow enabling optimistic locking from MDL
claude Aug 17, 2026
35efa02
feat(rename): rewrite XPath constraints when an attribute is renamed …
claude Aug 17, 2026
71d3f95
feat(settings): expose the remaining model settings, and stop leaking…
claude Aug 17, 2026
2db2718
Merge pull request #161 from ako/claude/issue-910-modify-attribute-ty…
ako Aug 17, 2026
c1fbbe3
docs: refute the view-entity finding by measuring it on Mendix 11.13
claude Aug 17, 2026
fe87c1a
docs: settle CASE/enum-split support, fix all seven contradicting sur…
claude Aug 17, 2026
85b3c33
docs: pin where an enum may be a string literal, measured per context
claude Aug 17, 2026
3f9cc20
Merge branch 'main' into claude/mxcli-issues-ovfoxk
ako Aug 17, 2026
17436d2
Merge pull request #162 from ako/claude/mxcli-issues-ovfoxk
ako Aug 17, 2026
4bbeae1
Merge pull request #163 from ako/claude/banking-app-feedback-do04dy
ako Aug 17, 2026
ee14174
Merge pull request #164 from ako/claude/bootstrap-prompt-smaller-37u3fu
ako Aug 17, 2026
c5abe5b
Merge branch 'mendixlabs:main' into main
ako Aug 17, 2026
218d4fe
feat(exec): refuse a script whose checks report an error
claude Aug 17, 2026
c464f18
fix(pages): write OnChange on every input widget, not just textbox
claude Aug 17, 2026
be223c5
fix(settings): name an unknown model setting instead of blaming the M…
claude Aug 17, 2026
a40fb0f
feat(microflows): IN QUEUE — run a call activity on a task queue
claude Aug 17, 2026
f288106
mxcli test: implement @verify, and fix the OQL path it runs on
claude Aug 17, 2026
9074d93
Merge pull request #165 from ako/claude/exec-preflight-check
ako Aug 17, 2026
028c398
Merge pull request #167 from ako/claude/fix-unknown-settings-key-message
ako Aug 17, 2026
51566ba
Merge origin/main into the attribute-rename branch
claude Aug 17, 2026
9223170
Merge pull request #166 from ako/claude/issue-910-rename-attribute-refs
ako Aug 17, 2026
bb027d7
Merge branch 'main' into claude/mxcli-findings-nnl181
ako Aug 17, 2026
4f893ce
Merge pull request #169 from ako/claude/sudoku-test-issue-46-iyxn30
ako Aug 17, 2026
b75f755
Merge remote-tracking branch 'origin/main' into claude/mxcli-findings…
claude Aug 17, 2026
622386c
fix(check): an action slot is authorable by its source, not its stora…
claude Aug 17, 2026
60ba481
mxcli test: leave the project byte-identical after a run
claude Aug 17, 2026
5c2cdcc
run --local --watch: wait for a write to finish before building
claude Aug 17, 2026
7256062
fix(pages): read a pluggable widget's action back, and refuse the ass…
claude Aug 17, 2026
a84bec6
Merge pull request #168 from ako/claude/mxcli-findings-nnl181
ako Aug 17, 2026
deb6289
Merge pull request #170 from ako/claude/sudoku-test-issue-46-iyxn30
ako Aug 17, 2026
c1d590c
docs: correct the capability docs, and read a query's table mapping back
claude Aug 17, 2026
678b3fd
docs(matrix): audit against Studio Pro's document-type list, not agai…
claude Aug 18, 2026
b7b82ff
fix(show): a grammar alternative with no visitor branch exited 0 in s…
claude Aug 18, 2026
5cf3b26
refactor(grammar): remove the dead notebook grammar
claude Aug 18, 2026
256e32e
fix(check): MDL003 no longer demands a RETURN that the builder synthe…
claude Aug 18, 2026
13bf70c
fix(queues): the rewrite guard was a no-op on the legacy engine
claude Aug 18, 2026
8329759
Merge pull request #171 from ako/claude/mxcli-findings-nnl181
ako Aug 18, 2026
cde2b8a
chore(lsp): drop NOTEBOOK from the generated completions
claude Aug 18, 2026
dada96c
fix(dbconnection): SQL replaced by a parameter default, and a lossy r…
claude Aug 18, 2026
fcfbe7b
fix(layout): size a loop box from its contents, not a statement count…
claude Aug 18, 2026
c8a7be3
Merge pull request #172 from ako/claude/mxcli-findings-nnl181
ako Aug 18, 2026
c134251
fix(lint): stop CONV010 and QUAL004 reporting correct code as violations
claude Aug 17, 2026
7a3bf7a
feat(lint): add MPR011, flagging activities outside their loop container
claude Aug 18, 2026
44d6b60
docs(oql): ORDER BY DESC on a nullable column puts the nulls first
claude Aug 18, 2026
d70598b
chore(lsp): regenerate the completion keywords from the current lexer
claude Aug 18, 2026
751c739
fix(executor): keep excluded documents excluded, and target the live …
claude Aug 18, 2026
cefa3b8
fix(microflows): keep a replaced microflow's StartEvent position
claude Aug 18, 2026
4d9a211
Merge remote-tracking branch 'origin/main' into claude/mxcli-issues-o…
claude Aug 18, 2026
4da50a2
Merge pull request #173 from ako/claude/sudoku-test-issue-46-iyxn30
ako Aug 18, 2026
632e5c0
Merge pull request #175 from ako/claude/lint-rule-vocabulary-drift
ako Aug 18, 2026
4705246
Merge pull request #176 from ako/regen-completions
ako Aug 18, 2026
3dacd08
Merge branch 'main' into claude/bootstrap-prompt-smaller-37u3fu
ako Aug 18, 2026
def9f3e
Merge pull request #177 from ako/claude/bootstrap-prompt-smaller-37u3fu
ako Aug 18, 2026
443e80d
Merge pull request #174 from ako/claude/mxcli-issues-ovfoxk
ako Aug 18, 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
29 changes: 23 additions & 6 deletions .claude/lint-rules/conv010_act_microflow_content.star
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,25 @@
#
# Microflows prefixed with ACT_ are page action microflows. They should only
# contain UI-related activities:
# - ShowFormAction (show page)
# - CloseFormAction (close page)
# - ShowHomeFormAction (show home page)
# - ShowPageAction (show page)
# - ClosePageAction (close page)
# - ShowHomePageAction (show home page)
# - ShowMessageAction (show message)
# - DownloadFileAction (download file)
# - SubMicroflow (call sub-microflow for logic delegation)
# - MicroflowCallAction (call sub-microflow for logic delegation)
#
# Business logic should be delegated to SUB_ microflows.
# Requires FULL catalog (REFRESH CATALOG FULL).
#
# NOTE ON NAMES: the catalog labels an action with its *SDK* type name, derived
# from the parsed action's Go type (catalog.getMicroflowActionType). That is not
# always the name Mendix uses in BSON: ShowPageAction is stored as
# "Microflows$ShowFormAction", ClosePageAction as "CloseFormAction", and so on
# (see the storage-name table in CLAUDE.md). This rule matches what the linter
# sees, so it must use the SDK names — it previously used the storage names and
# therefore matched nothing, flagging every ACT_ microflow that showed a page,
# closed one, or called a sub-microflow. Both spellings are listed so the rule
# keeps working if the catalog's vocabulary is ever changed to the storage names.

RULE_ID = "CONV010"
RULE_NAME = "ACTMicroflowContent"
Expand All @@ -20,16 +30,23 @@ SEVERITY = "warning"

# Allowed action types in ACT_ microflows
ALLOWED_ACTIONS = (
# SDK names — what the catalog actually reports.
"ShowPageAction",
"ClosePageAction",
"ShowHomePageAction",
"ShowMessageAction",
"DownloadFileAction",
"MicroflowCallAction",
# Storage names — belt and braces; see the note above.
"ShowFormAction",
"CloseFormAction",
"ShowHomeFormAction",
"ShowMessageAction",
"DownloadFileAction",
)

# Allowed activity types (non-action activities)
ALLOWED_ACTIVITY_TYPES = (
"SubMicroflow",
"MicroflowCallAction",
"StartEvent",
"EndEvent",
"ExclusiveSplit",
Expand Down
33 changes: 26 additions & 7 deletions .claude/lint-rules/orphaned_elements.star
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ ENTRY_POINT_PREFIXES = ["ACT_", "SCH_", "WS_", "REST_", "OData_"]
# Page name patterns that are likely entry points
ENTRY_PAGE_PATTERNS = ["Home", "Login", "Index", "Dashboard"]

# Reference kinds that mean "something causes this microflow to run". These are
# catalog RefKind values (mdl/catalog/builder_references.go); a kind missing here
# turns a live document into a false "not called from anywhere" finding.
MICROFLOW_ENTRY_KINDS = ["call", "schedule", "datasource", "action", "calculate"]

# Reference kinds that mean "something opens this page".
PAGE_ENTRY_KINDS = ["show_page", "home_page", "login_page", "menu_item", "action"]

def is_entry_point_microflow(name):
"""Check if a microflow name suggests it's a UI/scheduled entry point."""
for prefix in ENTRY_POINT_PREFIXES:
Expand Down Expand Up @@ -54,13 +62,20 @@ def check():
# Get references to this microflow
refs = refs_to(mf.qualified_name)

# A scheduled event is an entry point: it runs the microflow without
# anything "calling" it, so a 'schedule' edge counts as a caller. Without
# this, a microflow that runs nightly in production was reported as
# orphaned — with the suggestion "Remove if unused".
# Anything that causes the microflow to run counts as a caller, not just
# a literal "call" edge. A microflow reached only through one of the other
# kinds was reported as orphaned with the suggestion "Remove if unused":
#
# schedule a scheduled event runs it (Mendix's cron)
# datasource a page or widget uses it as a data source
# action a widget button calls it
# calculate a calculated attribute computes with it
#
# The banking-app report hit the 'datasource' case: DS_CurrentCustomer and
# DS_MyAccounts are both page data sources and both were flagged.
has_callers = False
for ref in refs:
if ref.ref_kind == "call" or ref.ref_kind == "schedule":
if ref.ref_kind in MICROFLOW_ENTRY_KINDS:
has_callers = True
break

Expand All @@ -86,10 +101,14 @@ def check():
# Get references to this page
refs = refs_to(page.qualified_name)

# Check if any reference shows this page
# A page is reachable if anything opens it. Navigation counts: a page that
# is only a home page, a login page or a menu item is reached by the
# client, not by a microflow. Counting only 'show_page' reported those as
# orphaned — masked until now by ENTRY_PAGE_PATTERNS, which happens to
# cover the pages most likely to be navigation targets.
is_shown = False
for ref in refs:
if ref.ref_kind == "show_page":
if ref.ref_kind in PAGE_ENTRY_KINDS:
is_shown = True
break

Expand Down
23 changes: 23 additions & 0 deletions .claude/skills/fix-issue.md

Large diffs are not rendered by default.

24 changes: 23 additions & 1 deletion .claude/skills/mendix/check-syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,28 @@ This skill ensures MDL scripts are validated before presenting them to users or
- Executing MDL scripts via `mxcli exec`
- Committing MDL files to version control

## `exec` refuses what `check` rejects

`mxcli exec` runs the same semantic checks before writing anything. A script whose
checks report an **error** is not executed at all — nothing is written — because
`exec` applies statements one at a time and cannot roll back, so a known-bad
script would leave the model partly updated. Warnings are printed and do not stop
the run.

```bash
mxcli exec script.mdl -p app.mpr # checked, then applied
mxcli exec script.mdl -p app.mpr --no-check # applied regardless
```

This does **not** replace running `check` yourself. `check` is faster, needs no
write connection, and reports the warnings worth reading before you commit to a
run. What the gate guarantees is narrower and still valuable: a script that slips
past you cannot half-apply.

It also does not mean the script is *correct*. `mxcli check` validates MDL syntax
and mxcli's own rules; it does not validate the Mendix model. Run
`mx check` (or `mxcli docker check -p app.mpr`) after applying a slice.

## Pre-Flight Validation Checklist

Before writing any MDL, verify these requirements:
Expand Down Expand Up @@ -46,7 +68,7 @@ Before writing any MDL, verify these requirements:
**NOT Supported (will cause errors):**
- `set $var = call microflow ...` - Use `$var = call microflow ...` (no SET)
- `while ... end while` - Use `loop` with lists
- `case ... when ... end case` - Use nested `if`
- `case ... when 'String' ...` - Case values are bare enum identifiers, never quoted or qualified; `case ... when Value then ... end case;` itself IS supported (enum splits only), and takes no `else` (MDL008) and no `AS` alias
- `TRY ... CATCH` - Use `on error` blocks
- `break` / `continue` - Not implemented
- `commit message 'text'` - Not in current grammar (session command only)
Expand Down
16 changes: 15 additions & 1 deletion .claude/skills/mendix/generate-domain-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,12 @@ alter entity Module.Order
add attribute VATRate: decimal
add attribute VATAmount: decimal;

-- Rename an attribute (preserves data)
-- Rename an attribute (preserves data). Every stored reference follows it:
-- microflow create/change members, page attribute widgets, validation rules,
-- access rules -- and XPath constraints too ([CreatedDate > ...]), including
-- ones that reach the entity through an association. Microflow expressions
-- ($Order/CreatedDate) are NOT rewritten -- mxbuild reports those as CE0117,
-- so build afterwards.
alter entity Module.Order
rename attribute CreatedDate to OrderDate;

Expand Down Expand Up @@ -988,6 +993,15 @@ alter entity Module.Customer

**Supported operations:** ADD ATTRIBUTE, RENAME ATTRIBUTE, MODIFY ATTRIBUTE (type + `NULLABLE`/`NOT NULL`/`UNIQUE`/`DEFAULT` constraints), DROP ATTRIBUTE, SET DOCUMENTATION, SET COMMENT, ADD INDEX, DROP INDEX, SET POSITION.

> **`MODIFY ATTRIBUTE` always takes a type** — restate it even when you only want
> to change a constraint. Its type slot accepts a bare qualified name, so a
> clause written in the type position is read as a type name:
> `MODIFY ATTRIBUTE X SET DEFAULT 0` treats `SET` as the type. mxcli now refuses
> that; before it did, the statement rewrote the attribute to an enumeration and
> produced a project Mendix could not open (#910).
>
> To clear a default value use **`DROP DEFAULT ON ATTRIBUTE <name>`**.

### Entity Positioning Guidelines

When creating or repositioning entities, follow these layout rules for readable domain models:
Expand Down
47 changes: 45 additions & 2 deletions .claude/skills/mendix/project-settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,45 @@ alter settings model JavaVersion = 'Java21'; -- or '21'; see note below
alter settings model RoundingMode = 'HalfUp';
alter settings model AllowUserMultipleSessions = true;
alter settings model ScheduledEventTimeZoneCode = 'Etc/UTC';
alter settings model DefaultTimeZoneCode = 'Europe/Amsterdam';
alter settings model FirstDayOfWeek = 'Monday'; -- Default, Monday..Sunday
alter settings model DecimalScale = 8;
alter settings model EnableDataStorageOptimisticLocking = true;
alter settings model UseDatabaseForeignKeyConstraints = true;
alter settings model UseOQLVersion2 = true;
alter settings model SslCertificateAlgorithm = 'PKIX'; -- PKIX or SunX509
```

**Not every project stores every setting.** Mendix adds model settings over time —
a blank 9.24 project stores 12 of them, a blank 11.13 project stores 17. mxcli
refuses an `alter` naming one the project does not store rather than introducing
it, because Studio Pro refuses to open a model carrying a property its type does
not define (and `mx check` does *not* catch that). `describe settings` emits only
what the project actually stores, so its output always replays.

**`UseSystemContextForBackgroundTasks` is read but not writable.** Mendix withdrew
it: `mx check` on 11.13 rejects a project holding `true` with
**CE9436** *"The project setting 'System context tasks' is not supported anymore."*
mxcli preserves whatever the project stores and offers no way to change it.

**Optimistic locking** is App Settings → Runtime → *Optimistic locking* in Studio
Pro. With it on, the runtime tracks an `MxObjectVersion` on every persistable
entity and a commit whose version no longer matches the database throws
`ConcurrentModificationRuntimeException`.

Reach for it when a microflow reads a value, decides on it, and writes it back —
the classic "check the balance, then debit it" shape. A microflow is one
transaction, so each run is *atomic*, but that does not make two concurrent runs
*serialisable*: both can pass the check and the second overwrites the first. With
optimistic locking on, the second commit fails and its whole microflow rolls back
instead of silently overdrawing the account.

It **detects, it does not retry.** Mendix's guidance is that the handler must
catch the exception, *reload* the object, re-apply and re-commit — "trying to
commit the same object without reloading always results in an optimistic locking
error." Without that the user sees a failure rather than a transfer that works.
The money is safe either way, which is the half that matters.

**JavaVersion spelling.** Mendix renamed this property between versions: up to 11.6
it stores `JavaVersion` = `'Java21'`, from 11.12 it stores `JavaMajorVersion` =
`'21'`. Write either spelling — mxcli reads which one the project uses and stores
Expand Down Expand Up @@ -64,8 +101,14 @@ alter settings configuration 'Default'
```

`HttpPortNumber`, `ServerPortNumber`, `BcryptCost`, `DefaultTaskParallelism` and
`WorkflowEngineParallelism` are Integer-typed, and `AllowUserMultipleSessions` is
Boolean. An unparseable value is rejected by `mxcli check` (MDL-SET01 / MDL-SET02)
`WorkflowEngineParallelism` and `DecimalScale` are Integer-typed;
`AllowUserMultipleSessions`, `EnableDataStorageOptimisticLocking`,
`UseDatabaseForeignKeyConstraints` and `UseOQLVersion2` are Boolean; `FirstDayOfWeek`
and `SslCertificateAlgorithm` are enumerations, matched case-insensitively and
stored in Mendix's own spelling (MDL-SET03 rejects a non-member rather than writing
it through — an unresolvable enum value is what makes Studio Pro throw
"Sequence contains no matching element").
An unparseable value is rejected by `mxcli check` (MDL-SET01 / MDL-SET02)
and by the write itself — it is no longer silently ignored. Quoted numbers are
fine: `HttpPortNumber = '8080'` and `HttpPortNumber = 8080` are equivalent.

Expand Down
56 changes: 31 additions & 25 deletions .claude/skills/mendix/rest-call-from-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,17 @@ describe json structure Module.JSON_MyStructure;
Derive one entity per JSON object type. Name them after what they represent (not after JSON keys).

```sql
create entity Module.MyRootObject (NON_PERSISTENT)
stringField : string
intField : integer
decimalField : decimal
boolField : boolean default false;

create entity Module.MyNestedObject (NON_PERSISTENT)
name : string
code : string;
create non-persistent entity Module.MyRootObject (
stringField : string,
intField : integer,
decimalField : decimal,
boolField : boolean default false
);

create non-persistent entity Module.MyNestedObject (
name : string,
code : string
);

create association Module.MyRootObject_MyNestedObject
from Module.MyRootObject
Expand All @@ -61,7 +63,9 @@ create association Module.MyRootObject_MyNestedObject
- All string fields: bare `string` (no length — unlimited)
- All number fields: `integer`, `decimal`, or `long` — remove defaults for optional fields
- Boolean fields **require** `default true|false`
- `NON_PERSISTENT` — these entities are not stored in the database
- `non-persistent` — these entities are not stored in the database. The keyword is
**hyphenated and goes before `entity`**: `create non-persistent entity Mod.X (...)`.
`NON_PERSISTENT`, and a `(NON_PERSISTENT)` inside the body, are both parse errors
- One association per parent→child relationship; name it `Parent_Child`

---
Expand Down Expand Up @@ -188,21 +192,23 @@ create json structure Integrations.JSON_BibleVerse
snippet '{"translation":{"identifier":"web","name":"World English Bible","language":"English","language_code":"eng","license":"Public Domain"},"random_verse":{"book_id":"1SA","book":"1 Samuel","chapter":17,"verse":49,"text":"David put his hand in his bag, took a stone, and slung it."}}';

-- Step 2: Entities
create entity Integrations.BibleApiResponse (NON_PERSISTENT);

create entity Integrations.BibleTranslation (NON_PERSISTENT)
identifier : string
name : string
language : string
language_code : string
license : string;

create entity Integrations.BibleVerse (NON_PERSISTENT)
book_id : string
book : string
chapter : integer
verse : integer
text : string;
create non-persistent entity Integrations.BibleApiResponse ();

create non-persistent entity Integrations.BibleTranslation (
identifier : string,
name : string,
language : string,
language_code : string,
license : string
);

create non-persistent entity Integrations.BibleVerse (
book_id : string,
book : string,
chapter : integer,
verse : integer,
text : string
);

create association Integrations.BibleApiResponse_BibleTranslation
from Integrations.BibleApiResponse
Expand Down
27 changes: 27 additions & 0 deletions .claude/skills/mendix/run-local.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,33 @@ $cf-over: rgb(168, 50, 30);
background-color: rgba($cf-over, 0.1);
```

## `--watch` waits for a write to finish before it builds

The watcher polls the model source and rebuilds when it changes — but it does
**not** rebuild the instant it sees the first change. It waits for the source to
stop moving first.

That matters because an `mxcli exec` of a real script rewrites the `.mpr` and
many `mprcontents/*.mxunit` files over several seconds. Building on the first
change deploys whatever is on disk at that instant: a **half-applied model**,
which looks like an ordinary stale build until you notice the app is missing
things the script definitely created.

The escape hatch that used to cover this — "just run the script again" — stopped
working when `exec` became byte-idempotent. A re-run of an already-applied script
writes nothing, so nothing re-triggers the watcher, and the stale build is what
you are left with. Waiting for the write to settle is what makes that
unreachable rather than merely unlikely.

Practical consequences:

- A rebuild starts a couple of poll intervals after your last change, not
immediately. A single editor save is unaffected in practice.
- A long `exec` produces **one** build, of the finished model, instead of a
build of the first file it happened to touch.
- If you ever do need to force a rebuild without changing anything, `touch` the
`.mpr` — the watcher keys on mtime, so that re-triggers it.

## "My edit didn't show up" — stale process, not stale cache

`run --local` refuses to boot when its ports (8080/8090/6543) are already answering,
Expand Down
Loading
Loading