Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5b8506a
fix(check): E001/E002 only for literals in value position (#969)
ako Oct 4, 2026
6b02dff
fix(check): type-check against enums and attributes the script create…
ako Oct 4, 2026
7bb835d
fix(check): alter page SET may name a flow or page the script creates…
ako Oct 4, 2026
2f4634d
feat(check): MDL-ASSOCDS01 predicts CE8812 for a list over a single-o…
ako Oct 4, 2026
5dc70e9
feat(fix): mxcli fix hashes verifies and repairs the MPR v2 ContentsH…
ako Oct 4, 2026
7090e72
feat: warn about git states that crash Studio Pro on open
ako Oct 4, 2026
49cae40
docs: Working Outside Studio Pro — fix hashes and git-state warnings
ako Oct 4, 2026
bbd65f1
feat(check): info hint for a quoted template parameter that reads lik…
ako Oct 4, 2026
0744ca3
fix(lint): MPR006 is an info note — an empty container builds and ren…
ako Oct 4, 2026
68b3197
fix(theme): a base-only design leaves the alternate variant's mixin a…
ako Oct 4, 2026
f689626
feat(translations): warn on Marketplace writes, add 'without marketpl…
ako Oct 4, 2026
d5c2a2c
fix(run-local): supervise the --watch web client bundler (#971)
ako Oct 4, 2026
ef91ffb
fix(run-local): keep an edit made while --watch applies a change
ako Oct 4, 2026
c4fd586
style: gofmt the quoted-template-param comments (#969)
ako Oct 4, 2026
7e5bc2c
docs: changelog, findings and widget reference for #969
ako Oct 4, 2026
f7ce019
fix: write, describe and alter a date picker's DateFormat (#968)
ako Oct 4, 2026
98e87cb
docs: date picker DateFormat and text box precision in syntax, skill …
ako Oct 4, 2026
d7d2d82
Merge remote-tracking branch 'origin/feat/972-fix-hashes-git-warnings…
ako Oct 4, 2026
e083025
Merge remote-tracking branch 'origin/fix/970-translations-scope-theme…
ako Oct 4, 2026
bb6a92a
Merge remote-tracking branch 'origin/fix/968-datepicker-format' into …
ako Oct 4, 2026
201f02e
Merge remote-tracking branch 'origin/fix/969-check-gaps' into c25-978
ako Oct 4, 2026
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)"}
4 changes: 4 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,7 @@
{"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"}
{"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"]}
Loading
Loading