Skip to content

Commit f18c307

Browse files
akoclaude
andcommitted
docs(changelog): release v0.24.0
179 commits since v0.23.0, of which [Unreleased] covered seven entries. The storage-GUID cluster (five write paths that re-minted GUIDs, plus the guard that now refuses such a write), MOVE ENTITY and RENAME leaving a project unbuildable, four scripts that passed check and exec and failed the build, and nine new features had no entry anywhere. As at v0.23.0, entries can precede their commits here, so candidates were checked against the [0.23.0] section as well as [Unreleased] before being written. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent cb7cfa8 commit f18c307

1 file changed

Lines changed: 61 additions & 0 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,30 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

77
## [Unreleased]
88

9+
## [0.24.0] - 2026-09-24
10+
11+
Headline: **An element's storage GUID is the database's identity, and mxcli now treats it as one.** A production report of 28 attributes emptied across 607 rows by a single edit (mendixlabs/mxcli#1119) traced to five write paths that re-minted GUIDs — one of them moving 282 in a single module. They are fixed, and a new guard at the write choke point refuses any write that moves one: a class of data loss that leaves the model valid, `mx check` clean and `DESCRIBE` byte-identical, and surfaces only when the package meets a database that already holds data. Alongside it, `MOVE ENTITY` and `RENAME` stop leaving a project unbuildable, and four more scripts that passed every gate and failed the build are refused.
12+
13+
### Added
14+
15+
- **A write that moves an element's storage GUID is refused** — the guard behind the data-loss fixes below. Changing a `GUID` while keeping the `$ID` is data loss with nothing to see: the model stays valid, `mx check` passes, the build succeeds, and `DESCRIBE` is byte-identical because the reader never surfaces a GUID. It becomes visible only when the package meets a database that already holds data. None of the three guards already at the write choke point could catch it — no-op elision compares the document with itself, and the identity-decision test sees only the codec's `FreshGUIDFields`.
16+
17+
- **`mxcli run --local --page-check`** (mendixlabs/mxcli#614) — answers in text what a screenshot answers in pixels: did the page render, is there an error banner, did the grid get rows. A PNG costs ~1,500 tokens to read against ~100 for a verdict, and an image read into a conversation is **re-read by every later model call**, so one screenshot early in a long session is charged hundreds of times. It is also the weaker instrument for this question, measured against two real rendered pages.
18+
19+
- **`mxcli diag loop-report` — where a session's mxcli calls actually went.** Every invocation already wrote a `session_start` naming its argv (`mdl/diaglog`, wired at `newLoggedExecutor`, so it covers all commands rather than a curated subset) and nobody had ever read it. Reports per-command call counts, wall time and median, plus `check` immediately followed by `exec` of the same script. The instrument first, because every efficiency lever after it is a hypothesis until a number moves.
20+
21+
- **`ALTER PAGE … SET '<design property>' = <value> ON <widget>`** — a design property lives in `Appearance.DesignProperties`, so `SET` dead-ended on it and `ALTER STYLING` was a second statement for the same operation. Both spellings now work.
22+
23+
- **`ALTER PAGES [IN <module>] SET '<design property>' = … WHERE WIDGETTYPE = <keyword|'widget id'> [DRY RUN]`** — the bulk form. Applying a styling decision across an app ("every data grid gets Compact and Striped") previously meant one statement per widget per page, which is how a design system drifts. The selector resolves an MDL widget keyword to the widget id.
24+
25+
- **Workflow Groups are authorable** (mendixlabs/mxcli#272) — `alter settings workflows add [or modify] group 'Approvers' (Description: '…')`, plus `modify` and `remove`, and they appear in `DESCRIBE SETTINGS`. They are project settings, peers of `UserEntity` and `DefaultTaskParallelism`, and were missing from both — so a scripted project setup had to stop and open Studio Pro to define the buckets a user task's group targeting selects from.
26+
27+
- **A sort column can name the association it navigates** — `describe` emitted only the sort attribute's qualified name, so replaying it had to re-derive the hop, and where two associations reach the same entity there is nothing to derive it from. Measured on 11.12.3 with `Order_ShipTo` and `Order_BillTo` both `Order → Address`: a microflow sorting by the billing address came back through describe → exec sorting by the shipping one.
28+
29+
- **Two more Starlark surfaces for custom lint rules** — `rest_clients()` and `rest_operations()` builtins (the catalog built both tables, including `rest_operations.Timeout`, and neither was reachable — so "REST/web services should have a timeout" could not be written as a rule at all), and `service_ref` / `action_ref` on the activity, so a rule that finds a `RestCallAction` can now learn what it calls.
30+
31+
- **A run's repeated `Unchanged` reports collapse into one line.** Measured on a 40-statement script applied to a project already holding it: **41 lines / 1,604 B → 2 lines / 177 B**. Cheap in a terminal and charged repeatedly in an agent session, where a tool result is written into the conversation once and re-read by every later model call.
32+
933
### Changed
1034

1135
- **A data grid's column filters are documented where a data grid is documented** — the filter widgets section of the `create-page` widget reference opened with "Filter widgets are used inside GALLERY FILTER containers", so a reader building a grid found the widgets only by reading a gallery section to the end. It now names both homes up front, with the grid form first. The `overview-pages` skill gains the two forms its type table did not cover: a `dropdownfilter` over an association (`Association:` + `datasource:` + `CaptionAttribute:`, verified `mx check` 0 errors and round-tripped through `describe`), `FilterType: between` for a date range, and the reminder that a grid filters itself — two generated apps built a filter bar over a non-persistent helper entity instead, one of them costing three microflows and 1,100 characters of XPath where five column filters would do.
@@ -35,6 +59,43 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
3559

3660
Two holes, and fixing either alone leaves the reported script unchecked. The image reference was collected by **widget type** (`if w.Type == "image"`), so the pluggable widget was resolved and the two widgets #1057 gave the same property — `staticimage`'s `Image` and `dynamicimage`'s `DefaultImage` — were not; it is a table now, so adding a widget that names an image means adding a row. And a page's widgets live in **two** AST fields: `Widgets` is the bare body, while `placeholder <Name> { … }` content is held apart in `Placeholders` (#532). All three page validators walked the first alone, so **every** reference inside a placeholder block — microflow, nanoflow, page, snippet, entity, image — was validated by nothing. Measured, the same button in the two positions: inside `placeholder Main` → `✓ All references valid`; in the bare body → `microflow not found`. That is the shape mxcli's own skills, examples and DESCRIBE output write, so it was the common case rather than an edge one, and it is the third copy of one walk — `validateIconRefs` (#1008) and `forEachWidget` had each grown the placeholder arm separately — so the roots are now collected once.
3761

62+
- **Storage GUIDs were re-minted by ordinary edits, emptying columns on the next deploy** (mendixlabs/mxcli#1119). The runtime keys `mendixsystem$entity.id` and `mendixsystem$attribute.id` on the model's `GUID`, so moving one makes the synchroniser treat the element as deleted-and-re-added: it drops and recreates the column and loses its values. Reported from production — **28 attributes of 607 rows emptied by one edit**. Five write paths did it, each silent:
63+
64+
- **Every `ALTER` of an existing entity** reset each attribute's GUID to that attribute's own `$ID`. `UpdateEntity` rebuilds the target with `entityToGen`, so every attribute and index arrived `raw == nil` and the codec's `EmitGUID` default wrote `GUID = $ID`. This is the unclosed half of #657, which carried the entity's own raw and left its children rebuilt.
65+
- **A whole-unit rewrite moved every GUID in the unit**, including elements the statement never named — measured at **282 in one module** (37 entities, 224 attributes, 21 associations), with a runtime crash (`Cannot invoke "...Table.getTableName()" because "table" is null`). `UpdateDomainModel`'s doc comment claimed it preserved "each element's identity", which was true of the `$ID` it re-sets and false of the GUID the database is keyed on.
66+
- **`CREATE OR MODIFY ENTITY` re-minted every attribute GUID**, because the statement's declared members are built from text and carry no element ID; `carryChildIdentity` read the empty ID as "a genuinely new member, so a fresh GUID is right".
67+
- **`MOVE ENTITY` re-minted the entity's GUID, every attribute's, and that of any association the move converted to a cross-association.** Since #1119 the third also made `MOVE ENTITY` **fail outright** for any entity in an association — most real entities.
68+
- **The new guard itself was wrong twice before it was right**, and both are worth knowing. It first paired elements on `$ID` alone, trusting `TransplantIDs` — but that pairs *structurally*, with a deliberately low correctness bar (a wrong `$ID` match only makes a diff bigger), and feeding that to a guard turns every wrong match into a refused write. Pairing then required an equal `$Type`, which went blind to the one conversion mxcli performs in place: `MOVE ENTITY` re-types a `DomainModels$Association` as a `DomainModels$CrossAssociation` under the same `$ID` and `Name`.
69+
70+
- **`MOVE ENTITY` and `RENAME` reported success and left the project unbuildable.**
71+
72+
- **A cross-module `MOVE ENTITY` left 33 `CE1613`s in a blank 11.13 app** — every one a reference still naming the source module: the entity (13), its attribute paths (11), the converted association (9), across microflow activities, page widgets, page parameters and access rules. Nothing warned. Both directions are 33 → 0 now.
73+
- **`RENAME ENTITY` / `RENAME ASSOCIATION` clobbered the reference sweep they had just run**, reporting an accurate count and then leaving the renamed element's *own* member references stale — `CE1613` at build time, naming an access rule of the entity that had just been renamed.
74+
75+
- **`mxcli check` and `exec` accepted scripts that could not build.**
76+
77+
- **A file that parsed to nothing but is not empty passed** (mendixlabs/mxcli#618) — `this is not valid mdl at all;` gave `Check passed!` and exit 0, and `exec` applied nothing and exited 0.
78+
- **`exec` skipped the reference-resolution pass that `check -p` runs** (mendixlabs/mxcli#607), so a dangling name reached the project. `exec` already connects before its preflight, so it had everything the second pass needs.
79+
- **`currentDeviceType()` in a nanoflow passed both and failed with CE0117** (mendixlabs/mxcli#1033) — MDL044 was wired to `CREATE MICROFLOW` only, and `log` message/node/template expressions were never checked in either flow type, which is exactly where the reported repro put the call.
80+
- **A script that silently removes a member is now reported (MDL087)** (ako/mxcli#562) — `create or modify entity` rebuilds the entity from the statement, so an attribute a *later* script added is deleted when the earlier one is re-run alone. The reporter's loss surfaced two slices later as CE1613 on a page: an error naming the page, never the script.
81+
- **A gallery's `template` / `filter` block was read as a CE0495 duplicate name** when it is a slot.
82+
83+
- **Workflow clauses that stored nothing.** `create workflow … overview page X` reported `Created workflow`, exit 0, and stored **no** page reference — not even the qualified name as a string (ako/mxcli#586). `mx check` passed, because a workflow with no overview page is valid, and `describe workflow` omitted the clause, so nothing revealed the loss: the executor set `Workflow.OverviewPage` and the backend only ever wrote `Workflow.AdminPage`. `CREATE WORKFLOW` also now accepts its clauses in any order.
84+
85+
- **`UPDATE WIDGETS` reported success after writing nothing** — it counted widgets it *found*, not assignments that *succeeded*, so a run where every assignment was refused claimed success immediately after warning about each refusal.
86+
87+
- **A view entity whose OQL alone changed reported `Unchanged`** while `describe entity` showed the new query stored. Changing the attribute list as well reported `Modified` correctly, which is why it hid: the OQL lives in a separate `DomainModels$ViewEntitySourceDocument`, which was replaced rather than written in place.
88+
89+
- **Page and widget writes that lost what they were given** — an action button's `captionparams` binding a bare attribute, an entity-type JS action parameter needing `EntityTypeCodeActionParameterValue`, entity-type code-action arguments written on their own line, `SET` addressing a pluggable widget's named action slot, a primitive snippet parameter resolved as an entity instead of refused, the widgets inside a `placeholder` block going unvalidated, a pluggable widget's `textTemplate` binding to an attribute (#575), and `describe` dropping a java action's type-parameter names (#1034) and leaving `all` off an import mapping that returns one object.
90+
91+
- **Layout and unit-rewrite corrections** — a `while` loop's first activity sat outside its own box; a layout rewrite replaced the stored unit instead of rewriting it; a unit re-inserted under the `$ID` of one just deleted was not reconciled; `create or modify entity` dropped validation rules it cannot spell rather than keeping them; and a view entity's pass-through length is no longer judged against a limit mxcli does not know.
92+
93+
- **`mxcli diag` under-recorded its own runs** — it logged the minority of invocations that happened to log, missed runs that failed argument validation, and attributed mxcli's own child processes to the wrong run. Since `loop-report` reads that log, every number it prints depended on these.
94+
95+
- **Documentation that named values nothing returns** — the MPR reference pages' "Unit Types" tables mapped BSON `$Type` to document kind with **15 wrong rows**: four gave the TypeScript SDK's qualified name (`Pages$Page` where every real unit says `Forms$*`) and eleven were lowercased. `$Type` is case-sensitive, so selecting on either spelling matches zero units — a wrong answer rather than an error. Also corrected: MPR v1 has no `UnitContents` table and detection is by directory, and the System module's measured String lengths (#584, #587).
96+
97+
- **CONV010 flagged the only delegation an `ACT_` nanoflow has**, and session-authenticated OData needs an `X-Csrf-Token` the vega skill pack said it did not.
98+
3899
## [0.23.0] - 2026-09-21
39100

40101
Headline: **The last workflow constructs only Studio Pro could author become MDL, `describe` → `exec` stops losing what it just read, and the legacy engine is gone.** Workflow event sub-processes, notification events, multi-user completion rules and AI agent tasks are authorable; a wave of page round-trip fixes closes the gap between what mxcli reads and what it writes back — a password field that came back as a plaintext text box is the one to read first; and `sdk/mpr` is deleted, leaving one engine behind the backend abstraction.

0 commit comments

Comments
 (0)