Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
efa9d4a
Make building blocks and icon collections resolvable by bare DESCRIBE
claude Aug 11, 2026
07613df
Resolve a document's module through folders, not just its direct cont…
claude Aug 11, 2026
c33c467
Add DESCRIBE MENU for standalone menu documents
claude Aug 11, 2026
53f444e
Add CREATE OR MODIFY MENU and DROP MENU
claude Aug 11, 2026
7d6cd0b
Wire menus into the CLI describe command, skill, docs-site and status
claude Aug 11, 2026
4cedd3c
Emit module roles from DESCRIBE MODULE, and correct §7's security claim
claude Aug 11, 2026
a3da553
Make the menu doctype example self-contained and skip it on legacy
claude Aug 11, 2026
3130ca7
Apply gofmt to ten files that were checked in unformatted
claude Aug 11, 2026
12a07fb
Phase 1 slice 1: module snapshot + drift comparison engine
claude Aug 11, 2026
031e632
Phase 1 slice 2: build a reference project from a marketplace package
claude Aug 11, 2026
99aec66
Paginate the marketplace versions endpoint
claude Aug 11, 2026
72b30c6
Phase 1 slice 3: the marketplace diff command
claude Aug 11, 2026
d6d5614
Stop reading page templates as pages
claude Aug 11, 2026
5fa1ce8
Refuse module install that would rewrite an MPR v2 project as v1
claude Aug 11, 2026
e351ee4
Make theme modules diffable by clearing the flag on a throwaway copy
claude Aug 11, 2026
cff6823
fix(new): create the project in a staging directory, so a deep --outp…
claude Aug 11, 2026
5cea8ca
ci(docs): only publish Pages where a Pages site exists
claude Aug 11, 2026
86b9e6a
Measure what GUID is for: the database keys on it
claude Aug 11, 2026
f0ca0e0
Version-gate the agent doctypes; correct the provider claim
claude Aug 11, 2026
f38f02b
fix(catalog): index page references from widget actions, and stop `sh…
claude Aug 11, 2026
5f82ea3
Phase 2 slice 1: capture a module's GUID identities
claude Aug 11, 2026
47f7015
Phase 2 slice 2: transplant captured GUIDs onto a replaced module
claude Aug 11, 2026
beec206
Merge pull request #136 from ako/claude/fix-docs-deploy-on-fork
ako Aug 11, 2026
4490851
Merge pull request #135 from ako/claude/mxcli-issues-ovfoxk
ako Aug 11, 2026
0adcc39
Merge branch 'mendixlabs:main' into main
ako Aug 11, 2026
bac4f3f
Phase 2 slice 3: transplant a module's units without mx module-import
claude Aug 11, 2026
b1a138c
feat(queues): author task queues in MDL, and refuse the rewrite that …
claude Aug 11, 2026
7d48678
feat(scheduled-events): author Mendix's cron in MDL, and read back wh…
claude Aug 11, 2026
650acda
Merge branch 'main' into claude/task-queues
ako Aug 12, 2026
3317ffc
Phase 2 slice 4: preserve a module's role grants across an update
claude Aug 12, 2026
e2d31dd
docs(queues,scheduled-events): wire the new document types into every…
claude Aug 12, 2026
ae105d3
Merge remote-tracking branch 'origin/claude/task-queues' into claude/…
claude Aug 12, 2026
68df51d
Phase 2 slice 5: marketplace update, with --save-edits and --force
claude Aug 12, 2026
f47d8bf
Install a module's widget binaries during an update
claude Aug 12, 2026
5d07764
fix(catalog,linter): catalog scheduled events and queues, and stop ca…
claude Aug 12, 2026
ec193a8
Merge pull request #137 from ako/claude/task-queues
ako Aug 12, 2026
d53691b
Merge remote-tracking branch 'upstream/main' into sync-upstream
claude Aug 12, 2026
43e5624
Copy every file a package ships, not just its widgets
claude Aug 12, 2026
8860e7c
Install modules with mxcli's writer instead of mx module-import
claude Aug 12, 2026
93e2182
Document the marketplace install/update lifecycle in a skill
claude Aug 12, 2026
1e18ba7
Refuse a marketplace version the project's Mendix cannot import
claude Aug 12, 2026
41c2a86
Persist Mendix's model repairs without collapsing MPR v2
claude Aug 12, 2026
c123e29
Merge origin/main
claude Aug 12, 2026
d762d2e
Merge pull request #138 from ako/claude/mxbuild-diagnostics-spike-emta6h
ako Aug 12, 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
7 changes: 5 additions & 2 deletions .claude/lint-rules/orphaned_elements.star
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,13 @@ def check():
# Get references to this microflow
refs = refs_to(mf.qualified_name)

# Check if any reference is a call
# 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".
has_callers = False
for ref in refs:
if ref.ref_kind == "call":
if ref.ref_kind == "call" or ref.ref_kind == "schedule":
has_callers = True
break

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

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions .claude/skills/mendix/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Detailed syntax for each MDL document type:
| [write-oql-queries.md](write-oql-queries.md) | OQL query syntax | Creating VIEW entities |
| [create-page.md](create-page.md) | Page and widget syntax | Creating pages |
| [fragments.md](fragments.md) | Fragment (reusable widget group) syntax | Reusing widget patterns across pages |
| [scheduled-events-and-queues.md](scheduled-events-and-queues.md) | Scheduled event (cron) and task queue syntax | Running a microflow on a schedule; bounding background concurrency |

## Patterns (By Use Case)

Expand Down Expand Up @@ -92,6 +93,9 @@ Load skills based on the task:
| "Create export mapping" | `json-structures-and-mappings.md` |
| "Map JSON to entities" | `json-structures-and-mappings.md` |
| "Seed/populate test data" | `demo-data.md` |
| "Run a microflow nightly / hourly / on a schedule" | `scheduled-events-and-queues.md` |
| "Add a cron job / batch job / recurring task" | `scheduled-events-and-queues.md` |
| "Limit how many background tasks run at once" | `scheduled-events-and-queues.md` |
| "Update widget properties" | `bulk-widget-updates.md` |
| "Change widgets in bulk" | `bulk-widget-updates.md` |
| "Reuse widgets across pages" | `fragments.md` |
Expand Down
16 changes: 16 additions & 0 deletions .claude/skills/mendix/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,22 @@ create model Module.MyModel (
);
```

