Skip to content
Merged
1 change: 1 addition & 0 deletions .claude/skills/fix-issue/findings/cmd-mxcli.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -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/<Page>.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 <node rollup-runner.mjs pid>`) 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`"}
5 changes: 5 additions & 0 deletions .claude/skills/fix-issue/findings/mdl-executor.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -857,3 +857,8 @@
{"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 <select alias>` builds; a function of a grouped expression (`datepart(...) + 1`) builds; and grouping by r.ID does NOT exempt r.Name (no functional dependency in mxbuild). Compare expressions after normalising case/whitespace/identifier quotes, substituting GROUP BY expressions longest-first; skip UNION queries, whose branches carry their own lists", "refs": ["#981"]}
{"area": "mdl/executor", "date": "2026-10-04", "symptom": "ako/mxcli#981 item 6: `datepart(MONTH, r.RaceDate)` next to `group by datepart(YEAR, r.RaceDate)` passes check AND mxbuild, then the view fails whenever it is read: PostgreSQL 42803, HSQLDB 42574", "cause": "mxbuild only checks plain columns against the GROUP BY; an expression is passed through to the database", "file": "`mdl/executor/oql_view_select_checks.go` (MDL036)", "insight": "A runtime-only failure has no CE to grep for, so nothing downstream catches it — make it an error. Plain column vs expression is exactly the line between mxbuild's CE0174 (MDL035) and the database's refusal (MDL036)", "refs": ["#981"]}
{"area": "mdl/executor", "date": "2026-10-04", "symptom": "ako/mxcli#981 item 7: a view using `sum(1)` / `count(1)` / `count('x')` / `count(true)` builds and runs on PostgreSQL but fails on HSQLDB (42567 \"data type cast needed\"); a view column `1 as One` reads fine but a consumer's `v.One + 1` returns 11 on HSQLDB (2 on PostgreSQL)", "cause": "Mendix sends string, integer and boolean literals as untyped `?` parameters (decimals are cast); HSQLDB cannot type them", "file": "`mdl/executor/oql_view_select_checks.go` ValidateOQLPortability (MDL037 warning, MDL038 info)", "insight": "Database-dependent findings must stay OUT of ValidateOQLSyntax: exec turns every violation it returns into a refusal, whatever the severity. Keep `'TOTAL' as Label` (used in docs) at info so check-mdl stays clean; the fix forms that run are count(<col>), sum(cast(1 as Integer)), sum(case … then 1 else 0 end), cast('…' as String)", "refs": ["#981"]}
{"area": "mdl/executor", "date": "2026-10-04", "symptom": "ako/mxcli#981 items 4-5: `r.Name + ' x' as S` declared `string` or `string(100)` passes check, CE6770 in mxbuild (String(200) builds); a view attribute declared `autonumber` over an AutoNumber column passes check, CE6770", "cause": "inferTypeStatic had no rule for `+` (fell through to Unknown); typesCompatible accepted declared AutoNumber on purpose, pending a language header (ADR-0011)", "file": "`mdl/executor/oql_type_inference.go` (inferTypeStatic / inferTypeFromExpression concatenation, viewAutoNumberRefused in validateViewEntityTypes)", "insight": "Concatenation is a derived string: String(200) whatever the operand lengths. A new rejection that check previously accepted goes behind a langver.Change (MDL-V1-VIEWAUTONUMBER) — and check's reference tier ran with langVersion 0 regardless of the header, so ValidateProgram(WithWarnings) now enters the program's language version", "refs": ["#981"]}
Loading
Loading