Sync ako/main → upstream: RSS-reader + sudoku findings — authoring/check fixes, DESCRIBE round-trips, tracing & catalog observability - #792
Merged
Conversation
`alter page P { set class = 'x' on topBar; }` hard-errored on every
built-in widget:
failed to set: failed to set class on topBar:
property "class" not found (widget has no pluggable Object)
The MPR page mutator (setRawWidgetPropertyMut) dispatched first-class
widget properties via a case-sensitive switch on canonical casing
("Class"/"Caption"/"DynamicClasses"/...). A lowercase name typed in MDL
missed every case and fell through to the pluggable-Object path, which
errors on built-in widgets. `create page` was unaffected because
WidgetV3.GetStringProp reads properties case-insensitively, and the
sibling MCP page mutator already lowercased — only the MPR ALTER path
diverged.
Match first-class property names via strings.ToLower (mirroring the MCP
backend); the pluggable fallback keeps the original casing since template
property keys are case-sensitive.
Reported from the RSS-reader build (finding #1).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
`call javascript action` (e.g. NanoflowCommons.OpenURL) passed `mxcli check --references` and `mxcli exec` reported success, but the activity was written with no action — DESCRIBE showed `-- Empty action` and `mx check` failed with CE0008 "No action defined." and CE0109 on the now-missing output variable. The default modelsdk engine's microflowActionToGen had no case for *microflows.JavaScriptActionCallAction, so it returned nil and the ActionActivity was emitted without an Action. The read path and the legacy engine both handled it, which is exactly why check/exec looked green. Nanoflows reuse the same converter, so both flow kinds were hit (the RSS-reader build tripped it via a nanoflow). Add the JavaScriptActionCallAction case, built directly to mirror the legacy serializer — including the JS-specific storage keys (JavaScriptAction/OutputVariableName) and the "ParameterValue" parameter key (not the Java-style "Value"). Reported from the RSS-reader build (finding #11). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Re-applying a byte-for-byte identical `create or modify persistent entity` NULLed every value in the affected columns on the next runtime DB sync — rows survived, values gone. The op reported success and `mx check` passed; the loss only surfaced when something read the data. On a real domain model this blanked 11 feeds + 98 articles. execCreateEntity minted a fresh attribute ID (GUID) for every attribute on each run, even unchanged ones. Mendix's DB synchronizer keys off attribute identity: a new ID for an unchanged attribute reads as "attribute departed + new attribute added", so it drops and re-adds the column, discarding the data. Only the entity's own ID was preserved. On CREATE OR MODIFY of an existing entity, reuse each retained attribute's existing ID (matched by name); only genuinely new attributes get a fresh ID. attrNameToID then propagates the reused ID into validation rules and indexes, so those stay stable too. An identical re-run is now identity-idempotent — no spurious sync, no data loss. Also steer the "already exists in project" duplicate hint toward `alter entity … add attribute` for entities (the non-destructive path). Includes fix-issue.md symptom rows for this and the two preceding high-severity fixes (ALTER PAGE case-insensitivity, JS-action serialization). Reported from the RSS-reader build (finding #13). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
A parameter name accepted bare by `call microflow M.Foo(Source = $x)`
was a parse error in a widget's OnClick/Action argument list:
container r (OnClick: microflow M.Foo(Source: $x))
-> no viable alternative at input 'OnClick'
The affected names — View, Source, Item, Page, Entity — are all lexer
keywords. The widget-argument name rule (microflowArgV3) accepted only
(IDENTIFIER | QUOTED_IDENTIFIER), while call-microflow's callArgument
used the permissive identifierOrKeyword. Same identifier, two grammars:
keyword-named params failed the widget rule but passed call-microflow.
The misattributed error also anchored on OnClick (a valid property)
rather than the offending token, which made it very hard to diagnose.
Widen microflowArgV3 to identifierOrKeyword (matching callArgument) and
read the name via identifierOrKeywordText in the visitor. Keyword-named
params now parse unquoted in widget action args, and the wrong-token
error position disappears for these names.
Reported from the RSS-reader build (findings #2 and #3).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…DGET13) A widget expression property (dynamicclasses / visibleif / editableif) that walks an association passed `mxcli check --references` and then failed the build with CE0117 "Error(s) in expression." — Mendix client-side expressions cannot follow associations. This is easy to trip over because a data binding (contentparams) on the same widget may traverse the same association legitimately. Add MDL-WIDGET13: scan the expression-typed widget properties for a module-qualified association step between slashes (`/Module.Assoc/`). A plain attribute (`$obj/Slug`) or a qualified enum literal (`Mod.Enum.Value`, no leading slash) does not match, so data bindings and enum comparisons are not flagged. The message points at the offending step and suggests denormalising onto the bound entity or using a data binding. Reported from the RSS-reader build (finding #4). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Three checker hints steered authors the wrong way: - MDL001 (nested loop) recommended `retrieve $Match from $List where … limit 1`, which is a parse error — a plain retrieve cannot filter a list variable. Recommend `$Match = FIND($List, <cond>)` (in-memory, O(N)). Same broken advice fixed in CLAUDE.md idiom #2. - MDL044 flagged `count()` used in an expression with "Did you mean 'round()'?" — count is an aggregate activity, not a typo. For the known aggregates (count/sum/average/minimum/maximum) emit "assign it to a variable first: $n = count($List);" instead of a did-you-mean. - MDL044's generic hint cited `mxcli syntax expressions`, which does not exist; point it at `mxcli syntax microflow`. Also lock in finding #5's second trigger: a microflow-call output variable reused across a fallback chain (try A, else try B) is CE0111. It was already caught by the shared create-output-variable check (`check --references`); add TestValidateDuplicateMicroflowCallOutputVar (same-scope + nested-in-if) as a regression guard and document the correct pattern (one variable per call + a plain set) in write-microflows.md. Reported from the RSS-reader build (findings #7, #8, #14c, #5). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
`placeholder:` and `onchange:` on a textbox passed check (with an MDL-WIDGET07 "silently dropped" warning) and then vanished — DESCRIBE showed neither. The design's live-search box degraded to a button and the field lost its hint text. Both are standard Mendix TextBox properties. The pages.TextBox model already carried Placeholder/OnChangeAction and the modelsdk (default) engine's widgetToGen already serialized them (mirroring TextArea/DatePicker/CheckBox) — but the grammar had no onchange:/placeholder: property, buildTextBoxV3 never populated the model, the legacy serializeTextBox hardcoded empty values, and the two keys weren't in the known-props list. - Grammar: add `ONCHANGE COLON actionExprV3` + `PLACEHOLDER COLON STRING_LITERAL` to widgetPropertyV3 (the tokens already existed). - Visitor: store Properties["OnChange"]/["Placeholder"]; add GetOnChange()/GetPlaceholder() accessors. - Builder: buildTextBoxV3 sets tb.Placeholder (a *model.Text) and tb.OnChangeAction (via buildClientActionV3, reusing the button path). - Legacy engine: serializeTextBox emits serializePlaceholderTemplate + serializeClientAction from the model instead of empties. - Add OnChange/Placeholder to staticWidgetKnownProps so MDL-WIDGET07 stops firing. Reported from the RSS-reader build (finding #9). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…ltiline-string hint Three separate authoring papercuts from the RSS-reader build: - #10 The CLAUDE.md/AGENTS.md template that `mxcli init` writes into every project contradicted the shipped skills: it listed `DECLARE $Entity Module.Entity;` and `DECLARE $List List of ... = empty;` as supported (both are rejected — MDL043/CE0053 and MDL040) and listed `CASE … END CASE` as NOT supported (it IS — the enum split). Move the two DECLARE forms to the NOT-supported table with the correct "use a parameter / retrieve / create" guidance, and list CASE as supported. - #12 write-microflows.md documented the `$latestHttpResponse` body attribute as lowercase `content`; the real name is `Content` (inherited from System.HttpMessage, so DESCRIBE ENTITY System.HttpResponse doesn't list it). Lowercase fails CE0117. Fix both mentions and note why. - #6 A string literal that runs off the end of its line got the "unescaped apostrophe — double your apostrophes" hint, which only makes it worse. Detect a newline inside the token-recognition-error payload and emit a distinct "string literals cannot span multiple lines" hint instead. The single-line apostrophe heuristic is unchanged. Reported from the RSS-reader build (findings #10, #12, #6). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…pying `mxcli new` copied the full ~111MB binary into every project's ./mxcli (gitignored, but a full duplicate per project). On Linux, prefer a hard link: it shares the inode (no duplication on the same filesystem), is a real ELF the devcontainer can exec, and survives the original binary being moved or removed (unlike a symlink). Fall back to a full copy across filesystems (EXDEV) or when linking isn't possible. Reported from the RSS-reader build (finding #14a). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
A `call javascript action` / `call java action` with a wrong-cased or misspelled parameter name passed `check --references` — the action itself resolved — but wrote a dangling reference that only failed the Mendix build with CE1613 "The selected … parameter … no longer exists". Example: NanoflowCommons.OpenURL's parameter is `Url`, but `(url = $x)` slipped through. The reference checker resolved only the action NAME (the qualified-name builders discard the parameter list) and never compared the call's written argument names to the action's declared parameters. Carry each call's argument names on the ref collector (codeActionCallRef); after the name-exists check passes, read the action's declared parameters (ReadJavaScriptActionByName / ReadJavaActionByName → Parameters[].Name) and diff case-sensitively. A casing-only mismatch suggests the correct spelling; System.* runtime actions are skipped, and the check degrades to no error when the backend can't report parameters. References-mode only. RSS-reader follow-up finding (verification note on #11). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
`DESCRIBE PAGE` omitted a textbox's placeholder and onchange even though both are written correctly (they are in the .mxunit and the live app honours them) — so a DESCRIBE round-trip wasn't a reliable way to confirm the write landed (verification note on the #9 fix). The describe read path (parseRawWidget) only read LabelTemplate and AttributeRef for a textbox; PlaceholderTemplate and OnChangeAction were never read back. Add Placeholder/OnChange to the rawWidget describe model, read the placeholder via extractTextFromTemplate (same as the label) and the onchange via a key-agnostic renderClientActionMDL — refactored out of extractButtonAction, since OnChangeAction is the same client-action type as a button's Action, just under a different BSON key — and emit `Placeholder:` / `OnChange:` in the TextBox case. DESCRIBE reads raw BSON via GetRawUnit, so one path serves both engines. RSS-reader follow-up (verification note on #9). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
`DESCRIBE PAGE` of a dynamictext bound to a non-String attribute
(Integer/DateTime/…) emitted the rendered template expression:
ContentParams: [{1} = toString($currentObject/CountAll)]
Re-applying that made mxcli treat `toString($currentObject/CountAll)` as
an attribute NAME, failing the build with CE1613 "The selected attribute
'…toString($currentObject/CountAll)' no longer exists." A string binding
round-tripped fine (bare attribute name). On the real Reader page this hit
all six non-string bindings at once.
The write side converts a non-String attribute binding to
`toString($currentObject/Attr)` (dynamictext template parameters must be
String); the describe reader emitted that Expression verbatim instead of
reversing the transform.
Unwrap the exact auto-generated forms — `toString($currentObject/<path>)`
→ `<path>`, `toString($param/<attr>)` → `$param.attr` — in
extractClientTemplateParameters, so DESCRIBE round-trips (the write side
re-derives the toString on the next apply). Any other expression (extra
text, a hand-written toString) is left untouched.
RSS-reader follow-up finding (found verifying the #9/DESCRIBE work).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The `--trace` console exporter omits start/end timestamps and parent span IDs, so call trees and durations can't be reconstructed from it — for flame charts you had to hand-set three OTEL_* env vars (OTEL_TRACES_EXPORTER=otlp, OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_EXPORTER_OTLP_ENDPOINT). Add `mxcli run --local --trace-otlp <endpoint>` (implies --trace): it switches the traces exporter to OTLP and points it at the collector (protocol http/protobuf). User-set OTEL_* still take precedence, so a fully hand-rolled env keeps working; without the flag the console default is unchanged. Reported from the sudoku project (OpenTelemetry tracing follow-up on the console-exporter limitation). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
Plain `refresh catalog` leaves the analytic tables empty (0 rows, no error); a query then silently returns nothing. `warnIfCatalogModeInsufficient` already warns for the FULL-only views, but `xpath_expressions` was missing from `fullOnlyTables` — so `SELECT … FROM CATALOG.XPATH_EXPRESSIONS` in fast mode returned empty with no hint (activities/refs were covered, but xpath was not). Add `xpath_expressions` to `fullOnlyTables` so a fast-mode query against it warns "requires refresh catalog full" like the sibling analytic tables. TestFullOnlyTablesCoverage guards that every FULL-gated view (activities/refs/xpath_expressions/widgets) is flagged and a fast-mode table (entities) is not. Reported from the sudoku "app warehouse" finding (#30) — the REFRESH-CATALOG-FULL silent-empty papercut. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
The catalog is a plain SQLite database at <projectDir>/.mxcli/catalog.db — exporting it to JSON first (as one might reach for) is strictly worse: it loses most of the tables, adds human-readable chatter, and goes stale. Document attaching it directly, plus an external DuckDB "app warehouse" recipe that joins the catalog to the app's Postgres and the OTLP trace dump read-only for cross-source questions no single source can answer (mxcli does not embed DuckDB — this is a dev-container recipe). Also flag, in both the use-cases page and the REFRESH CATALOG page, that FULL mode is what populates the analytic tables (activities/refs/ xpath_expressions/widgets) — plain refresh leaves them empty. Reported from the sudoku "app warehouse" finding (#30). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
…alog) Analyzing an app's runtime behavior is a recurring procedure spanning four signals — the runtime log, Prometheus metrics, OpenTelemetry traces, and the catalog (model shape) — so it belongs in a skill, not scattered across feature docs. Add `.claude/skills/mendix/analyze-runtime.md`: when to reach for each signal, how to collect it (`run --local` + `--metrics` / `--trace` / `--trace-otlp`, `refresh catalog full`), the cross-source "app warehouse" join (external DuckDB — mxcli does not embed it), the logs↔traces gap, and a decision guide mapping questions to tools. Registered in the skills README (Specialized Skills + loading guide); synced into user projects by `mxcli init`. Trim the docs-site catalog "app warehouse" block to the catalog-attach feature note plus a pointer to the skill, so the full procedure has a single canonical home. Follow-up to the sudoku "app warehouse" finding (#30). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXnEgoc2NQP1Y2TWMCMXC4
run --local: --trace-otlp to export traces to an OTLP collector
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings the 17 commits on ako/main since the last sync (#788). Clean merge against
main; each fix has a Go regression test and (where applicable) an MDL repro under
mdl-examples/bug-tests/. Two rounds of findings, all verified by the reporters:Authoring / write-path fixes (RSS-reader)
set X on <builtin>now matches property names case-insensitively(was a hard error on every built-in widget).
mdl/backend/pagemutatorcall javascript actionis serialized on the default modelsdk engine (waspersisted as
-- Empty action→ CE0008/CE0109). Microflows + nanoflows.create or modify persistent entitypreserves attribute identity — no moredestructive DB column drop/re-add on an identical re-run (data loss).
textboxplaceholder + onchange are supported end-to-end (were silently dropped).View/Source/Item/Page/Entity)unquoted, matching
call microflow.Checker (
check --references)dynamicclasses/
visibleif/editableif) is flagged (CE0117) before the build.parameters (a wrong/mis-cased name wrote a dangling ref → CE1613).
FIND(...)) and MDL044 (aggregate-aware) hints; fixed thedead
mxcli syntax expressionsreference; documented fallback-chain CE0111.DESCRIBE round-trip
(
toString($currentObject/Attr)→ bare attribute) now round-trip — re-applying aDESCRIBE dump no longer corrupts the page (was CE1613).
Diagnostics / docs
"double your apostrophes"); corrected the generated CLAUDE.md template and the
$latestHttpResponse/Contentcasing.Tooling & observability (sudoku)
mxcli newhard-links the binary into new projects (no ~111 MB copy each).mxcli run --local --trace-otlp <endpoint>exports traces to an OTLP collector(the console exporter can't do flame charts). Implies
--trace; userOTEL_*wins.xpath_expressionsis flagged as FULL-only (a fast-mode query warnedfor
activities/refsbut not xpath → silent empty results).analyze-runtimeskill (logs + metrics + traces + catalog + the cross-source"app warehouse" join) and a catalog direct-attach docs recipe.
🤖 Generated with Claude Code