**`Provider` is free-form, and nothing validates it.** MDL passes the value
straight through; `MxCloudGenAI` is only the default applied when you omit the
property, not the only accepted value. `Provider: OpenAI` parses, writes, and
round-trips through `describe model` — as does `Provider: TotallyMadeUp`.

That means a typo is caught by **nothing** in the headless path. Agent Editor
documents are stored as custom blobs, and mxbuild contains no agent-editor
strings at all, so `mx check` and the build both stay green and the fault
surfaces only when Studio Pro opens the document.

mxcli deliberately does not enforce an allowlist, because the authoritative set
of provider values is not available to it: the agent editor is a Studio Pro
*extension*, its enum is not in `generated/metamodel` and not in mxbuild. Guessing
a list risks rejecting a value Mendix accepts. **Take the value from a model
document Studio Pro created**, rather than from memory or from this file.

### Knowledge Base

```sql
Expand Down
304 changes: 281 additions & 23 deletions .claude/skills/mendix/download-marketplace-content.md

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions .claude/skills/mendix/manage-navigation.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Use when the user asks to:
- **Home Page** — The default page shown after login. Can be a PAGE or MICROFLOW.
- **Role-Based Home Pages** — Override the default home page per user role (e.g., admins see a dashboard, users see a task list).
- **Menu Items** — Hierarchical menu tree. Each item has a caption and optionally targets a PAGE or MICROFLOW. Sub-menus nest with `menu 'caption' (...)`.
- **Menu Documents** — A *separate* document type (`Menus$MenuDocument`) holding a reusable menu that a menu widget points at, e.g. Atlas_Core's `Phone_Menu`. Not the same thing as a profile's menu, though both are built from the same items, so the item syntax is identical. Managed with `create/describe/drop menu` — see below.
- **Login Page** — Custom login page (optional; Mendix provides a default).
- **Not-Found Page** — Custom 404 page (optional).

Expand Down Expand Up @@ -244,6 +245,55 @@ create or replace navigation Responsive
);
```

## Menu Documents (standalone, reusable)

