Sync ako/main → upstream: MDL safety, fragments/building blocks, typed design properties, warm-loop debugging#785
Merged
Merged
Conversation
Record the multi-project-solution orchestration as slice 5: several apps of one solution running side by side in a single container, each its own runtime + database with auto-allocated ports, all registered under one --hub-solution and grouped in the hub overview, with sibling URLs wired from the hub-assigned subdomains via a mxcli.solution.yaml manifest driven by `mxcli run --solution`. It's an orchestration layer over shipped primitives (per-project DB/deploy isolation, port-triple flags, the slice-4 hub's solution grouping) — no new runtime or hub mechanics. Also marks slices 3 & 4 verified-live and merged (PR #11). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The warm loop's three long-lived children — mxbuild --serve, the runtime JVM, and the rollup web-client bundler — were each tracked by a single PID, and every Stop() signalled only that direct child. Because mxbuild is a shell-script wrapper that spawns a Temurin JVM grandchild, SIGTERM to the wrapper left the JVM orphaned, still holding its port (6543/8080/8090). The next `mxcli run --local` then refused to start with "port already in use". Start each child in its own process group (Setpgid) and have Stop() signal the whole group (negative-PID SIGTERM, then SIGKILL after the grace period), so wrapper grandchildren are reaped too. Cross-platform via a Windows no-op stub (no POSIX process groups there; the warm loop is a Linux-devcontainer feature). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Under `run --local --watch`, a structural model change (new/removed page, nav or home change) went through a runtime restart, but the app then 404'd on /dist/index.js and booted only the <noscript> shell until a clean full restart. Cause: the serve Deploy build rewrites web/ source and clears web/dist, while the incremental rollup bundler can report a "success" for an intermediate bundle that does not match the final source. The watch-apply loop reported "build applied" off that in-process signal with no check that the bundle was actually served. Gate the "applied" report on the bundle being present on disk AND returning 200 from the app. When it isn't, recover with the reliable synchronous one-shot BuildWebClient (the same path the non-watch boot uses) and re-probe before reporting success. A pure model reload never touches web/dist, so it passes the probe instantly — no cost on the common path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
docs(warm-loop): add slice 5 — multi-project solutions in one container
Land the atlas-design proposal — a taste + workflow skill layer that makes mxcli-generated Mendix apps look designed by default (the Atlas-first method, 4-layer token architecture, recipe library, chart/dark-mode widget recipes, a gotchas catalog, and a mandatory runtime-verify loop). It sits on top of the page-styling, page-composition, building-blocks, and page-templates proposals and consumes what they build rather than duplicating them. Drafted from the Itinera / Atlas-MES build (test app ako/MxcliTest1). Two corrections vs the original draft, verified against the codebase: - all chart types (incl. Line/Bubble/Heatmap/TimeSeries) are MDL-authorable today; the "stick to the authorable four" note was stale. - the two P0 warm-loop tooling bugs it listed are now fixed (PR #13), so those rows are marked done. Regenerates the proposals README index. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Granting a page/microflow/nanoflow/published OData|REST service access to a module role from a DIFFERENT module than the document passed `mxcli check` and `exec`, then failed the Mendix build with an opaque CE0148 "reselect roles". Document access (AllowedModuleRoles) may only reference the document's OWN module roles — Studio Pro's role picker only offers those — so a cross-module reference is invalid even though it is well-formed. The grant handlers wrote `role.Module + "." + role.Name` verbatim, guarded only by validateModuleRole (which checks the role exists in ITS module, not that it matches the document's module). The MOVE path already enforced the same-module rule by remapping (remapDocumentAccessRoles); GRANT had no equivalent. Add checkDocumentAccessRolesSameModule and wire it into all five grant handlers (page, microflow, nanoflow, OData service, published REST service). It rejects a cross-module role with an actionable message that names the document's module and suggests the own-module role — turning an opaque deploy-time failure into an immediate, clear error. Reject (not silently remap): a GRANT is explicit, so a wrong role or wrong document must not be substituted. Tests: the pure guard (both branches) plus page/microflow/nanoflow handler tests via MockBackend (cross-module rejected, no write; same-module still works). Adds mdl-examples/bug-tests/ce0148-cross-module-grant.mdl and a fix-issue.md symptom-table entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Add read-only discovery for Mendix Building Blocks (Pages$BuildingBlock), mirroring the SHOW/DESCRIBE SNIPPET path. Building Blocks are the native "reusable widget composition" library; this surfaces them so a project's existing blocks can be discovered and inspected (the READ prerequisite for the Atlas design-system recipe work — INSTANTIATE/AUTHOR remain separate). - SHOW BUILDING BLOCKS [in module] — lists Qualified Name, Module, Name, Display Name, Platform, Category. - DESCRIBE BUILDING BLOCK Module.Name — informational output (doc block + metadata comments + widget tree). Read-only: not a re-executable CREATE, since Building Blocks can't be authored via MDL. Data layer: the storage $Type is Pages$BuildingBlock (per the generated metamodel), not Forms$BuildingBlock as the reader previously assumed — so ListBuildingBlocks now does a dual-type lookup (Pages$ first, Forms$ fallback), fixing a latent empty-result bug. parseBuildingBlock reads DisplayName / Platform / TemplateCategory scalars. The widget tree is read from the raw unit (GetRawUnit) as a top-level Widgets[] array — the same shape and reader logic as a Studio-Pro snippet — not from the typed parser. Grammar: new BUILDING / BLOCK / BLOCKS lexer tokens (added to the keyword rule so they remain usable as identifiers), plus showStatement / describeStatement alternatives. Wired through AST, visitor, executor dispatch, and the CLI describe registration (both $Type strings). Backend interface/impls unchanged (ListBuildingBlocks + GetRawUnit already existed everywhere). Tests: executor mock tests (list, module filter, describe-not-found) + a check-parseable doctype example. Docs: MDL_QUICK_REFERENCE + a syntax feature. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Surface Building Blocks in the SQLite catalog so they are queryable via `select ... from CATALOG.building_blocks` and listed by SHOW CATALOG TABLES. - New building_blocks_data table + full-snapshot view (tables.go), registered in Catalog.Tables() as CATALOG.BUILDING_BLOCKS. - buildBuildingBlocks() populates it (Id, Name, QualifiedName, Module, Folder, Description, DisplayName, Platform, Category, WidgetCount). WidgetCount is read from the raw unit's top-level Widgets array (full mode only). - ListBuildingBlocks added to the CatalogReader interface (*mpr.Reader already implements it). Deliberately NOT wired into widgets_data / the refs projection: a Building Block is a read-only copy-template (deep-copied onto pages with no live link), so it has no meaningful cross-reference edges — only a widget count. The SELECT path needs no grammar change (catalogTableName already accepts IDENTIFIER). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The doctype integration harness (TestMxCheck_DoctypeScripts) execs each example on both engines, and 36-building-block-examples.mdl failed two ways: - modelsdk: `show building blocks` errored because the modelsdk backend's ListBuildingBlocks was an unimplemented stub. Implement it on *Backend (mprread over genPg.BuildingBlock + scalar metadata), mirroring ListSnippets, so SHOW/DESCRIBE BUILDING BLOCK work on the codec engine too. The widget tree still comes from the shared GetRawUnit, so read-only describe needs no more. - legacy (and modelsdk): the example `describe building block MyModule.LoginForm` errored "building block not found" — a fresh test project ships no building blocks, and DESCRIBE of a missing block correctly errors. Make the example exec-safe: keep the `show building blocks` statements (empty, no error) and show DESCRIBE + the CATALOG.building_blocks query illustratively in comments. Follow-up to the merged building-blocks feature (PR #16). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
fix(building-blocks): modelsdk ListBuildingBlocks + exec-safe doctype example
A user-facing skill (synced to Mendix projects via `mxcli init`) that makes a
generated app reach "designed product" quality, the way artifact-design does for
HTML and dataviz does for charts. It's the taste + workflow layer on top of
theme-styling (SCSS mechanics), create-page (widget syntax), fragments
(composition), and migrate-design-prototype (design handoff).
Grounded in the STANDARD Atlas design system every project already ships —
Atlas_Core classes + the 39 out-of-the-box Atlas_Web_Content building blocks —
rather than a bespoke per-app library, so it works in any project. Built on the
building-blocks READ capability (SHOW/DESCRIBE BUILDING BLOCK), and validated
against a real Atlas project (the inventory + the Card structure in the worked
example are real DESCRIBE output).
Covers: the Atlas-first thesis and "reach down the stack first"; the 4-layer
architecture (Atlas → brand tokens → identity SCSS → mandatory runtime verify);
the discover → inspect → mirror workflow over out-of-the-box blocks; the Atlas
class + typed-designproperties vocabulary; a Layer-1 brand-token scaffold and an
optional dark-mode widget-override scaffold (inline SCSS); the transparent-Plotly
chart theme; the run --local --watch --screenshot verify loop ("mx check is not
enough"); and the full gotchas catalog.
Honest about the current capability boundary: discovery works today, but
instantiation (USE BUILDING BLOCK) does not yet exist, so the skill teaches
mirroring a block's shape with classes + design properties + fragments, treating
the Atlas block as the canonical target — migrating to native instantiation when
USE BUILDING BLOCK lands.
Single self-contained file (the sync/embed only handles top-level skills/*.md).
Implements the atlas-design proposal (docs/11-proposals/PROPOSAL_atlas_design_system.md).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Mark the Building-Blocks proposal partial — READ (SHOW/DESCRIBE + CATALOG.building_blocks) shipped in PR #16/#17 and is validated live against a real Atlas app — and add the USE BUILDING BLOCK instantiate slice: - Syntax mirroring `use fragment` (`use building block Mod.Name [as prefix_]`), a page-body element sourced from a persisted Pages$BuildingBlock. - Deep-copy / no-live-link / prefix semantics. - The configuration decision: afterwards via `alter page` (v1), an optional inline override block (v1.1), a possible `over database …` data-context shortcut — and explicitly NOT implicit slot inference. - v1 implementation approach: reuse the fragment-expansion hook; convert the block's BSON widgets to []*ast.WidgetV3 by round-tripping through the DESCRIBE renderer + visitor.Build (verified re-parseable), not a hand-written converter. Also corrects the doc's stale storage-type (Pages$BuildingBlock) and refreshes the proposals README index. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Instantiate an out-of-the-box Atlas building block onto a page in one line,
instead of hand-mirroring its DESCRIBE output. Mirrors `use fragment`:
use building block Atlas_Web_Content.Card
use building block Atlas_Web_Content.Card as cust_
It's a page-body element (valid anywhere `use fragment` is), sourced from a
persisted Pages$BuildingBlock rather than a script-scoped `define fragment`. v1
is deep-copy only (+ optional `as <prefix>` rename); configure the copied widgets
afterwards with `alter page` (inline config is the v1.1 slice in the proposal).
Wiring mirrors use fragment: a `useBuildingBlockRef` grammar rule → a
USE_BUILDING_BLOCK sentinel WidgetV3 in the visitor → expansion in the same
expandIfFragment hook. The block's BSON widgets are turned into []*ast.WidgetV3
by round-tripping through the DESCRIBE renderer (render → `define fragment`
wrapper → visitor.Build → .Widgets) rather than a hand-written BSON→AST
converter, so it reuses the full widget parser and degrades to a clear parse
error. The prefix rename reuses prefixWidgetNames.
Verified end-to-end against the real Atlas_Web_Content.Card on a live 11.12.1
project: the container + dynamictext deep-copy with the cust_ prefix, preserving
the card-title class and the 'Card style' design property. Unit tests
(mock-backend) cover prefix/no-prefix/not-found and the parse.
Notes: requires MXCLI_ENGINE=legacy until the modelsdk ListBuildingBlocks lands
(PR #17). Compound/nested design-property values (e.g. Spacing: [...]) are a
pre-existing create-page writer gap (they drop on direct authoring too), out of
scope here.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…rimary path Now that USE BUILDING BLOCK v1 exists, the skill leads with the one-line instantiation instead of the hand-mirror workaround: - Workflow renamed discover → inspect → USE (was → mirror). Step 3 is now `use building block Mod.Name [as prefix_]` (deep-copy), step 4 configures the copied widgets with `alter page`. Mirroring is reframed as the explicit fallback (hand-tuning, or the modelsdk engine before its building-block support lands). - Capability table: Instantiate → ✅ v1 (deep-copy; configure afterwards; legacy engine today). Capability note updated to "discovery AND instantiation both work". - Validation checklist updated to prefer `use building block` + `alter page`. All `use building block` / `alter page` examples are bare page-body lines, which the skill-MDL checker auto-skips (first keyword not in its create/alter/drop entity|page checkable set) — verified with a binary built WITHOUT the grammar, so this PR's CI is green even before the USE BUILDING BLOCK feature (#19) merges. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…veat, refresh status Two verified doc fixes from the atlas-design proposal's P2 list, plus a status refresh of the proposal itself: - create-page.md: correct the stale chart note — Line/Bubble/Heatmap/TimeSeries ARE MDL-authorable (via the line/scalecolor object-lists; see 34-chart-widget-examples.mdl), not "use Studio Pro for those". Add the Slider/RangeSlider `showTooltip: false` caveat (React findDOMNode removed in MX 11 → "Could not render widget" on drag, a runtime crash mx check misses). - PROPOSAL_atlas_design_system.md: mark the shipped work done in the "mxcli work required" table — CE0148 grant guard (PR #15), Building Blocks READ (PR #16/#17) and INSTANTIATE (PR #19), and the two P2 doc fixes above. Bump status proposed → partial (the skill shipped in PR #18); list related PRs. Refresh the proposals README index. Remaining open proposal items: parameterized fragments, chart theme colourway, CREATE BUILDING BLOCK (author), typed designproperties, lint rules, and USE BUILDING BLOCK v1.1 (inline override + data-context shortcut). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
An inline compound design property on a widget — e.g. the Atlas Spacing group `designproperties: ['Spacing': ['margin-bottom': 'Large', ...]]`, and any block copied in via `use building block` — was silently dropped by the legacy builder write path. `check`, `exec`, and even `mx check` all passed, but the nested styling vanished from the .mpr; only flat toggle/option props survived. Root cause: serializeDesignProperties switched on p.ValueType with cases for toggle/option/custom and a `default: continue` that dropped the `compound` kind entirely. A compound value nests its sub-properties in a Forms$CompoundDesignPropertyValue whose Properties list has the same marker-prefixed Forms$DesignPropertyValue shape as the outer array. Add a `case "compound"` that emits Forms$CompoundDesignPropertyValue and recurses via serializeDesignProperties(p.Compound). The AST, visitor, and builder already produced the nested model — only the serializer's terminal switch dropped it. This is the CREATE write counterpart to the describe read-back fix in mendixlabs#668. Verified: mxcli docker check = 0 errors on 11.12.1 (authoritative), plus a write->describe round-trip preserves Spacing on both engines. Test TestSerializeDesignProperties_Compound; bug-test mdl-examples/bug-tests/compound-designproperties.mdl; symptom table row added to fix-issue.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
A plain fragment substitutes a fixed widget group. A content slot lets a
fragment WRAP arbitrary caller-supplied content — a reusable shell (an
Atlas card, panel, or section) whose body varies per use. Neither a plain
fragment nor a Mendix Snippet can express this (Snippets are context-entity
only, no content slot), so the design-recipe library previously needed
hand-written .mdl copy-and-fill for every content-varying block.
Syntax:
define fragment Card as {
container cardWrap (class: 'card', designproperties: ['Card style': on]) {
slot content -- caller's widgets land here
}
};
use fragment Card {
dynamictext cardHeading (content: 'Welcome', rendermode: H2)
dynamictext cardText (content: 'Wrapped content')
}
Semantics (v1):
* slot name optional (defaults to "content"); one slot per fragment
* use fragment with no payload leaves the slot empty (valid shell)
* payload supplied to a slotless fragment is an error
* payload is deep-cloned; `as prefix_` still renames the fragment's own
widgets, not the caller's payload
* resolves entirely at fragment-expansion time — no BSON changes; describe
page shows the fully-expanded tree (slot marker gone)
Implementation is confined to the AST-expansion layer: a SLOT sentinel rides
in the fragment tree, the `use fragment X { … }` payload rides on the
USE_FRAGMENT sentinel's Children, and expandFragmentRef splices the payload
into the slot before buildWidgetV3 ever runs. Full stack: SLOT lexer token
(+ keyword rule so it stays a usable identifier), slotMarkerV3 +
useFragmentPayload grammar, visitor builders, executor splice + guards.
Verified: mxcli docker check = 0 errors on a real Mendix 11.12.1 project;
write->describe round-trip preserves the wrapped tree; all three edge cases
(no-slot-with-payload, bare slot, empty slot) behave. Tests in
visitor_fragment_test.go and fragment_test.go; example in
15-fragment-examples.test.mdl. Docs: fragments.md and atlas-design skills,
page-composition proposal (now leads with the content slot; scalar params
deferred to v1.1), atlas proposal open-items table, and syntax help
(fragment.slot topic).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…perimental)
Content slots vary a component's structure; this adds the data and behavior
axes. A fragment can now declare typed datasource/action parameters, and a
`use building block` can rebind the block's datasource and primary button —
turning a reusable shell into a real component bound to a specific entity and
microflow per use.
Fragment parameters:
define fragment DataPanel($data: datasource, $onEdit: action) as {
container panel (class: 'card') {
listview lv (datasource: $data) {
slot content
actionbutton edit (caption: 'Edit', action: $onEdit, buttonstyle: primary)
}
}
};
use fragment DataPanel ($data: database Sales.Order, $onEdit: microflow Sales.Edit) {
dynamictext heading (content: 'Orders', rendermode: H4)
}
Building-block rebind overrides:
use building block Atlas_Web_Content.List_Cards
(datasource: database Sales.Order, action: microflow Sales.Open) as orders_;
Semantics: values substitute at fragment-expansion time (no BSON changes;
describe shows the concrete datasource/action, no $param). Every declared
parameter must be supplied; unknown args and type mismatches are errors. A
microflow/nanoflow value parses as a datasource (grammar overlap on those two
keywords) and is reinterpreted as a call action when the parameter's kind is
action. Building-block binding-point rule: datasource → first widget carrying a
datasource; action → first button widget (Atlas placeholder buttons ship with
no action, so the target is matched by widget type, not an existing Action).
Full stack: actionExprV3 gains a $var alt; fragmentParams / fragmentArgs /
blockOverrides grammar; AST FragmentParam + FragmentArg + param-action;
visitor builders; executor substituteFragmentParams (with validation) and
rebindFirst. Sentinel-internal keys (Args, DataSourceOverride, ActionOverride)
added to the MDL-WIDGET07 allowlist.
Verified end-to-end on a real Mendix 11.12.1 project: mx check = 0 errors for a
param-bound fragment page AND for datasource + action building-block rebinds;
describe round-trip shows the resolved bindings; four error cases (missing arg,
unknown arg, type mismatch, args-to-no-param-fragment) behave. Tests in
visitor_fragment_test.go and fragment_test.go; example LEVEL 5c/5d in
15-fragment-examples.test.mdl. Docs: fragments.md and atlas-design.md skills
(marked experimental), page-composition proposal (Parameter Bindings section
with open design points), and syntax help (fragment.params topic).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
A `use fragment` / `use building block` (or a content `slot`) nested inside a container, layout column, or dataview — anywhere but the page-body top level — failed exec with "unsupported widget type: USE_FRAGMENT". expandFragments only ran on the top-level widget list; the layout/container builders build children via buildWidgetV3 directly, which has no case for the sentinel types. This crippled the natural usage — a reusable card/panel could not be placed inside a layout column — and affected content slots and building blocks too. Fix: expandFragments now recurses into every widget's children, so the tree is fully expanded to concrete widgets before buildWidgetV3 runs (no builder needs a sentinel case). Expansion is idempotent on concrete widgets, so re-traversing an already-expanded slot payload is harmless. Verified: nested cards inside a layoutgrid column exec + mx check = 0 on a real 11.12.1 project. Test TestExpandFragments_NestedInsideContainer; bug-test mdl-examples/bug-tests/nested-fragment-expansion.mdl; symptom-table row added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…ing P004
CI (push-test.yml) runs `mxcli check` over doctype-tests/*.mdl but skips
*.test.mdl, so the slot/binding examples buried in 15-fragment-examples.test.mdl
had no CI coverage. Extract them into focused, self-contained plain .mdl files
that CI checks:
* 15b-fragment-slots-examples.mdl — DEFINE FRAGMENT … SLOT, USE FRAGMENT { … },
empty slot, prefixed reuse, nested-in-layout
* 15c-fragment-bindings-examples.mdl — ($data: datasource, $onEdit: action) params,
USE BUILDING BLOCK (datasource:/action:) rebind
Both pass `mxcli check` and exec + mx check = 0 on a real 11.12.1 project. The
old .test.mdl levels are replaced with a pointer to the focused files.
Also fixes a pre-existing error in 15-fragment-examples.test.mdl: P004_Customer_New
bound a data view to a database source (MDL-WIDGET09 — a data view shows one
object). It now takes a page parameter, so the whole file passes `mxcli check`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…a-loss guard Two sudoku-app findings, both about entity DDL that passes `mxcli check` but bites later: #6 (alter path): the per-attribute checks (MDL021 reserved words, MDL022 AutoX rename, MDL023 autonumber seed) only ran on CREATE ENTITY, so an attribute added via `alter entity ... add attribute` escaped all of them — a seedless autonumber returned "Check passed!" then failed the build with CE7247. Extract the CREATE loop body into validateEntityAttribute and add ValidateAlterEntity for the ADD ATTRIBUTE path, wired into both `check` and the LSP. The entity kind is unknown from ALTER, so persistent-only MDL020 is skipped; the kind-independent checks all run. #24 (data loss): `create or modify entity` rebuilds the entity from the statement alone and replaces the stored one, so any attribute the statement omits is silently dropped (36->2 in practice), later surfacing as CE1613 on widgets still bound to them. Make it warn (non-blocking — the user asked to modify) and list the dropped members, pointing at `alter entity ... add attribute` for incremental edits. Also fix the "already exists" message so it recommends `alter entity` for a member change and reserves `create or modify` for a full replace. Adds unit + mock tests and bug-test examples for both, plus fix-issue symptom rows. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…trophes looksLikeUnescapedApostrophe matched any 1-4 char lowercase token as a contraction leftover, so a genuine parse error on a short lowercase MDL keyword/identifier (mismatched input 'on'/'in'/'as'/'to'/'by') drew the "unescaped apostrophe" hint and sent the user hunting for a quote that isn't there (findings #4). Restrict the match to the fixed contraction-suffix set (s/t/d/m/re/ve/ll — the leftovers from it's, don't, he'd, I'm, you're, we've, you'll). Real apostrophe errors still hint; short real keywords no longer do. Drop the now-unused isLowerAlpha helper; add negative test cases for the keywords. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…ange) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Findings #10: `exec` halted on the first error, so re-running a domain script that is 90% already-applied (e.g. "attribute already exists") applied none of the remaining 10%. Add ExecuteProgramContinueOnError: it attempts every statement, reports each failure prefixed with its statement number, and returns a summary; the CLI exits non-zero when any statement failed. Unlike a blanket error-swallow, every failure is still surfaced, so a real mistake stays visible. `exit`/`quit` (ErrExit) still stop the run. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Findings #10: with no way to re-apply a domain delta, re-running a script that adds an attribute erroring "already exists" was a hard error. Add IF NOT EXISTS to ADD ATTRIBUTE and IF EXISTS to DROP ATTRIBUTE (grammar + AST + visitor + executor): when the attribute is already present / already gone, the op is skipped with a notice instead of erroring, so a domain script re-runs cleanly and converges. The plain forms still error, so the guard is explicit and opt-in. Includes visitor + executor tests, a bug-test example, and syntax/quick- reference/symptom-table docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…ignments Two sudoku-app findings about microflow expressions that pass `mxcli check` but fail the build with CE0117: #1 — a call to a name that is not a Mendix expression function (e.g. the non-existent `randomInt()`) was ignored by the checker. Add exprcheck.UnknownFunctionCalls: it walks the expression tree and flags any CallExpr whose name is not in funcTable (which lists every built-in — a bare name(...) in a Mendix expression is always a built-in call), with a Levenshtein/prefix "did you mean random()?" hint. Surfaced as MDL044 from the microflow validator (declare/set/return/if-condition). #2 — a Decimal-returning function (random(), the duration *Between family) assigned to an Integer/Long variable fails CE0117, but the check only caught bare `div`. Add SourceRejectedForIntegerTarget (arithmetic Decimal OR a non-rounding Decimal-returning built-in) and use it in MDL041. Also correct the funcTable return kinds: secondsBetween/minutesBetween/ hoursBetween/daysBetween/weeksBetween are Decimal in Mendix (the calendar variants stay Integer, millisecondsBetween stays Long). Fixes 519-rest-mapping-as-list-of.mdl, which used a non-existent count() in an expression (now returns the list directly). Adds exprcheck + validator tests, two negative-test examples, and skill/symptom docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Findings #4: `index name on (cols)` — the SQL-like form documented for entity indexes — failed with "extraneous input 'on' expecting '('", because indexDefinition had no ON token. The bare `index name (cols)` worked, so the documented ON form was the only broken variant. Make ON optional in indexDefinition (IDENTIFIER? ON? LPAREN … RPAREN), regenerate the grammar. It covers both CREATE (entityOption) and ALTER ENTITY ADD INDEX (shared rule). buildIndex reads columns from IndexAttributeList only, so ON can't be mistaken for a column and no visitor change is needed; the bare form still parses. Adds visitor tests, a bug-test example, and syntax/quick-reference/skill/ symptom-table docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
`retrieve … where [Seq = $Game/MoveSeq + 1]` failed with a bare `mismatched input '+' expecting ']'`, with no hint that Mendix XPath constraints take a literal/token/variable/path on the value side — never an arithmetic expression. This is a Mendix limitation, not an mxcli bug, so adding grammar support would only let through XPath that mxbuild rejects. Add an error hint keyed on `mismatched input '<+|*|div|mod>' expecting ']'` (the `expecting ']'` shape only occurs inside a bracketed constraint) that names the limitation and the workaround: compute the value into a variable first, then compare against it. Documented in xpath-constraints.md; visitor test + negative bug-test example. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The migration skill was custom-SCSS-first — it had you hand-write .ss-*
component classes for cards/panels/buttons and demoted design properties to
a one-line aside, the exact "reinvent what Atlas gives you for free" pattern
atlas-design.md is written to prevent. The two skills also didn't link.
Reframe it Atlas-first without dropping the (valid) custom-SCSS identity
layer:
- Link atlas-design.md as the first related skill ("read first").
- Add an Atlas-first principle after the golden rule: prefer building blocks
→ Atlas classes / typed designproperties (spacing-*, flex-*/align-*,
'Card style': on, …) → brand-token retune → custom .ss-* SCSS for identity
only (layer 4).
- Step ② now leads with "check for an Atlas block/class first"; custom class
is the fallback for shapes Atlas can't express.
- Widget map: card → Atlas Card / class:'card'; simple row/column → flex
utilities (no layoutgrid); layoutgrid/display:grid reserved for real
multi-column/fractional layouts.
- Design-property gotcha upgraded from "if you use it" to a recommendation,
noting mxcli check -p validates keys/values (MDL-WIDGET11/12).
Docs only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Design properties were written fully free-form: a flat value always serialized as an option, so a ColorPicker/ToggleButtonGroup property got the wrong BSON $Type for Studio Pro's Appearance tab, and a typo'd key or value (both case-sensitive) sailed through mxcli check. Root cause of why the existing theme registry was unused: resolveDesignPropsKey upper-cased the MDL keyword but the lookup map is lowercase-keyed, so `container` never resolved to `DivContainer`. That left resolveDesignPropertyValueType as dead code and also made `show design properties <widget>` report "not found" for valid widgets. Fixed to lower-case the lookup. - Write path: astDesignPropToValue now takes the widget's theme properties and resolves each flat value's type from metadata (ColorPicker / ToggleButtonGroup -> custom), falling back to option when no project / themesource (backward compatible). - Check (-p only, when themesource defines properties): new ValidateDesignProperties walks page/snippet/alter-page widget trees and warns on MDL-WIDGET11 (unknown key, with a case-sensitivity hint / the list of valid keys) and MDL-WIDGET12 (invalid value, listing the allowed values). Warnings, not errors, for forward-compat with newer themes. Compound entries and widgets without type-specific metadata are skipped. - LSP: caches the theme registry once per session (no per-keystroke I/O) and surfaces the same diagnostics. Adds executor unit tests, an example, and theme-styling / symptom-table docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Findings #25: the warm loop prints its own progress but the Mendix runtime's own stdout/stderr — server stack traces and microflow LOG output — went only to an in-memory buffer surfaced solely on a startup failure. So when a page action threw, the browser showed the generic error dialog with nothing to correlate against, and a server-side bug couldn't be told apart from a client one. Tee the runtime JVM's stdout+stderr to <projectDir>/.mxcli/runtime.log via io.MultiWriter(buffer, file): the in-memory buffer still backs startup-error reporting, and the file makes the running app debuggable (tail -f). Appended across restarts with a "=== runtime start … ===" marker; the handle is closed on Stop and reopened on restart. On by default; --runtime-log <path> relocates it and "-" disables it. The path is printed at boot. Adds TestOpenRuntimeLog and run/run-local docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…tured The runtime-log tee added earlier writes the runtime JVM's stdout/stderr to .mxcli/runtime.log, but a standalone runtime (launched via runtimelauncher.jar) attaches no log subscriber by default — unlike a Studio Pro / m2ee run, which calls create_log_subscriber after start. Mendix application logs flow to log subscribers, not stdout, so microflow LOG output and server-side exception stack traces never reached the file: a page-action error still showed only the generic Mendix dialog with nothing to correlate against. After a successful start (and on every restart's Start, since each fresh JVM starts with no subscriber), attach a "file" log subscriber pointing at the same runtime.log with autosubscribe INFO. max_rotate is 0 so the runtime never renames the file out from under the JVM tee's open handle; the path is passed absolute because the runtime's cwd is <install>/runtime, not mxcli's. The attach is best-effort — a logging failure warns to stdout rather than failing an otherwise-healthy start. Tests: TestStart_AttachesLogSubscriber (params), TestStart_NoLogSubscriberWhenUnset, TestStart_LogSubscriberFailureNonFatal. Docs: run-local skill, docs-site, and the fix-issue symptom table (findings #25, round 2). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…, not custom
The typed-design-properties write path mapped a design property's BSON value
type from its declared control type: ToggleButtonGroup and ColorPicker both
became "custom". That is wrong for ToggleButtonGroup — a toggle-button-group
selection picks one of a fixed option set, so Studio Pro stores it as an Option.
A Custom value type mismatches the declaration and mx check fails CE6084
("Expected design property Flex container / Column gap / … to be of type Toggle
button group, but found Custom") on any Atlas flex/spacing/typography property,
e.g. 'Column gap': 'Medium'.
This broke TestMxCheck_DoctypeScripts on 12-styling-examples,
15c-fragment-bindings-examples, and 31-pluggable-datagrid-gallery-v010-examples
(both engines) — a make test-integration failure that unit tests didn't catch.
The value type is decided by the value, not the control type. Make
resolveDesignPropertyValueType value-aware: a value that is one of the
property's declared options serializes as "option" for every control type
(Dropdown, ToggleButtonGroup, and a ColorPicker's predefined swatches);
only a ColorPicker's off-list value (a free-form hex/color) is "custom";
everything else stays "option"; no metadata falls back to "option". Reuses the
validator's themeOptionAllowed helper for consistency.
Verified: all three doctype examples pass mx check with 0 errors on both the
legacy and modelsdk engines. TestAstDesignPropToValue_Typed extended with the
'Column gap': 'Medium' regression case and ColorPicker swatch-vs-hex cases.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…livers Registering a file log subscriber (findings #25, round 2) was necessary but not sufficient: a standalone runtime boots with the logging subsystem NOT started, so the subscriber sits inert and receives nothing. runtime.log still held only the JVM banner — a probe microflow's LOG output and a forced runtime exception produced zero lines. The isolation test in the findings doc pins it exactly: boot+subscriber → 0 probe lines; then call start_logging → 6 lines. After create_log_subscriber, call the start_logging admin action in the same step (order: create subscriber → start_logging). An "already started" response is treated as success, because Start re-runs on the DB-update retry and restart paths against a still-running JVM. Still best-effort — a logging hiccup warns to stdout and never fails an otherwise-healthy start. Renamed attachFileLogSubscriber → configureRuntimeLogging since it now does both. Tests: TestStart_StartLoggingAlreadyStartedIsSuccess; TestStart_Attaches- LogSubscriber now asserts the [start, create_log_subscriber, start_logging] sequence. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings the 39 commits on ako/main since the last upstream sync (#781). Clean
merge against main;
make build && make testgreen.Validation & DDL safety
create or modifyentity data-loss guard + alter-path attribute checksALTER ENTITY ADD/DROP ATTRIBUTE IF (NOT) EXISTSFragments & building blocks
SHOW/DESCRIBE BUILDING BLOCKS(read-only discovery) +building_blockscatalog tableUSE BUILDING BLOCKv1 — deep-copy a block onto a pageTyped design properties / Atlas
MDL-WIDGET11/12check validationuse building blockas the primary pathrun --local/--hubwarm loopstart_logging(findings TUI: yazi-style Miller columns, image collections, review fixes #25, end-to-end)--watchchangerun --hub: re-register with the hub on heartbeat 404Other
exec --continue-on-errorso partially-applied scripts re-run