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
3 changes: 3 additions & 0 deletions .claude/commands/mendix/diff-local.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ Summary: 2 new, 3 modified, 1 deleted
- Use `--format side` when comparing large objects with subtle differences
- Use `--ref HEAD~5` to see what changed in the last 5 commits
- Use `--ref feature-branch` to compare against a different branch
- After restoring a `.mxunit` with `git checkout`/`git restore`, run
`mxcli fix hashes -p app.mpr --repair`: git does not update the `.mpr`'s
ContentsHash index, and `mx check` does not notice the stale entry

## Requirements

Expand Down
3 changes: 3 additions & 0 deletions .claude/skills/fix-issue/findings/cmd-mxcli.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,6 @@
{"area": "cmd/mxcli", "date": "2026-10-03", "symptom": "`mxcli report` could not score a project's own modules: `lint` has --modules, `report` had only --exclude", "cause": "Feature gap; and the LintContext module filter alone would not make the score exact, because project-level findings (CONV008 role mappings, project security) carry no module and are reported regardless", "file": "`cmd/mxcli/cmd_report.go`, `mdl/linter/report.go` (`ScopeToModules`, Report.Modules)", "insight": "Filter the scored violations to those located in a selected module, and print the selection in every format so a module score is not mistaken for the project's", "refs": ["ako/mxcli#953"]}
{"area": "cmd/mxcli/docker", "date": "2026-10-03", "symptom": "`mxcli docker build` (and docker run / reload, which call it) rewrote an MPRv1 project's .mpr, rewrote every MPRv2 .mxunit (restored with new mtimes), and wrote theme-cache/, deployment/, javasource/ proxies, the .launch file, .classpath and .project into the project; the TUI checker and `mxcli eval`'s mx_check wrote theme-cache/web/ and deployment/sass/ on every run", "cause": "update-widgets ran on the project under a snapshot that restored only v2 storage, and mx check / MxBuild ran on the project itself; the TUI and eval runner ran a bare `mx check <project>`", "file": "`cmd/mxcli/docker/build.go` (`buildOnCopy`), `cmd/mxcli/docker/check.go` (`MxCheckOnCopy`), `cmd/mxcli/tui/checker.go` (`runMxCheck`), `cmd/mxcli/evalrunner/checks.go` (`checkMxCheck`)", "insight": "Run update-widgets, mx check and MxBuild on one temporary copy (copyProjectToTemp) and write only the output directory. Measured on the 11.14 testapp: the PAD from the copy differs from the in-place build in the same 9 files as two in-place builds of identical copies differ (cache-bust stamps, operation ids, the native metro paths), so the output is equivalent; rebuild time unchanged (58s vs 59s), because a PAD build gains nothing from the project's deployment/. 11.14's blank app needs JDK 25, so the real-mx build test skips without it; run it with 11.13's mx.", "refs": ["ako/mxcli#961", "ako/mxcli#951", "mendixlabs/mxcli#808"]}
{"area": "cmd/mxcli", "date": "2026-10-03", "symptom": "ako/mxcli#962 item 3: in VS Code, two calls to a stored void JavaScript action with the same output name (Studio Pro's `$ReturnValueName`/`$RefreshEntity` shape) are squiggled MDL063, while `mxcli check -p` passes them", "cause": "runSemanticValidation called executor.ValidateMicroflow/ValidateNanoflow, which pass a nil void-action resolver: without the project every call output counts as a declaration", "file": "`cmd/mxcli/lsp_diagnostics.go` (runSemanticValidation); `mdl/executor/validate_void_code_calls.go` (FlowRules, CodeActionCache)", "fix": "executor.NewFlowRules(prog, s.findMprPath(), s.codeActions): resolves actions through the script and the workspace project (opened lazily), treats an unresolvable action as possibly void (unknownIsVoid) for MDL063 but never for MDL093, and shares project answers across keystrokes for 30s since one action read costs ~300ms on PedApp", "insight": "An entry point that wraps a richer internal API with nil arguments (ValidateMicroflow = validateMicroflowWith(stmt, nil)) silently gives every caller the weakest behaviour; a fix landed in the richer API (#958) does not reach them. Grep the exported wrapper's callers when the internal one gains a parameter. Measure the cost before putting project I/O on a keystroke path", "test": "`cmd/mxcli/lsp_void_calls_test.go` (PedApp: void pair clean with and without project, Boolean pair control, MDL093 only with project); `mdl/executor/validate_void_code_calls_test.go` (TestCodeActionCache_SharesProjectAnswersAcrossRuns)"}
{"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)"}
2 changes: 2 additions & 0 deletions .claude/skills/fix-issue/findings/mdl-executor.jsonl
Original file line number Diff line number Diff line change
Expand Up @@ -853,3 +853,5 @@
{"area": "mdl/executor", "date": "2026-10-03", "symptom": "`list workflows` and `show structure` report fewer workflow activities than the catalog's workflows_data (TestApp Workflow1: 5 vs 8)", "cause": "cmd_workflows.go countFlowActivities and cmd_structure.go countStructureFlowActivities each recursed over outcome flows only, skipping boundary-event flows and event sub-processes; the catalog had moved to a shared walk in #937 and the executor copies were left behind", "file": "`mdl/backend/wfnames/walk.go` (`WalkActivities`, `CountActivities`), `mdl/executor/cmd_workflows.go`, `mdl/executor/cmd_structure.go`, `mdl/catalog/workflow_walk.go`", "insight": "Duplicate-resolver drift: fixing one copy of a traversal (#937) left two private copies answering differently. The walk now lives in wfnames, which both catalog and executor already import, so there is one place to add a new sub-flow slot. Grep for every recursion over `workflows.Flow` when one is fixed.", "refs": ["ako/mxcli#963", "ako/mxcli#937"]}
{"area": "mdl/executor", "date": "2026-10-03", "symptom": "ako/mxcli#962 item 1: `$V = call java action M.VoidAction(...)` then `log ... + $V` (or `$V` as a JS call argument) passes `check` and `exec`, then mxbuild 11.13.0 fails CE0109 \"Undefined variable 'V'\"", "cause": "#953 taught MDL063 that a void call's output name declares nothing, but no rule read the other half: the name cannot be READ either. The resolver only answered void/not-void, so 'unknown' and 'non-void' were the same answer", "file": "`mdl/executor/validate_void_call_output.go` (checkVoidCallOutputUse, MDL093); `mdl/executor/validate_void_code_calls.go` (resolve -> voidness{void, known})", "fix": "MDL093: collect output names of calls KNOWN to be void, drop any name another statement defines flow-wide (declare, parameter, non-void producer), report each remaining name the flow reads (loopRefVars over every nested body). The resolver now returns known-ness, so 'possibly void' (the editor's policy) never produces MDL093", "insight": "A finding that says 'X declares nothing' has two consequences — no collision AND no definition; fixing the first and logging the second as follow-up left a CE gap. When a resolver's default is a policy (unknown counts as non-void), make the unknown state explicit before a second rule reads it: the CE0109 rule must use only knowledge, never the policy", "test": "`mdl/executor/validate_void_call_output_test.go`; `cmd/mxcli/check_void_calls_test.go` (TestCheck_ReadOfAStoredVoidCallOutput, PedApp stored void JS action, Boolean and unresolvable controls)"}
{"area": "mdl/executor", "date": "2026-10-03", "symptom": "ako/mxcli#962 item 2: describe of a microflow with the same output name in each if/else branch (or inside a loop and again after it) printed no duplicate-variable warning; mxbuild 11.13.0 reports CE0111 for both", "cause": "duplicateOutputVariableWarnings only warned when one assignment could REACH another (a reachability walk written for #710's performance), so exclusive branches and a loop body vs. the flow after it were treated as separate scopes; a test pinned the branch scoping", "file": "`mdl/executor/cmd_microflows_show.go` (duplicateOutputVariableWarnings)", "fix": "Count non-void output names over every object collection (loop bodies included); warn for any name created twice. Linear, so #710's cost concern disappears with the reachability walk", "insight": "The reachability model encoded a belief (exclusive paths may reuse a name) that no one had measured; MDL063 had already been aligned to flow-wide names in #958, so two renderings of the same rule disagreed. When one rule is corrected against mxbuild, grep for the other places that encode the same rule — here describe's header warning", "test": "`mdl/executor/cmd_microflows_duplicate_output_test.go` (TestFormatMicroflowActivitiesWarnsForExclusiveBranchOutputs, TestFormatMicroflowActivitiesNamesAreFlowWide)"}
{"area": "mdl/executor", "date": "2026-10-04", "symptom": "ako/mxcli#970 item 1: unscoped `create or modify translations for nl_NL ('Cancel' as 'Annuleren')` on TestApp set 50 translations in 38 documents, 35 of them in Marketplace modules (WorkflowCommons, Atlas_Web_Content, FeedbackModule; 14 page templates/building blocks), silently — the next module update overwrites them", "cause": "translationScope returned nil (whole project) for an unscoped run, by design and documented; nothing told the author where the writes landed and there was no way to exclude Marketplace modules short of one `in <Module>` run per own module", "file": "`mdl/executor/cmd_translations.go` (translationScope, reportMarketplaceWrites, reportOutOfScopeEntries); `mdl/translations/project.go` (Stats.Written); grammar MDLSettings.g4/MDLCatalog.g4 (WITHOUT MARKETPLACE)", "fix": "kept the default (ADR-0011: changing what a committed headerless or mdl 1 script writes is a meaning change, and a translated Marketplace page is a runtime-visible effect a user may want); warn per Marketplace module with the template/building-block count; added the additive clause `without marketplace` on create and describe, whose scope excludes units in modules with FromAppStore or AppStoreGuid (isMarketplaceModule, the same test alter page/layout use) and whose skipped entries are reported, not counted as drift", "insight": "When the fix the issue asks for is a change of default, check ADR-0011 first: a frozen language version turns 'skip by default' into 'warn + additive opt-out'. The precedent for the opt-out's semantics was already in ALTER ENTITIES (unscoped sweep skips Marketplace, naming a module means it). Per-unit write records (Stats.Written) are what let a caller say WHERE a project-wide walk wrote", "test": "`mdl/executor/cmd_translations_marketplace_test.go` (TestApp: unscoped still reaches Marketplace AND warns; without marketplace changes 0 Marketplace units and >0 own units; scoped runs do not warn; describe emits the clause); `mdl/visitor/visitor_translations_test.go` (TestTranslations_WithoutMarketplaceParses)"}
{"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"}
16 changes: 16 additions & 0 deletions .claude/skills/mendix/create-page/reference/widgets.md
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,23 @@ radiobuttons rbStatus (label: 'Status', attribute: status)
**DATEPICKER** - Date/time selection:
```sql
datepicker dpCreated (label: 'Created Date', attribute: CreatedDate)
-- DateFormat is the picker's MODE: Date (default), Time, DateTime, or Custom.
-- A time-of-day or date-and-time field needs Time / DateTime, or it renders date-only.
datepicker dpStart (label: 'Start', attribute: StartTime, DateFormat: DateTime)
datepicker dpAt (label: 'At', attribute: StartTime, DateFormat: Custom, CustomDateFormat: 'dd-MM-yyyy HH:mm')
```
`CustomDateFormat` needs `DateFormat: Custom` (written alone it is refused — it would
never apply), and `DateFormat: Custom` needs a non-empty pattern (mxbuild CE0493).
A pattern beside another explicit DateFormat is accepted: Studio Pro keeps it when the
format is switched away from Custom, and `describe` prints it. Change an existing
picker with `alter page … { set (DateFormat: DateTime) on dpStart; }`.

**TEXTBOX numeric formatting** — on a Decimal/Integer/Long attribute:
```sql
textbox tbAmount (label: 'Amount', attribute: Amount, DecimalPrecision: 2, GroupDigits: true)
```
A text box binds no dates (CE2421), so `DateFormat` on it is dropped and warned about
(MDL-WIDGET07), as on any widget other than a date picker.

**COMBOBOX** - Combo box (pluggable widget):
```sql
Expand Down
Loading
Loading