A profile menu lives *inside* a navigation profile and is edited with
`create or replace navigation`. A **menu document** is its own document, and a
menu widget on a page points at it. Atlas_Core ships `Phone_Menu` and
`Tablet_Menu`.

Tell them apart by which command reads them:

```sql
show navigation menu; -- the menu inside each profile
describe menu Atlas_Core.Phone_Menu; -- a standalone menu document
```

Menu documents use the same item syntax as the profile `menu (...)` block:

```sql
create or modify menu MyModule.Main_Menu (
menu item 'Home' page MyModule.Home_Web icon Atlas_Core.Atlas.home;
menu item 'Run' microflow MyModule.DoThing;
menu 'Admin' (
menu item 'Accounts' page Administration.Account_Overview;
);
menu item 'Plain';
);

drop menu MyModule.Main_Menu;
```

`describe menu` emits a re-executable `create or modify` statement, so
describe → edit → exec is the normal editing loop.

**`or modify` replaces the whole item list.** An omitted item is a removed item,
exactly as with `create or replace navigation`. The document's identity and
export level are preserved, so menu widgets pointing at it keep working.

### 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.
- **Authoring needs the default engine.** Under `MXCLI_ENGINE=legacy`,
create/modify/drop refuse rather than writing a differently-shaped document.

## Checklist

- [ ] Profile name matches an existing profile (Responsive, Phone, Tablet, or a native profile)
Expand All @@ -254,3 +304,5 @@ create or replace navigation Responsive
- [ ] `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
- [ ] Use `describe navigation` to verify changes after applying
- [ ] For a **menu document**, confirm you want `create menu` and not a profile menu — `show navigation menu` vs `describe menu` tells them apart
- [ ] No menu item targets a page with required parameters (CE1571)
208 changes: 208 additions & 0 deletions .claude/skills/mendix/scheduled-events-and-queues.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# Scheduled Events and Task Queues

## When to Use This Skill

Use this skill when the user wants to:
- Run a microflow on a schedule ("every night at 4", "hourly", "cron", "batch job")
- Inspect or change an existing scheduled event
- Limit how many background tasks run at once (a task queue)
- Understand why `mxcli` refuses to rewrite a microflow that has a queued call

**These two features are unrelated.** A scheduled event does **not** go through a
task queue. Its own concurrency control is `OnOverlap`.

## Scheduled Events

Mendix's cron: run a microflow on a repeating schedule.

```sql
-- Inspect
list scheduled events;
list scheduled events in Ops;
describe scheduled event Ops.NightlyCleanup; -- re-executable MDL

-- Create
create scheduled event Ops.NightlyCleanup (
Microflow: Ops.SE_Cleanup,
Repeat: Daily,
HourOfDay: 4,
MinuteOfHour: 0,
TimeZone: Server,
Enabled: true
);

