Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
dbe5cc2
fix(check): report project conflicts for every document type exec ref…
claude Sep 7, 2026
037b7aa
fix(lint): CONV010 allows the merge an `if` cannot avoid creating
claude Sep 7, 2026
02bead4
feat(catalog): count a project setting as a reference to the microflo…
claude Sep 7, 2026
1e5807d
fix(check): resolve a keyword's design properties against the widget …
claude Sep 7, 2026
a29d0cf
fix(snippets): unquote a quoted entity name in a snippet parameter
claude Sep 7, 2026
450e90e
fix(modelsdk): carry a page's and snippet's documentation on read
claude Sep 7, 2026
6fb01d3
feat(security): DROP USER ROLE / DROP DEMO USER ... IF EXISTS
claude Sep 7, 2026
d3d35a5
feat(check): MDL078 validates an `icon glyph` code against the font
claude Sep 7, 2026
6c39b98
docs(findings): record the seven CapTrackV4 fixes
claude Sep 7, 2026
f90c13f
feat(check): SHOW GLYPHS and DESCRIBE GLYPH
claude Sep 7, 2026
3e9b5d5
fix(check): resolve an enumeration inside a folder
claude Sep 8, 2026
cd9713a
Merge pull request #411 from ako/claude/mxbuild-diagnostics-spike-emta6h
ako Sep 8, 2026
855a15e
Merge remote-tracking branch 'origin/main' into claude/mxcli-unit-tes…
claude Sep 8, 2026
578603a
docs(proposal): pin offline sync against ako/TestApp, closing all fou…
ako Sep 8, 2026
e1e6404
Merge pull request #412 from ako/claude/mxcli-unit-test-perf-n7ggx8
ako Sep 8, 2026
ae89776
fix(odata): carry a parameter's nullability onto the call (mendixlabs…
claude Sep 8, 2026
c97e841
feat(navigation): carry an offline entity config's stored properties …
ako Sep 8, 2026
c9cabc4
fix(pages): resolve a pluggable widget property by name, not by casing
claude Sep 8, 2026
b9bbd6c
fix(workflow): refuse an unqualified decision outcome, and stop crash…
ako Sep 8, 2026
6573bc8
Merge remote-tracking branch 'origin/main' into fix/1031-workflow-out…
ako Sep 8, 2026
cb8dbf4
feat(navigation): author offline synchronization (SYNC block)
ako Sep 8, 2026
6b1156b
fix(navigation): take XPath first-class in SYNC, and stop folding ins…
ako Sep 8, 2026
a1e3f92
docs(proposal): first-class expressions for expression-typed MDL prop…
ako Sep 8, 2026
d670f87
Merge pull request #413 from ako/docs/offline-sync-reference-measured
ako Sep 8, 2026
da277e0
Merge pull request #414 from ako/claude/mxbuild-diagnostics-spike-emta6h
ako Sep 8, 2026
ba745d4
Merge pull request #416 from ako/claude/mxcli-unit-test-perf-n7ggx8
ako Sep 8, 2026
e1721c5
Merge origin/main into feat/offline-sync-carry-read
ako Sep 8, 2026
7de53a2
Merge phase 1 (#418) into phase 2
ako Sep 8, 2026
25aef5e
Merge pull request #418 from ako/feat/offline-sync-carry-read
ako Sep 8, 2026
c648359
Merge pull request #419 from ako/fix/1031-workflow-outcome-and-crash
ako Sep 8, 2026
980b7ff
test(doctype): skip the offline-sync example on legacy, which cannot …
ako Sep 8, 2026
716b31d
Merge remote-tracking branch 'origin/main' into claude/banking-app-fe…
claude Sep 8, 2026
2d91654
docs(navigation): document offline sync where users actually look
ako Sep 8, 2026
545025d
Bring offline-sync authoring onto main
ako Sep 8, 2026
bc0bf02
Merge pull request #421 from ako/docs/proposal-first-class-expressions
ako Sep 8, 2026
daae912
Merge pull request #422 from ako/claude/banking-app-feedback-do04dy
ako Sep 8, 2026
4e79f37
Trim the generated CLAUDE.md from ~6,900 to ~1,000 tokens
claude Sep 8, 2026
9cf6e76
Stop a concurrent capture being lost by a brain promote
claude Sep 8, 2026
ef590fe
Make the brain commands machine-readable
claude Sep 8, 2026
c64a6a9
Let a dispatcher ask what one slice staged
claude Sep 8, 2026
9f84339
Add `brain brief`: the reading pack the sharding was designed for
claude Sep 8, 2026
b049f45
Keep brain anchors valid when `mxcli rename` moves the model
claude Sep 8, 2026
6dddd6b
fix(workflow): auto-wire a nested call-microflow activity (#417)
ako Sep 8, 2026
e9fbd1c
Merge remote-tracking branch 'origin/main' into claude/vigilant-barti…
ako Sep 8, 2026
d4ee4db
Preserve YAML frontmatter when a brain shard is rewritten
claude Sep 8, 2026
2cbf765
Merge pull request #423 from ako/feat/offline-sync-authoring-v2
ako Sep 8, 2026
8e8ee2d
Merge pull request #425 from ako/claude/vigilant-bartik-704080
ako Sep 8, 2026
104c74d
Merge pull request #424 from ako/claude/project-brain-multi-agent-oipjtv
ako Sep 8, 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
8 changes: 8 additions & 0 deletions .claude/lint-rules/conv010_act_microflow_content.star
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,20 @@ ALLOWED_ACTIONS = (
)

# Allowed activity types (non-action activities)
#
# ExclusiveMerge is here because an `if` produces BOTH a split and a merge. The
# list allowed the split and forbade the join it necessarily creates, so an ACT_
# microflow that guards anything — "do not open a page with an empty parameter" —
# could not be written cleanly: the guard was permitted and its own closing brace
# was reported. Measured on a microflow whose ONLY violation was the merge, and
# 122 times over on one real project.
ALLOWED_ACTIVITY_TYPES = (
"SubMicroflow",
"MicroflowCallAction",
"StartEvent",
"EndEvent",
"ExclusiveSplit",
"ExclusiveMerge",
"Annotation",
)

Expand Down
7 changes: 1 addition & 6 deletions .claude/lint-rules/missing_documentation.star
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# covers every document type a user authors, not just the domain model.
#
# Documents swept generically (one option each, all default True):
# Module, Entity, Page, Snippet, BuildingBlock, Layout, Enumeration,
# Entity, Page, Snippet, BuildingBlock, Layout, Enumeration,
# JavaScriptAction, ImageCollection, DataTransformer, Workflow,
# BusinessEventService, RestClient, PublishedRestService, Constant,
# JsonStructure, ImportMapping, ExportMapping
Expand Down Expand Up @@ -41,11 +41,6 @@ SEVERITY = "info"
# A new Mendix document type is covered by adding a row in Go's
# documentableSources and a row here — not by writing another loop.
_DOC_KINDS = {
"Module": (
"check_modules",
"Module",
"Document what the module is for: it is the first thing a newcomer opens.",
),
"Entity": (
"check_entities",
"Entity",
Expand Down
11 changes: 9 additions & 2 deletions .claude/lint-rules/orphaned_elements.star
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ 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"]
MICROFLOW_ENTRY_KINDS = ["call", "schedule", "datasource", "action", "calculate", "settings"]

# Reference kinds that mean "something opens this page".
PAGE_ENTRY_KINDS = ["show_page", "home_page", "login_page", "menu_item", "action"]
Expand Down Expand Up @@ -70,9 +70,16 @@ def check():
# datasource a page or widget uses it as a data source
# action a widget button calls it
# calculate a calculated attribute computes with it
# settings a project setting names it (after-startup, before-shutdown,
# health-check) — the RUNTIME calls it, and nothing in the
# model does
#
# The banking-app report hit the 'datasource' case: DS_CurrentCustomer and
# DS_MyAccounts are both page data sources and both were flagged.
# DS_MyAccounts are both page data sources and both were flagged. The
# CapTrackV4 report hit 'settings': the project's own AfterStartupMicroflow
# was reported as "not called from anywhere. Remove if unused", and taking
# that advice left a dangling name mx check did not catch either — only the
# runtime refused to start.
has_callers = False
for ref in refs:
if ref.ref_kind in MICROFLOW_ENTRY_KINDS:
Expand Down
3 changes: 3 additions & 0 deletions .claude/skills/fix-issue/findings/mdl-backend.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,6 @@
{"area": "mdl/backend", "date": "2026-08-27", "symptom": "A rebuilt mapping drops `MessageDefinition2`, so a document written by Studio Pro 11.10+ never round-trips", "cause": "The key is version-introduced — `modelsdk/gen/mappings/version.go` records `messageDefinition2` as `Introduced: \"11.10.0\"` — and gen generates **no accessor** for it, so nothing read or wrote it. A blank 11.13 app's own mappings carry it as `\"\"`; none of the older pinned fixtures has it at all", "file": "`model.ImportMapping`/`ExportMapping` (`MessageDefinition2 *string`), `mdl/backend/modelsdk/mapping_read.go` (`messageDefinition2FromRaw`) + `mapping_write.go`, `sdk/mpr/parser_*_mapping.go` + `writer_*_mapping.go`, `mdl/executor/cmd_import_mappings.go` + `cmd_export_mappings.go`", "insight": "**Carry it, do not derive it.** A pointer, because nil (absent) is NOT the same as present-and-empty — writing the key onto a pre-11.10 document is the overlay-rule mistake CLAUDE.md warns about, which mxbuild tolerates and Studio Pro refuses to open. The executor decides: carry `existing.MessageDefinition2` on an update, apply the version gate only on a CREATE where there is nothing to read it off. A plain version gate in the writer looks equivalent and is not — it re-adds the key to every older document a rewrite touches, which turned four previously-clean fixtures red. gen having no accessor means the read goes to raw BSON, the route `parameterEntityFromRaw` takes. **`MappingSourceReference` is the same family and deliberately NOT carried**: its gate (10.16) predates every project in the field, and the codec emits it through a package-level `TypeDefaults` registration, so making it conditional would touch every mapping write to preserve one pre-10.16 fixture. Issue ako/mxcli#279", "refs": ["ako/mxcli#279"]}
{"area": "mdl/backend", "cause": "The **reader**, not the writer: `mdl/backend/modelsdk/navigation_read.go` type-asserted only the `$Type`s `modelsdk/gen` declares for those two slots, and neither is what the documents carry \u2014 `LoginPageSettings` is stored as `Forms$FormSettings` with the page under `Form` (gen expects `Navigation$NavigationProfileLoginFormSettings` / `LoginPage`), and `NotFoundHomepage` as `Navigation$HomePage` (gen and `generated/metamodel` both expect `Navigation$NotFoundHomePage`). A failed type assertion leaves the field empty, so the loss is silent", "date": "2026-09-01", "file": "`mdl/backend/modelsdk/navigation_read.go` (`navLoginPageOf`, `navNotFoundPageOf`), cross-check `generated/metamodel/types.go` `NavigationNavigationProfile`", "insight": "**The other engine is the control.** Legacy read the same bytes correctly throughout, which is what identifies a reader bug: `describe navigation X` on both engines must agree, and a disagreement localises the defect to the one that reads through gen. Accept the `$Type` the documents actually carry and keep gen's as a fallback branch. Note the two slots fail in **opposite directions** and want opposite fixes: for the login page a real Studio Pro document and `generated/metamodel` agree with the writers, so **gen** is wrong; for the not-found page \u2014 Studio Pro's **\"Fallback page\"** \u2014 metamodel and gen agree with each other and the three mxcli **writers** are the odd one out, emitting `Navigation$HomePage` where Studio Pro stores `Navigation$NotFoundHomePage`. ako/TestApp supplied the reference document that settled it. **Correction (2026-09-01): mxbuild does NOT accept either.** Measured on 11.13 against a build emitting the old spelling, `mx check` and `mxbuild --target=deploy` both exit 1 with \"Object of type 'Mendix.Modeler.WebUI.Navigation.HomePage' cannot be converted to type '...NotFoundHomePage'\" -- the project will not LOAD, so every downstream check is lost. What actually let it through is that nothing ever BUILT a project with a fallback page set: the automated mx-check coverage is doctype-tests/ only and no script there sets one, so the first was added by the fix itself. **Generalisable: 'the build tolerates it' is a claim that needs the same control as the fix** -- revert the writer, rebuild, and run the tool, or the reason a bug escaped gets recorded backwards and sends the next reader looking in the wrong place. Keep reading both `$Type`s regardless, but for the repair path rather than round-tripping: a pre-fix project does not build, and mxcli reads BSON directly, so accepting the old spelling is what lets it open and fix one. Repro `mdl-examples/bug-tests/navigation-describe-profile-pages.mdl`", "symptom": "`DESCRIBE NAVIGATION` prints `home page` and the menu but **silently omits `login page` and `not found page`**, so pasting its output back (the documented copy workflow) deletes both from the profile. The clauses are on disk and `MXCLI_ENGINE=legacy` prints them"}
{"area": "mdl/backend", "date": "2026-09-07", "symptom": "CE0066 \"Entity access is out of date\" after `ALTER ENTITY M.Gen ADD ATTRIBUTE ...` where a SPECIALISATION of Gen also has an access rule — and `UPDATE SECURITY`, project-wide or scoped, reported \"All entity access rules are up to date\" and changed nothing (mendixlabs/mxcli#1047, reported against 0.21.0 on MPR v2, reproduced on 11.12.1 on BOTH engines). The reporter's own tool named the one missing member — `+ Spec: ProbeSecond.Gen.AfterSpec (ReadWrite)` — and took mx check 1 -> 0.", "cause": "ReconcileMemberAccesses computes the same-module ancestor set (sameModuleAncestors -> ownerIDs) and then uses it ONLY for the association pass. The attribute pass walked the entity's OWN attributes (ent.AttributesItems() / entityDoc[\"Attributes\"]), so a specialisation's expected member set never contained an inherited attribute: nothing looked missing, nothing was added, and `modified` stayed 0 — which the command prints as \"up to date\". Fixed by walking the chain for attributes too, qualifying each against the entity that DECLARES it, with a nearer entity's attribute shadowing an ancestor's of the same name.", "file": "`mdl/backend/modelsdk/domainmodel_security_write.go` (ReconcileMemberAccesses, collectAttrs) and `sdk/mpr/writer_security.go` (entityAttrsInChain, ownAttrsOf, generalizationRefOf); tests `mdl/backend/modelsdk/reconcile_inherited_attr_test.go`, `sdk/mpr/writer_security_inherited_test.go`; example `mdl-examples/bug-tests/1047-inherited-member-reconcile.mdl`", "insight": "The bug was one line of REUSE that never happened: the ancestor set was already computed two lines above the attribute loop and only the association loop consumed it. When a function computes a set and uses it for one of two symmetric passes, check the other pass. Three things worth carrying: (1) A count that drives a message is part of the contract — reporting 0 modified is what turned a broken model into the words \"All entity access rules are up to date\", and a false success is worse than an error because it ends the investigation. Assert the count, not just the stored state. (2) Legacy keyed coverage by BARE attribute name while preserving any ref not qualified against this entity, so naively adding inherited attributes would have preserved AND re-added the same member — the duplicate only shows on the second reconcile, so the regression test runs it twice. (3) The repair path cannot be proven by an .mdl file once the write path is fixed: ALTER ENTITY reconciles as it writes, so the script ends clean either way. Break the project with a PRE-FIX binary and repair it with the fixed one — that finally showed `update security` taking a real CE0066 from 1 to 0, which an earlier fix had recorded as unproven.", "ce": ["CE0066"]}
{"area": "mdl/backend", "date": "2026-09-08", "symptom": "A property Studio Pro writes on an offline entity config (CompatibilityMode) was read from the model and silently discarded, so any future write path would have dropped it with no error and no mx check failure", "cause": "types.NavOfflineEntity carried three of the four properties Studio Pro actually writes. TestFieldCountDrift, which exists to catch exactly this on hand-copied structs, did not list NavOfflineEntity or NavigationProfile — so adding the field left the guard passing vacuously", "file": "`mdl/types/navigation.go`, `mdl/backend/mpr/convert.go`, `mdl/backend/mpr/convert_roundtrip_test.go`", "insight": "A drift guard is only worth what its list covers, and a guard that passes on a struct it does not know about is worse than none — it reads as coverage. Check the guard names your type before trusting a green run. Also: measure which properties are actually WRITTEN before deciding what to carry — gen declared six here and the reference document had four, with DownloadMode and ShouldDownload occurring zero times, so the risk was inverted from the expected one (writing a property Studio Pro fills in on load, not dropping one)", "refs": ["ako/TestApp", "ako/mxcli#413"]}
{"area": "mdl/backend", "date": "2026-09-08", "symptom": "An offline navigation profile authored by mxcli builds, routes and installs as a PWA, and shows an empty app — every gate green", "cause": "MDL had no syntax for offline synchronization, so a created offline profile got an empty OfflineEntityConfigs list. A Mendix offline profile downloads nothing until each entity has a sync mode; `mx check` reports 0 errors either way because an empty list is valid", "file": "`mdl/grammar/MDLParser.g4` (navSyncDef), `mdl/backend/modelsdk/navigation_write.go`, `sdk/mpr/writer_navigation.go`", "insight": "Creating a document kind is not the same as being able to configure it, and the gap is invisible to every static check — the symptom is an empty screen at runtime. When adding a profile/document kind, ask what makes it DO anything, not just what makes it exist. The write is an overlay keyed by entity so CompatibilityMode (stored, unauthorable) survives; building the element from the spec alone would clear it silently, the access-rule defect again", "refs": ["ako/TestApp", "PROPOSAL_offline_sync_configuration.md"]}
{"area": "mdl/backend", "date": "2026-09-08", "symptom": "`alter page P { set PageSize = 10 on <datagrid2> }` errors `pluggable property \"PageSize\" not found` on a grid that `create page \u2026 (PageSize: 20)` had just written and the app really pages at. `mxcli check --references` passes the script, so it fails only at exec, after earlier statements have landed; DESCRIBE PAGE prints the same capitalised `PageSize:`, so describe \u2192 edit \u2192 exec produced a script mxcli refused to run", "cause": "A pluggable property key is lowerCamel in the widget template (`pageSize`). CREATE resolves the author's spelling case-INsensitively (widget engine `lookupProperty`, and `WidgetV3.GetStringProp` before it); ALTER went through `setPluggableWidgetPropertyMut`, which compared the template key byte-for-byte, so only the exact `pageSize` worked. Direct sequel to Findings #1 (2026-07-27), which fixed the same class for FIRST-CLASS props and deliberately left the pluggable fallback case-sensitive with the comment 'template keys must match the template exactly'", "file": "`mdl/backend/pagemutator/mutator.go` (`setPluggableWidgetPropertyMut`)", "insight": "`strings.EqualFold` against the widget's own PropertyTypes. **The disproven belief is the reusable part**: keys are STORED case-sensitively, which is not a reason to MATCH them that way \u2014 the resolver searches one object type's PropertyTypes, and across every shipped template and definition that scope holds no two keys differing only in case (96 scopes, 1208 keys, 0 collisions). Measure the ambiguity before assuming it; here there was none, and the assumption cost a whole verb. **Cheapest localiser**: run the failing statement with the template's own casing \u2014 `set pageSize` succeeded where `set PageSize` failed, in ONE measurement, on the same widget in the same project. Both engines share `pagemutator`, so an engine split says nothing here (verified: modelsdk and legacy both fixed by the one change). Tests `TestSetPluggableProperty_MatchesTemplateKeyRegardlessOfCase` (+ typo-still-errors control, + `TestPluggablePropertyKeysAreUniqueIgnoringCase` pinning the no-collision argument); repro `mdl-examples/bug-tests/alter-page-pluggable-property-casing.mdl`; verified 0 errors on `mx check` 11.13.0. Two reporter claims did NOT hold: `describe page` DOES emit PageSize, but only when it differs from the widget default 20 (deliberate, so describe round-trips) \u2014 at the default it is omitted, which reads as 'describe cannot show it'. Still open and separate: `check --references` does not resolve pluggable property names at all, so a genuine typo (`PagSize`) still checks clean and fails at exec", "refs": ["mendixlabs/mxcli#1069"]}
Loading
Loading