diff --git a/.claude/skills/fix-issue/findings/cmd-mxcli.jsonl b/.claude/skills/fix-issue/findings/cmd-mxcli.jsonl index 76b151ce58..89be86dc8f 100644 --- a/.claude/skills/fix-issue/findings/cmd-mxcli.jsonl +++ b/.claude/skills/fix-issue/findings/cmd-mxcli.jsonl @@ -161,3 +161,4 @@ {"area": "cmd/mxcli", "date": "2026-10-04", "symptom": "`run --local --watch` (Mendix <= 11.13, rollup bundler): after a while every page change fails with `web client rebuild failed: web client watcher exited` (or `client bundle not served after apply: web client re-bundle: web client build timed out after 5m0s`) and nothing reaches the browser until `run --local` is restarted; after adding an entity the next changes fail with `ENOTDIR: not a directory, stat '.../web/pages/.js/package.json'`", "cause": "watchAndApply held a bare *WebClientWatcher: (1) once it exited nothing restarted it (only recoverMissingPages did), so WaitForRebuild failed every later change; (2) a failed incremental build (rollup's commonjs resolver hitting web/pages mid-rewrite by the serve build) left the watcher erroring and the change was dropped; (3) ensureClientServed's recovery ran a one-shot NODE_ENV=production BuildWebClient in the same web/ dir while the watcher was still running — two rollups on web/dist; (4) the 5m limit was hard-coded and a timeout printed nothing about why", "file": "cmd/mxcli/docker/webclient_supervisor.go, cmd/mxcli/docker/runlocal.go (watchAndApply, ensureClientServed), cmd/mxcli/docker/webclient.go (webClientTimeout, webClientBuildLogTail)", "fix": "bundlerSupervisor owns the bundler: EnsureAlive restarts an exited one (backoff 2s..60s after failed starts), AwaitRebuild retries a failed/aborted incremental rebuild once with a fresh bundler, Rebundle = stop+reap then start (never two). ensureClientServed takes the rebundle func; clientRebundler hands it the supervisor under --watch and the one-shot otherwise. --web-client-timeout / MXCLI_WEB_CLIENT_TIMEOUT; timeout errors append the last 30 lines of deployment/log/web-client-build.log. sessionNotice prints that a restart dropped sessions", "insight": "Killing the runner (`kill `) reproduces the dead-watcher state in seconds — no need to wait for it to die on its own. A fresh bundler is the universal recovery under --watch: its first build is a full bundle of the current source, so it covers missing pages, dangling chunks, a dist/ deleted by Gradle, and a transient incremental failure alike, without a second process on web/dist. Note: exec.Cmd.Wait called a second time concurrently with the reaper did block until exit on this Go version, so the old Stop was not the overlap — the one-shot in ensureClientServed was", "test": "cmd/mxcli/docker/webclient_supervisor_test.go (TestBundlerSupervisor_RestartsExitedBundler, _AwaitRebuildRecovers, _RebundleNeverOverlaps, TestEnsureClientServed_WatchModeRebundleIsExclusive, TestBuildWebClient_TimeoutShowsLogTail); live: 11.13 scratch app, 7 consecutive changes incl. a killed runner"} {"area": "cmd/mxcli", "date": "2026-10-04", "symptom": "`run --local --watch`: an `mxcli exec` (or save) made while a change is still building/applying is never built — no `Change detected` follows, the app keeps the previous model, and re-running the script writes nothing (byte-idempotent) so nothing re-triggers", "cause": "watchAndApply set `last = sourceMTime(...)` after every successful apply, under a comment claiming it kept mid-build edits; it did the opposite — the edit's mtime was folded into the baseline, so the next tick saw nothing newer", "file": "cmd/mxcli/docker/runlocal.go (watchAndApply)", "fix": "keep `last` at the settled mtime the build was taken from; the build writes nothing under the watched model/theme source (checked with find -newer during a live run), so this cannot self-trigger", "insight": "Found while reproducing #971 with a script that waited for the first output line of a change instead of its `applied` line — the next exec landed during a 2-minute restart-apply and vanished. Any test of a watch loop should include an edit made DURING a build, not only between builds", "test": "live only (11.13 scratch app, hsqldb): exec an entity add, exec a page change 15s into its build; fixed binary builds it as the next build, the binary with the refresh restored shows no further build after 45s"} {"area": "cmd/mxcli", "date": "2026-10-04", "symptom": "ako/mxcli#970 item 2: `theme create --from design.css` with only a :root block (no dark block) wrote the design's light --mxt-ground/--mxt-ink/--mxt-brand into the base theme's dark mixin, whose other surfaces stayed dark; the first injected token also sat on the `@mixin … {` line, unindented", "cause": "Tokens.forVariant returned the base declarations for EITHER variant, and seedTokens applied it to the alt-palette mixin unconditionally; applyTokens matched `(?m)^(\\s*)name`, and \\s* at ^ swallows the preceding newline (and blank line), so the replacement lost its line break and indent", "file": "`cmd/mxcli/theme/create.go` (seedTokens, Create/CreateResult.UnseededVariant); `cmd/mxcli/theme/tokens.go` (applyTokens, Tokens.declares); `cmd/mxcli/cmd_theme.go` (note)", "fix": "seed the alt mixin only when the design declared a block for that variant; otherwise leave it byte-identical to the base and report UnseededVariant, which the CLI prints as a note; match the indent with [ \\t]* instead of \\s*", "insight": "A base palette is the default variant's palette, not 'both': a token set that does not say which variant it describes must not seed the other one. And under (?m), ^\\s* is not 'leading indentation' — it crosses lines; use [ \\t]*. The control for 'mixin untouched' is the same scaffold with no design at all, compared byte for byte", "test": "`cmd/mxcli/theme/create_variant_test.go` (all three bases, base-only vs variant block, indentation); `cmd/mxcli/cmd_theme_test.go` (TestThemeCreate_NotesTheVariantABaseOnlyDesignDidNotSeed)"} +{"area": "cmd/mxcli", "date": "2026-10-04", "symptom": "ako/mxcli#982 item 2: `run --local --admin-port 8091` printed `Query data: mxcli oql -p app.mpr` and that command failed `cannot connect to Mendix admin API at localhost:8090`; `mxcli log list` failed the same way", "cause": "oql resolved the admin port as flag > ADMIN_PORT env > .docker/.env > 8090 and log used flag defaults 8090/mxcli-local-dev; neither read the .mxcli/run-local.json handshake the loop publishes with its port and password (only `constant set --apply` did)", "file": "`cmd/mxcli/devloop_admin.go` (devLoopAdminOptions); `cmd/mxcli/docker_oql.go`; `cmd/mxcli/cmd_log.go` (logAdminOptions, logConnectionHint); `cmd/mxcli/docker/runlocal.go` (hint)", "fix": "one helper takes port/password from a LIVE handshake for any flag not given (explicit host means 'not this loop'), forces direct loopback HTTP; oql and log both use it; the log hint prints the resolved port", "insight": "A hint printed by the process that knows the port must be runnable without that knowledge: either print the flags or make the consumer read what the producer published. Reading the handshake fixes every consumer at once; the test is an httptest admin API on a random port with a fake run-local.json, controlled by the no-handshake and stale-pid cases", "test": "`cmd/mxcli/devloop_admin_test.go`"} diff --git a/.claude/skills/fix-issue/findings/mdl-backend.jsonl b/.claude/skills/fix-issue/findings/mdl-backend.jsonl index 2c812702a1..0bd91c033e 100644 --- a/.claude/skills/fix-issue/findings/mdl-backend.jsonl +++ b/.claude/skills/fix-issue/findings/mdl-backend.jsonl @@ -166,3 +166,5 @@ {"area": "mdl/backend", "date": "2026-10-02", "symptom": "A view entity's OQL document that Studio Pro excluded is re-included by any CREATE OR MODIFY of the view entity that changes its query.", "cause": "encodeViewEntitySourceDocument writes Excluded=false as a constant, and WriteViewEntitySourceDocument's update path carried only the stored ExportLevel (#816), not Excluded.", "file": "`mdl/backend/modelsdk/move_view_write.go` (WriteViewEntitySourceDocument), `export_level_carry.go` (keepStoredExcluded)", "insight": "A shared create/update encoder that writes model state as a constant needs a carry on the update path for every such key, not just the one a previous bug named (#816 ExportLevel, #914 Excluded).", "refs": ["ako/mxcli#827", "ako/mxcli#914"]} {"area": "mdl/backend", "date": "2026-10-02", "issue": "ako/mxcli#803", "symptom": "`create association A.X from A.E to B.F on delete restrict` (TO entity in another module) stores ChildDeleteBehavior DeleteMeIfNoReferences with a null ChildErrorMessage — the shape that stops the runtime starting (CapTrackV2 §1). The same statement within one module, and ALTER on the cross-module one, were fine.", "cause": "crossAssocToGen built the delete behaviour without the restrict message; #795 added patchCrossDeleteErrorMessage only at one call site (patchCrossAssociations' new-element arm), not in the converter, so CreateCrossAssociation still wrote null.", "file": "`mdl/backend/modelsdk/association_move_write.go` (crossAssocToGen)", "fix": "Call patchCrossDeleteErrorMessage inside crossAssocToGen, as assocToGen does inline; drop the now-redundant call site.", "insight": "A property fix belongs in the converter, not at the call site that was reported — enumerate the converter's callers. Test: mdl/backend/modelsdk/issue803_cross_assoc_restrict_test.go, keep behaviour as the null control."} {"area": "mdl/backend", "date": "2026-10-02", "symptom": "getput: describe → exec of a combo box with `Editable: ` rewrites ConditionalEditabilitySettings/Conditions[0]: 2 -> 3", "cause": "Forms$ConditionalEditabilitySettings registered Conditions as a plain mandatory list (default marker 3); Studio Pro writes [2], as for visibility", "file": "`mdl/backend/modelsdk/widget_write.go` (RegisterTypeDefaults)", "insight": "measured on both editability settings in TestApp; the ALTER path (pagemutator setWidgetConditionalSettingMut) still hand-writes [3] for both settings", "refs": ["#721"]} +{"area": "mdl/backend", "date": "2026-10-04", "symptom": "ako/mxcli#980 (found adding navigation profiles to the TestApp round-trip harness): describe -> exec of an offline profile rewrote every multi-line sync constraint as the one-line text describe prints", "cause": "navOfflineConfigs wrote the spec's constraint verbatim; describe folds Studio Pro's multi-line layout (singleLine) and the visitor re-normalises it, so the text never matches the stored bytes", "file": "`mdl/backend/modelsdk/navigation_write.go` navOfflineConfigs / xpathWithoutLayout", "insight": "When describe deliberately changes layout, the writer must carry the stored layout for an unchanged value; compare with whitespace outside string literals removed (inside a literal it is part of the value — that is the control)", "refs": ["#980"]} +{"area": "mdl/backend", "date": "2026-10-04", "symptom": "ako/mxcli#980: create or modify navigation on a native profile ignored its menu block, sync block, login/not-found page and on-sync-error while reporting 'updated'; describe printed a native nanoflow home as `home microflow` and bottom bar items without action or icon", "cause": "navPatchNativeProfile only patched home pages; nativeNavProfileFromGen read BottomBarItem.Page only; the grammar had no nanoflow home", "file": "`mdl/backend/modelsdk/navigation_write.go` navPatchNativeProfile, `navigation_read.go` nativeNavProfileFromGen, `mdl/executor/cmd_navigation.go` checkProfileClauses", "insight": "A profile-kind-specific writer must refuse every clause it does not apply (checked in the executor before anything is written, including profile creation), and describe must not print what exec would refuse — list it as comments. No native profile exists in any local fixture, so the reader is pinned with a hand-built gen document", "refs": ["#980"]} diff --git a/.claude/skills/fix-issue/findings/mdl-executor.jsonl b/.claude/skills/fix-issue/findings/mdl-executor.jsonl index 7723d68b23..c18bd6573f 100644 --- a/.claude/skills/fix-issue/findings/mdl-executor.jsonl +++ b/.claude/skills/fix-issue/findings/mdl-executor.jsonl @@ -857,3 +857,11 @@ {"area": "mdl/executor", "date": "2026-10-04", "symptom": "ako/mxcli#968 / mendixlabs/mxcli#1263: `datepicker d (DateFormat: Time)` or `DateFormat: Custom, CustomDateFormat: '\u2026'` passes check and exec, mx check 0 errors, but every picker is stored FormattingInfo.DateFormat=Date; describe prints no format, so describe \u2192 exec silently turns a Studio Pro date-time picker into a date-only one. Same drop for a text box's DecimalPrecision/GroupDigits", "cause": "Three hops each dropped it: buildDatePickerV3/buildTextBoxV3 never read the properties, widget_write.go hard-coded newFormattingInfo() on DatePicker and TextBox, and describe never extracted FormattingInfo. Check stayed silent because validateStaticWidgetUnknownProps exempted the dynamic-text format keys (dateformat, customdateformat, \u2026) on EVERY widget type, not just dynamictext", "fix": "pages.DatePicker.FormattingInfo; executor input_formatting.go (inputFormattingInfo + inputFormattingProblems shared by builder and MDL-WIDGET18 check), writer formattingInfoToGen(x.FormattingInfo), describeInputFormatting, pagemutator setWidgetFormattingMut; per-widget allow-list pages.FormattingProperties. Measured: Custom with empty pattern = CE0493; Studio Pro stores CustomDateFormat beside DateFormat DateTime (TestApp WorkflowCommons), so only a pattern with NO DateFormat is refused \u2014 the param-format rule that refused it broke check on describe output", "file": "mdl/executor/input_formatting.go", "insight": "A key exempted from the unknown-property warning must be exempted per widget type: the dynamic-text format keys were skipped on every widget, which turned `DateFormat:` on a date picker (where nothing read it) into a silent drop. Before refusing a cross-field combination, scan Studio Pro-authored units for it \u2014 CustomDateFormat beside DateTime is stored by Studio Pro, and refusing it broke check on describe output.", "test": "mdl/executor/input_formatting_pedapp_test.go, input_formatting_test.go, mdl/backend/modelsdk/widget_formatting_write_test.go"} {"area": "mdl/executor", "date": "2026-10-04", "symptom": "ako/mxcli#969 item 2: `alter page … { set Action = microflow M.X on btn }` with M.X created earlier in the same script failed check (\"microflow not found\") and exec refused the script; the same for nanoflow and show page targets", "cause": "validateAlterSetProperties dry-runs the SET against the stored document, and resolveMicroflow / resolveNanoflowByName / resolvePageRef only know the session cache (createdMicroflows, …) that executing fills — which check never does", "file": "`mdl/executor/validate_alter_set.go` (scriptDeclaresMissing)", "insight": "A dry run of a mutator in check must treat a NotFound for a name the script declares (scriptContext.microflows/nanoflows/pages/snippets) as satisfied, matching on the typed mdlerrors.NotFoundError Kind+Name through errors.As rather than the message. Do not register fake IDs in ctx.Cache instead: exec runs check on the same executor and would resolve to them. Control: an undeclared target still fails", "refs": ["#969"]} {"area": "mdl/executor", "date": "2026-10-04", "symptom": "ako/mxcli#969 item 3: a list view / data grid / gallery with `datasource: $currentObject/M.Assoc` over a single-object association passed check and exec, then mxbuild failed CE8812 \"A grid association path must result in a list\"", "cause": "No rule modelled association multiplicity for list widgets", "file": "`mdl/executor/validate_assoc_list_source.go` (MDL-ASSOCDS01), hooked into attributeScopeValidator.walk", "insight": "Measured 8 shapes x 3 widgets on 11.13.0 and 11.14.0, identical: CE8812 for a Reference followed from its FROM entity (owner Default or Both) and for a Reference with owner Both from the TO entity (one-to-one); the reverse of a default Reference and every ReferenceSet build clean. Judge only those shapes with an exact context entity; skip specializations, self-associations and multi-hop paths. The attribute-scope walk already carries the data context, so hook there", "refs": ["#969"]} +{"area": "mdl/executor", "date": "2026-10-04", "symptom": "ako/mxcli#982 item 1: a chart microflow built JSON with `',\"v\":' + formatDecimal($x, '0.00')` (as the Vega skill pack recommended); a Dutch user got `12,50` and 'Data is not valid JSON', while it worked for the author", "cause": "formatDecimal without a locale formats in the current user's language; an underscore tag ('nl_NL') is silently ignored, only hyphenated tags ('en-US') apply. toString(round(x, 2)) is locale-independent with no exponent (measured 11.13)", "file": "`.claude/skills/packs/mendix-vega-charts/SKILL.md`, `specs/README.md`; `mdl/executor/validate_json_locale_number.go` (MDL-JSONNUM01, hooked in checkExprFunctions)", "fix": "skill uses toString(round(x, 2)) and explains the locale trap; check emits info MDL-JSONNUM01 for a locale-less formatDecimal in a + chain whose string literals contain '{' or '\":'", "insight": "Locale-dependent output passes every test run by the author, whose language is the one that works; the measurement that settles it is the same call under a second locale. The lint heuristic keys on the JSON-looking literal in the same concatenation so display strings stay quiet", "test": "`mdl/executor/validate_json_locale_number_test.go` (positive shapes + controls: toString(round), explicit locale, display string, bare call)"} +{"area": "mdl/executor", "date": "2026-10-04", "symptom": "ako/mxcli#981 items 1-3: view-entity OQL passed check and failed mxbuild CE0174 — `r.Season = r.CurrentSeason as IsCurrent` (\"The '=' part is incomplete or incorrect. You could use here: FROM.\"), `count(r.Season)` next to `group by r.Season` (also `max(r.D)` by `datepart(YEAR, r.D)`, `sum(r.S)` by `r.S + 1`), and a plain column that is not grouped (`r.Name` next to `group by datepart(...)`, `group by r.Season` or `group by r.ID`)", "cause": "ValidateOQLSyntax had no select-expression or GROUP BY rules at all", "file": "`mdl/executor/oql_view_select_checks.go` (MDL033/034/035), called from ValidateOQLSyntax", "insight": "Measure the controls along with the failures — they decide the predicate: the comparison inside CASE builds; count of a NON-grouped column builds; `group by