drop scheduled event Ops.NightlyCleanup;
```

`Microflow` and `Repeat` are **always required**. `show` is a synonym for `list`.

### Pick the Repeat first, then use only its fields

Mendix stores the repeat rule as one of eight types, and they differ in **which
fields they carry** — not just in their values. Naming a field from another
repeat is an error, not a no-op:

```
Error: Repeat Daily does not have Multiplier — it takes HourOfDay, MinuteOfHour
```

| Repeat | Fields | Means |
|--------|--------|-------|
| `Minutely` | `Multiplier` | every N minutes |
| `Hourly` | `Multiplier`, `MinuteOffset` | every N hours, at :MM past |
| `Daily` | `HourOfDay`, `MinuteOfHour` | every day at HH:MM (**no multiplier**) |
| `Weekly` | `Weekdays`, `HourOfDay`, `MinuteOfHour` | on the named days at HH:MM |
| `MonthlyByDate` | `Multiplier`, `MonthOffset`, `DayOfMonth`, `HourOfDay`, `MinuteOfHour` | the Dth of every N months |
| `MonthlyByWeekday` | `Multiplier`, `MonthOffset`, `DaySelector`, `Weekday`, `HourOfDay`, `MinuteOfHour` | the last Friday of every N months |
| `YearlyByDate` | `Month`, `DayOfMonth`, `HourOfDay`, `MinuteOfHour` | every 2 January |
| `YearlyByWeekday` | `Month`, `DaySelector`, `Weekday`, `HourOfDay`, `MinuteOfHour` | the first Monday of March |

Field values:

| Field | Value |
|-------|-------|
| `Multiplier` | 1 or more (defaults to 1) |
| `MinuteOffset` | 0–59 |
| `MonthOffset` | 0-based: which month of a multi-month cycle fires |
| `HourOfDay` / `MinuteOfHour` | 0–23 / 0–59 |
| `DayOfMonth` / `Month` | 1–31 / 1–12 |
| `Weekdays` | quoted list: `'Monday, Friday'` (case-insensitive) |
| `DaySelector` | `First`, `Second`, `Third`, `Fourth`, `Last` |
| `Weekday` | `Sunday` … `Saturday` |

Optional on any repeat:

| Property | Values | Default |
|----------|--------|---------|
| `Enabled` | `true` / `false` | `false` — **a new event does not run until you enable it** |
| `OnOverlap` | `DelayNext` / `SkipNext` | `DelayNext` |
| `TimeZone` | `UTC` / `Server` | `UTC` |
| `StartDateTime` | RFC 3339, e.g. `'2026-01-01T04:00:00Z'` | none |
| `Documentation` | free text | none |

`SkipNext` drops a run that would overlap the previous one; `DelayNext` waits.

### More examples

```sql
-- Every two hours, 23 minutes past
create scheduled event Ops.HourlyPing (
Microflow: Ops.SE_Ping,
Repeat: Hourly,
Multiplier: 2,
MinuteOffset: 23
);

-- Mondays and Fridays at 09:30
create scheduled event Ops.WeeklyReport (
Microflow: Ops.SE_Report,
Repeat: Weekly,
Weekdays: 'Monday, Friday',
HourOfDay: 9,
MinuteOfHour: 30
);

-- The last Friday of every third month, 18:00
create scheduled event Ops.QuarterEnd (
Microflow: Ops.SE_Close,
Repeat: MonthlyByWeekday,
Multiplier: 3,
MonthOffset: 2,
DaySelector: Last,
Weekday: Friday,
HourOfDay: 18
);
```

## Task Queues

A task queue bounds how many queued microflow calls run at once.

```sql
list queues;
describe queue Ops.OrderProcessing;

create queue Ops.OrderProcessing ( Parallelism: 3, ClusterWide: true );
create queue Ops.Mail; -- defaults: parallelism 1, per-instance

create or modify queue Ops.OrderProcessing ( Parallelism: '$MyModule.Workers' );
drop queue Ops.Mail;
```

| Property | Meaning | Default |
|----------|---------|---------|
| `Parallelism` | how many run at once — an **expression**, not a number | `1` |
| `ClusterWide` | `true` = across the cluster, `false` = per runtime instance | `false` |

Mendix stores parallelism as an expression string, so `3` and `'3'` are the same
thing and an arbitrary expression is legal.

## Common Mistakes

| Mistake | Symptom | Fix |
|---------|---------|-----|
| `Multiplier` on a `Daily` repeat | `Repeat Daily does not have Multiplier` | Daily has no multiplier — use `HourOfDay`/`MinuteOfHour`, or switch to `Hourly` |
| Forgetting `Enabled: true` | The event is in the model but never runs | Set `Enabled: true` (the default is false) |
| `TimeZone: server` | `has the wrong casing — Mendix stores it as "Server"` | Use the exact spelling: `Server`, `UTC`, `DelayNext`, `SkipNext`, `Last`, `Friday` |
| `HourOfDay: 24` | `it must be between 0 and 23` | Hours are 0–23; midnight is `0` |
| Expecting a queue to throttle a scheduled event | Nothing changes | They are unrelated — use `OnOverlap` |

## Rewriting a Microflow with a Queued Call Is Refused

MDL cannot yet author a *queued call* — the binding lives on the call activity
inside a microflow, not on the queue. So `create or replace|modify microflow` is
refused when the stored microflow has one:

```
Error: microflow Ops.ACT_Caller has 1 call(s) bound to a task queue (Ops.MyQueue),
and rewriting it would silently drop that binding
```

This is deliberate. Change that microflow in Studio Pro, or remove the task queue
from the call first. Without the refusal the binding was written back as null and
the project then looked *healthier* than before — `mx check` stopped reporting
`CE1613 "The selected task queue no longer exists"`, because the configuration
the error was about had been deleted.

## Validation Checklist

Before presenting a script:

```bash
mxcli check script.mdl # catches wrong-repeat fields (MDL-SCHED01)
mxcli check script.mdl -p app.mpr --references
```

- [ ] Every scheduled event has `Microflow` and `Repeat`
- [ ] Only that repeat's fields are used
- [ ] `Enabled: true` if it is meant to run
- [ ] Enum values spelled exactly (`Server`, `DelayNext`, `Last`, `Monday`)
- [ ] The target microflow exists and takes no parameters

## Querying and Linting

Both document types are in the catalog after `refresh catalog`:

```sql
-- Anything that fires more often than once a minute
select QualifiedName, RepeatDescription, Microflow
from CATALOG.SCHEDULED_EVENTS
where Enabled = 1 and IntervalSeconds < 60;

