diff --git a/.claude/skills/fix-issue.md b/.claude/skills/fix-issue.md index d8aab95da..505fefab3 100644 --- a/.claude/skills/fix-issue.md +++ b/.claude/skills/fix-issue.md @@ -172,7 +172,8 @@ to the symptom table below, so the next similar issue costs fewer reads. | A microflow expression calls a function that doesn't exist (e.g. `randomInt(9)` — in some docs but not a Mendix built-in): parses, passes `mxcli check`, then fails the build with CE0117 "Error(s) in expression". Also: a Decimal-returning function (`random()`, `secondsBetween`, the duration `*Between` family) assigned to an Integer/Long variable fails CE0117, but `mxcli check` only caught bare `div` | (1) The func checker only validated arity for *known* functions; an unknown call name was ignored. (2) `checkNumericAssignment` used `SourceIsArithmeticDecimal`, which fires only on arithmetic roots, so a Decimal *function* result slipped through. Also the `*Between` duration funcs were mistyped Integer in `funcTable` | `mdl/exprcheck/unknown_funcs.go` (`UnknownFunctionCalls`, `SourceRejectedForIntegerTarget`, `nearestFunc`) + `mdl/exprcheck/func_checker.go` (`funcTable` between-date return kinds) + `mdl/executor/validate_microflow.go` (`checkExprFunctions` → MDL044; `checkNumericAssignment` now uses `SourceRejectedForIntegerTarget`) | Walk each expression for `CallExpr` names not in `funcTable` (which lists *every* built-in — a bare `name(...)` is always a built-in call) → **MDL044** with a Levenshtein/prefix "did you mean" hint. Extend the Decimal-into-Integer check to Decimal-returning non-rounding functions → **MDL041**. Correct `secondsBetween`/`minutesBetween`/`hoursBetween`/`daysBetween`/`weeksBetween` to Decimal (calendar variants stay Integer, millisecondsBetween stays Long). When adding a new Mendix built-in, add it to `funcTable` or MDL044 will false-positive. Bug-tests `f1-unknown-expression-function.fail.mdl`, `f2-decimal-func-into-integer.fail.mdl`. Findings #1, #2 | | `index name on (cols)` inside `create entity` (or `alter entity add index name on (cols)`) fails with `extraneous input 'on' expecting '('` — the SQL-like form docs/users expect. The bare `index name (cols)` worked | `indexDefinition` had no `ON` token: `IDENTIFIER? LPAREN indexAttributeList RPAREN` | `mdl/grammar/domains/MDLDomainModel.g4` (`indexDefinition`) | Make `ON` optional: `IDENTIFIER? ON? LPAREN indexAttributeList RPAREN`, regen grammar. `buildIndex` reads columns from `IndexAttributeList` only, so ON can't be mistaken for a column and no visitor change is needed. Covers both CREATE (entityOption) and ALTER ADD INDEX (shared rule). Bug-test `mdl-examples/bug-tests/f4-entity-index-on.mdl`. Findings #4 | | `retrieve … where [Seq = $Game/MoveSeq + 1]` fails with a bare `mismatched input '+' expecting ']'` — no hint that Mendix XPath can't compute values (this is a Mendix limitation, not an mxcli bug) | Mendix XPath constraints take a literal/token/variable/path on the value side, never an arithmetic expression; the parse error named the token but not the cause | `mdl/visitor/visitor.go` (`enhanceErrorMessage`, `looksLikeXPathArithmetic`/`xpathArithmeticRe`) | Do NOT add grammar support (mxbuild would still reject the XPath). Add an error hint keyed on `mismatched input '<+|*|div|mod>' expecting ']'` (`expecting ']'` only occurs inside a `[…]` constraint) explaining the limitation and the workaround: compute into a variable first, then compare. Also documented in `xpath-constraints.md`. Bug-test `mdl-examples/bug-tests/f8-xpath-arithmetic.fail.mdl`. Findings #8 | -| Design properties are written free-form: a `ColorPicker`/`ToggleButtonGroup` value serializes as a plain option (wrong `$Type` for Studio Pro's Appearance tab), and a typo'd key/value (they're case-sensitive) passes `mxcli check`. Also `show design properties ` reports "No design properties found for widget type container" for a valid widget | Root bug: `resolveDesignPropsKey` upper-cased the MDL keyword but the lookup map is **lowercase-keyed**, so `container`→`DivContainer` never resolved — leaving `resolveDesignPropertyValueType` dead code and the theme registry unused on the write/validate paths | `mdl/executor/theme_reader.go` (`resolveDesignPropsKey` case fix) + `mdl/executor/cmd_pages_builder_v3.go` (`astDesignPropToValue` takes theme props) + `mdl/executor/validate_design_properties.go` (new, MDL-WIDGET11/12) wired from `cmd/mxcli/cmd_check.go` + `cmd/mxcli/lsp_diagnostics.go` (cached `themeRegistry`) | Fix `resolveDesignPropsKey` to lower-case the lookup. On write, resolve each flat value's type from the registry (`ColorPicker`/`ToggleButtonGroup`→custom). On check (`-p` only, when themesource defines properties), walk page/snippet/alter-page widget trees and warn: **MDL-WIDGET11** unknown key (case-sensitivity hint / valid-key list), **MDL-WIDGET12** invalid value (lists allowed values). Warnings, not errors — a newer theme may add keys/values (forward-compat, per `page-styling-support.md:402`). Skip compound (registry doesn't model sub-props) and widgets with no type-specific metadata (pluggable). Bug-test `mdl-examples/bug-tests/typed-design-properties.mdl` | +| Design properties are written free-form: a `ColorPicker`/`ToggleButtonGroup` value serializes as a plain option (wrong `$Type` for Studio Pro's Appearance tab), and a typo'd key/value (they're case-sensitive) passes `mxcli check`. Also `show design properties ` reports "No design properties found for widget type container" for a valid widget | Root bug: `resolveDesignPropsKey` upper-cased the MDL keyword but the lookup map is **lowercase-keyed**, so `container`→`DivContainer` never resolved — leaving `resolveDesignPropertyValueType` dead code and the theme registry unused on the write/validate paths | `mdl/executor/theme_reader.go` (`resolveDesignPropsKey` case fix) + `mdl/executor/cmd_pages_builder_v3.go` (`astDesignPropToValue` takes theme props) + `mdl/executor/validate_design_properties.go` (new, MDL-WIDGET11/12) wired from `cmd/mxcli/cmd_check.go` + `cmd/mxcli/lsp_diagnostics.go` (cached `themeRegistry`) | Fix `resolveDesignPropsKey` to lower-case the lookup. On write, resolve each flat value's type from the registry **by matching the value against the property's declared options** (see the CE6084 correction below — the control type alone does NOT decide it). On check (`-p` only, when themesource defines properties), walk page/snippet/alter-page widget trees and warn: **MDL-WIDGET11** unknown key (case-sensitivity hint / valid-key list), **MDL-WIDGET12** invalid value (lists allowed values). Warnings, not errors — a newer theme may add keys/values (forward-compat, per `page-styling-support.md:402`). Skip compound (registry doesn't model sub-props) and widgets with no type-specific metadata (pluggable). Bug-test `mdl-examples/bug-tests/typed-design-properties.mdl` | +| Follow-up regression from the row above: after typed design properties merged, `mx check` fails **CE6084** "Expected design property _Flex container_ / _Column gap_ / _Align items Y_ … to be of type **Toggle button group**, but found **Custom**" on any page using a flat `ToggleButtonGroup` value (Atlas flex/spacing/typography, e.g. `'Column gap': 'Medium'`). Broke `TestMxCheck_DoctypeScripts` on `12-styling`, `15c-fragment-bindings`, `31-pluggable-datagrid-gallery-v010` (both engines) — green on unit tests, red only in `make test-integration` | `resolveDesignPropertyValueType` mapped `ToggleButtonGroup`→`custom` by control type. But a ToggleButtonGroup selection picks one of a **fixed option set**, so Studio Pro stores it as an **Option** — a `Custom` value type mismatches the declaration. Only a ColorPicker's **off-list** value (a free-form hex) is genuinely Custom. The value type is decided by the **value**, not the control | `mdl/executor/cmd_pages_builder_v3.go` (`resolveDesignPropertyValueType`, now takes the value and reuses `themeOptionAllowed`) | Make it value-aware: value ∈ declared options → `option` (Dropdown, ToggleButtonGroup, predefined ColorPicker swatch alike); off-list **and** `ColorPicker` → `custom`; else `option`; no metadata → `option`. Verified: the three doctype examples pass `mx check` = 0 errors on both engines. Test `TestAstDesignPropToValue_Typed` extended with the `Column gap: Medium` + ColorPicker swatch/hex cases. **Diagnosis pattern**: a value-type/BSON-`$Type` mapping keyed on a *declared control type* is a trap — verify it against `mx check`, never assert it from the type name alone (this is exactly how the original bug slipped in). **Process lesson**: this shipped red because `make test-integration` (mx-check doctype roundtrips) was not run before merge — run it, not just unit tests, for any page/widget-serialization change | | `mxcli run --local`: when a page action throws, the browser shows the generic Mendix error dialog and there is nothing to correlate it against — the runtime's own stdout/stderr (server stack trace, microflow `LOG` output) is swallowed, so a server-side bug can't be told apart from a client one | The runtime JVM was spawned with `cmd.Stdout=log; cmd.Stderr=log` where `log` is an in-memory `syncBuffer` surfaced only on a *startup* failure; during normal operation it goes nowhere on disk | `cmd/mxcli/docker/localboot.go` (`spawnAndConfigure`, `openRuntimeLog`, `LocalRuntime.logFile`, `LocalRuntimeOptions.RuntimeLogPath`) + `cmd/mxcli/docker/runlocal.go` (default `/.mxcli/runtime.log`) + `cmd/mxcli/cmd_run.go` (`--runtime-log`) | Tee the JVM's stdout+stderr to `/.mxcli/runtime.log` via `io.MultiWriter(log, file)` (the in-memory buffer still backs startup-error reporting). Append across restarts with a `=== runtime start … ===` marker; close the handle on Stop/reopen. Default on; `--runtime-log ` relocates, `-` disables. Print the path at boot. Test `TestOpenRuntimeLog`. Findings #25 | | Follow-up to the above (#25 re-test): `run --local` writes `runtime.log` but it stays **nearly empty** — the JVM tee captures startup/JVM output only; **application** logs (microflow `LOG`, server-side exception stack traces) never reach stdout, so a page-action error still can't be diagnosed | A standalone runtime (launched via `runtimelauncher.jar`) attaches **no log subscriber** by default — unlike a Studio Pro / m2ee run, which calls `create_log_subscriber` **after** start. Mendix application logs flow to log *subscribers*, not stdout, so with none attached they go nowhere | `cmd/mxcli/docker/runtime_controller.go` (`RuntimeController.LogSubscriberFile`/`Stdout`, `attachFileLogSubscriber`, called at the end of `Start`) + `cmd/mxcli/docker/localboot.go` (`StartLocalRuntime` sets `ctrl.LogSubscriberFile` to the abs runtime-log path) | After a successful `start` (and on every restart's `Start`, since each fresh JVM has no subscriber), call the `create_log_subscriber` admin action with `{type:"file", name:"mxcli-run-local", autosubscribe:"INFO", filename:, max_size:1GiB, max_rotate:0}`. **`max_rotate:0` is load-bearing**: the JVM stdout tee holds an fd on the same file, and a rotate-rename would detach it. Best-effort (a logging failure must not fail an up runtime — warn to Stdout instead). Pass an **absolute** path (the runtime's cwd is `/runtime`, not mxcli's). Tests `TestStart_AttachesLogSubscriber`, `TestStart_NoLogSubscriberWhenUnset`, `TestStart_LogSubscriberFailureNonFatal`. Findings #25 (round 2) | diff --git a/mdl/executor/cmd_pages_builder_v3.go b/mdl/executor/cmd_pages_builder_v3.go index 142a43000..a16736116 100644 --- a/mdl/executor/cmd_pages_builder_v3.go +++ b/mdl/executor/cmd_pages_builder_v3.go @@ -600,27 +600,40 @@ func astDesignPropToValue(p ast.DesignPropertyEntryV3, themeProps []ThemePropert default: return pages.DesignPropertyValue{ Key: p.Key, - ValueType: resolveDesignPropertyValueType(p.Key, themeProps), + ValueType: resolveDesignPropertyValueType(p.Key, p.Value, themeProps), Option: p.Value, }, true } } -// resolveDesignPropertyValueType determines the correct ValueType for a design property -// based on the theme definition. ToggleButtonGroup and ColorPicker use "custom" type; -// Dropdown uses "option" type. Falls back to "option" if theme info is unavailable. -func resolveDesignPropertyValueType(key string, themeProps []ThemeProperty) string { +// resolveDesignPropertyValueType determines the BSON ValueType for a flat design +// property value from the theme definition. +// +// The control type alone does NOT decide the value type: a value that is one of +// the property's declared options is always stored as an "option", whether the +// control is a Dropdown, a ToggleButtonGroup, or a ColorPicker's predefined +// swatches. Studio Pro rejects a mismatch (CE6084 "Expected design property … to +// be of type Toggle button group, but found Custom"), so a ToggleButtonGroup +// selection like 'Column gap': 'Medium' must serialize as an option, not custom. +// +// Only a value that is NOT a declared option, on a ColorPicker, is a free-form +// value (a custom hex/color) stored as "custom". Any other off-list value stays +// "option" (an invalid value is reported separately by MDL-WIDGET12). Falls back +// to "option" when no theme metadata is available (backward compatible). +func resolveDesignPropertyValueType(key, value string, themeProps []ThemeProperty) string { for _, tp := range themeProps { - if tp.Name == key { - switch tp.Type { - case "ToggleButtonGroup", "ColorPicker": - return "custom" - default: - return "option" - } + if tp.Name != key { + continue + } + if themeOptionAllowed(tp.Options, value) { + return "option" + } + if tp.Type == "ColorPicker" { + return "custom" } + return "option" } - // No theme info available — default to "option" (Dropdown) + // No theme info available — default to "option". return "option" } diff --git a/mdl/executor/validate_design_properties_test.go b/mdl/executor/validate_design_properties_test.go index e7ef9bbfe..a741795b9 100644 --- a/mdl/executor/validate_design_properties_test.go +++ b/mdl/executor/validate_design_properties_test.go @@ -23,21 +23,28 @@ func testThemeRegistry() *ThemeRegistry { } // TestAstDesignPropToValue_Typed verifies the write path picks the BSON value -// type from the theme registry (ColorPicker/ToggleButtonGroup → custom) rather -// than always defaulting flat values to option. Typed design properties. +// type from the theme registry: a value that is one of the property's declared +// options is stored as "option" for every control type (Dropdown, +// ToggleButtonGroup, ColorPicker swatch); only a ColorPicker's off-list value is +// "custom". A ToggleButtonGroup selection must NOT become custom or Studio Pro +// fails CE6084 ("Expected … Toggle button group, but found Custom"). Typed design +// properties. func TestAstDesignPropToValue_Typed(t *testing.T) { props := []ThemeProperty{ - {Name: "Background color", Type: "Dropdown"}, - {Name: "Text alignment", Type: "ToggleButtonGroup"}, - {Name: "Accent", Type: "ColorPicker"}, + {Name: "Background color", Type: "Dropdown", Options: []ThemeOption{{Name: "Brand Primary"}, {Name: "Default"}}}, + {Name: "Text alignment", Type: "ToggleButtonGroup", Options: []ThemeOption{{Name: "Left"}, {Name: "Center"}, {Name: "Right"}}}, + {Name: "Column gap", Type: "ToggleButtonGroup", Options: []ThemeOption{{Name: "Small"}, {Name: "Medium"}, {Name: "Large"}}}, + {Name: "Accent", Type: "ColorPicker", Options: []ThemeOption{{Name: "Brand Primary"}}}, } cases := []struct { key, val, wantType string }{ {"Background color", "Brand Primary", "option"}, - {"Text alignment", "Center", "custom"}, - {"Accent", "#ff0000", "custom"}, - {"Unknown Key", "x", "option"}, // not in registry → default option + {"Text alignment", "Center", "option"}, // ToggleButtonGroup option → option (not custom!) + {"Column gap", "Medium", "option"}, // the CE6084 regression case + {"Accent", "Brand Primary", "option"}, // ColorPicker predefined swatch → option + {"Accent", "#ff0000", "custom"}, // ColorPicker free-form color → custom + {"Unknown Key", "x", "option"}, // not in registry → default option } for _, c := range cases { dp, ok := astDesignPropToValue(ast.DesignPropertyEntryV3{Key: c.key, Value: c.val}, props)