Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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`"}
1 change: 1 addition & 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,4 @@
{"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)"}
4 changes: 2 additions & 2 deletions .claude/skills/packs/mendix-vega-charts/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,12 +162,12 @@ begin
set $Json = $Json + $Sep
+ '{"cat":"' + $R/CategoryName + '"'
+ ',"m":"' + $Month + '"'
+ ',"v":' + formatDecimal($R/Total, '0.00') + '}';
+ ',"v":' + toString(round($R/Total, 2)) + '}';
set $Sep = ',';
end loop;
```

`formatDecimal(x, '0.00')` is the right way to write a number into JSON — it emits a plain decimal with no grouping separators. Never write a value that could be empty into an unquoted position; emit `null` instead, and never emit `0` for "no data" (a zero against a full budget reads as maximally under budget, which is a lie the chart tells convincingly).
`toString(round(x, 2))` is the right way to write a number into JSON: it always uses a `.` decimal point, whatever the user's language, and never switches to an exponent (measured on 11.13: `12.35`, and `0.0000001` stays a plain decimal). **Do not use `formatDecimal(x, '0.00')`** — without a locale argument it formats in the *current user's* language, so a Dutch user gets `12,50` and the chart reports "Data is not valid JSON" while it renders fine for you. If you need `formatDecimal` (fixed trailing zeros), pass an explicit locale with a **hyphenated** tag: `formatDecimal(x, '0.00', 'en-US')`. The underscore form `'nl_NL'`/`'en_US'` is not an error — it is silently ignored and falls back to the user's language, so the bug comes back. Never write a value that could be empty into an unquoted position; emit `null` instead, and never emit `0` for "no data" (a zero against a full budget reads as maximally under budget, which is a lie the chart tells convincingly).

## Verifying without running the app

Expand Down
4 changes: 3 additions & 1 deletion .claude/skills/packs/mendix-vega-charts/specs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,7 @@ out any one and the columns drift apart while every panel insists it is correct.
## Emitting the rows

One JSON array of flat objects, built by a microflow over an OQL view entity. Numbers via
`formatDecimal(x, '0.00')`; `null` — never `0` — for "no value"; a discriminator column
`toString(round(x, 2))` — locale-independent, unlike `formatDecimal(x, '0.00')`, which
writes `12,50` for a Dutch user (if you need it, pass a hyphenated locale:
`formatDecimal(x, '0.00', 'en-US')`; `'en_US'` is silently ignored); `null` — never `0` — for "no value"; a discriminator column
(`k` above) when one payload feeds several panels.
Loading
Loading