select QualifiedName, Parallelism, ClusterWide from CATALOG.QUEUES;
```

A scheduled event counts as a caller of the microflow it runs, so
`show callers of Ops.SE_Cleanup` lists it and the lint rule for orphaned
microflows (QUAL004) does not flag it. `IntervalSeconds` is derived from the
schedule, not from the legacy `Interval`/`IntervalType` pair Mendix also stores.

Starlark lint rules can iterate both: `scheduled_events()` yields
`repeat`, `interval_seconds`, `on_overlap`, `time_zone`, `enabled`, and
`microflow_name`; `queues()` yields `parallelism` (a string) and `cluster_wide`.

## Related

- `mxcli syntax scheduled-event`, `mxcli syntax queue` — full syntax reference
- `write-microflows.md` — writing the microflow the event calls
- `project-settings.md` — after-startup / before-shutdown microflows
16 changes: 12 additions & 4 deletions .claude/skills/verify-in-runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,18 @@ whatever happens to be cached.
mxcli new PopupDemo --version 11.12.2 --output-dir /root/pd
```

> **Trap: `PathTooLongException`.** Mendix's package extractor fails on deep paths.
> A scratchpad path like
> `/tmp/claude-.../<uuid>/scratchpad/proj` is already too long and
> `mx create-project` dies. Use a short root (`/root/pd`). The same applies to Go
> **Trap: `PathTooLongException`.** MxToolset refuses any full path over **259
> characters** — its own Windows-compatibility limit, not the filesystem's — and
> aborts extraction part way through, leaving ~259 files and no `.mpr`. With the
> blank 11.13 template's longest relative path at 181 characters, the output
> directory gets **77**. A scratchpad path like
> `/tmp/claude-.../<uuid>/scratchpad/proj` blows that on its own.
>
> `mxcli new` handles this since #825 — it creates the project in a short staging
> directory and moves it into place, so any depth works and a failure never leaves
> partial output. It warns when the final path exceeds 259 that Studio Pro on
> Windows may not open the project. **Calling `mx create-project` directly still
> dies**, so keep using a short root (`/root/pd`) for that. The same applies to Go
> tests: set `TMPDIR=/root/t` so `t.TempDir()` stays short, or the scaffolding fails
> and the test **skips** — which is indistinguishable from passing.

Expand Down
3 changes: 3 additions & 0 deletions .claude/skills/version-awareness.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ Common version gates:
| REST query params | 11.0+ | Build query string manually in microflow |
| DB runtime connection | 11.0+ | Hardcode connection in Database Connector config |
| Design properties v3 | 11.0+ | Use Atlas v2 design properties |
| Agent Editor documents (MODEL, KNOWLEDGE BASE, CONSUMED MCP SERVICE, AGENT) | 11.9+, plus the AgentEditorCommons module | None — call an LLM from a microflow via the OpenAI Connector instead |

The executor will reject commands that target unavailable features with an actionable error — but checking upfront avoids wasted work.

The agent doctypes are the row where checking upfront matters most: their documents are stored as **custom blobs**, so mxbuild validates nothing about them. On a project below 11.9 the build stays green and the fault appears only when Studio Pro opens the document. Every other row in this table is caught by `mx check` sooner or later; that one is not.

## Upgrade Planning

When migrating to a newer version:
Expand Down
Loading
Loading