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
2 changes: 1 addition & 1 deletion .claude/skills/design-mdl-syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ Worked example — the REST call, migrated:
$Customer = call rest service post 'https://api.example.com/customers/{1}' with ({1} = $Id) (
Headers: ('Content-Type': 'application/json'),
Authentication: basic (Username: @Shop.ApiUser, Password: @Shop.ApiPassword),
Body: template '{"name": "{1}"}' with ({1} = $Name),
Body: template '{{"name": "{1}"}' with ({1} = $Name),
Timeout: 30,
) returns mapping Shop.IMM_Customer as Shop.Customer
on error rollback;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/executor", "date": "2026-10-27", "symptom": "`log info node 'Mod' 'Item {number} not found';` and a REST POST's `Body: template '{}'` printed \"Check passed!\"; mx check then reported [CE0712] \"Brace should be followed by a number of digits or another brace.\" at the Log message / Call REST service activity (ako/mxcli#1397, mendixlabs/mxcli#1515; reported on 11.12.5 and 11.15). Four of mxcli's own docs taught the failing JSON body `template '{\"name\": \"{1}\"}'`, and MDL-WF21 / MDL-WIDGET04 refused the legal `{{1}}` as an unfilled {1}.", "cause": "Nothing modelled the template grammar: the only template checks counted placeholders with a `\\{(\\d+)\\}` regexp (CE0720 for workflow texts and dynamic text/button captions), which never sees a malformed brace and counts the escaped `{{1}}` as a placeholder. Microflow templates (log, show message, validation feedback, send email, REST URL/body, Java action text template) had no placeholder check at all.", "file": "`mdl/texttemplate/texttemplate.go` (Check, Placeholders — the grammar), `mdl/executor/template_text_grammar.go` (templateTextRefusal = the one decision, MDL-TEMPLATE02, checkTemplateGrammar / refuseTemplateGrammar), wired in validate_microflow/nanoflow/alter_flow/program, the four builders in cmd_microflows_builder_calls.go and javaActionTemplateValue; `validate_workflow_templates.go` and `validate_widgets.go` use the same scanner; tests `mdl/texttemplate/texttemplate_test.go`, `mdl/executor/template_text_grammar_test.go`; fixtures `mdl-examples/bug-tests/template-braces-1397-brace-grammar{,.fail}.mdl`", "insight": "Measure the slot x input matrix in ONE project with `mx check -j` (one document per text, the JSON names the document): 60 inputs x up to 12 slots took one mxbuild run per version, and 10.24.24, 11.13.0 and 11.15.0 agreed on every cell they shared (478 between 11.13 and 11.15, 540 between 10.24 and 11.15), which is what licenses one shared grammar instead of per-slot rules. The full CE family is in Mendix.Modeler.Texts.dll (`strings -el | grep -i brace`): CE0712 non-digit after `{`, CE0713 digits then end, CE0714 digits then not `}`, CE0716 `{0}`, CE0717 past int32 (also a non-ASCII digit, which IsDigit accepts and Parse refuses), CE0720 index > params — one per text, first in reading order, CE0720 only for an otherwise clean text. The surprising accepts are the controls that decide the predicate: a lone `}`, `}}`, a `{` that ENDS the text, `{01}` and `{{1}}` all build, so a regexp in either direction over-fires. A matrix parser keyed on letters-only case names silently dropped every case with a digit in its name and made `{2147483646}` look accepted — check the harness against a row you know before trusting a surprising cell. Pluggable widget textTemplates are not covered: mxcli rewrites their named `{Attr}` placeholders to `{1}` first. Side find: mxcli-written workflows do not load on 10.24.24 (TypeCacheUnknownTypeException Workflows$StartWorkflowActivity).", "refs": ["ako/mxcli#1397", "mendixlabs/mxcli#1515", "ako/mxcli#1714", "ako/mxcli#650"], "ce": ["CE0712", "CE0713", "CE0714", "CE0716", "CE0717", "CE0720"], "rules": ["MDL-TEMPLATE02", "MDL-WF21", "MDL-WIDGET04"], "test": "TestCheck_MeasuredAgainstMxbuild, TestTemplateGrammar_MicroflowSlots, TestTemplateGrammar_InEveryFlowKind, TestTemplateGrammar_JavaActionTemplateArgument, TestWorkflowTemplates_BraceGrammar, TestValidateClientTemplateGrammar"}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/exprcheck", "date": "2026-10-27", "symptom": "mendixlabs/mxcli#1515 item 3: \"a lone \\ in replaceAll was stored as an unterminated or wrong regex and failed at runtime ('Failed to evaluate expression'); a JSON escape needs the 4-backslash and 8-backslash forms because replaceAll is a regex replace\". Asked: warn on a replacement whose backslash escapes nothing.", "cause": "Half the premise is wrong. The Mendix runtime's replaceAll/replaceFirst (10.24.24, 11.13.0, 11.15.0) is text.replaceAll(regex, Matcher.quoteReplacement(replacement)), and the nanoflow client escapes `$` before String.replace: the REPLACEMENT is literal text, so '\\' or '$1' there is inserted as written. Only the PATTERN is a regex, and one ending in a lone backslash builds (mx check 11.13.0: 0 errors) and throws PatternSyntaxException \"Unescaped trailing backslash\" on every call (client: \"\\ at end of pattern\"); isMatch too. The 4/8-backslash forms are mdl 0's `\\\\` = one backslash applied to the correct 2/4.", "file": "`mdl/exprcheck/regex_backslash.go` (TrailingBackslashRegexes), `mdl/executor/validate_regex_backslash.go` (MDL-REGEX01 warning, from checkExprFunctions so microflows and nanoflows share it); `cmd/mxcli/syntax/features_microflow.go` (new microflow.expressions topic); tests `mdl/exprcheck/regex_backslash_test.go`, `mdl/executor/validate_regex_backslash_test.go`; fixture `mdl-examples/bug-tests/regex-backslash-1397-replaceall.mdl`", "insight": "Settle a runtime-semantics claim by CALLING the runtime, not by reasoning about Java: mxbuild ships the runtime jars, and com.mendix.languages.expressions.StringFunctions$/DateTimeFunctions$ can be driven from a ten-line Java program (classpath: mxruntime, internal-public-api, public-api, scala-library, scala3-library; a java.lang.reflect.Proxy EvaluationContext answering mathContext/locale is enough). `javap -c` on the anon class showed Matcher.quoteReplacement before any run. The same probe settled the issue's aside: millisecondsBetween..weeksBetween return the ABSOLUTE difference on the server (11.13/11.15) and the client (`.sub().abs().div()` in mxui.js). What is stored also needs measuring, not assuming: `mxcli bson dump` shows mdl 1 stores a literal as written and mdl 0 halves `\\\\`, so the check judges the stored text (microflowExprSource), which makes one rule correct under both headers. Item 2 (toString(Decimal) into a bounded String attribute) was not done: attribute values are ResolveBy:AttributeOf slots nothing enforces, and no tier knows an attribute's String length — the catalog reader has kind but not length, and the no-project tier tracks no script entities.", "refs": ["ako/mxcli#1397", "mendixlabs/mxcli#1515"], "rules": ["MDL-REGEX01", "MDL-V1-ESCAPE"], "test": "TestTrailingBackslashRegexes, TestRegexTrailingBackslash"}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "modelsdk/property", "date": "2026-10-27", "symptom": "A codec-created page stores CanvasWidth/CanvasHeight and PopupWidth/PopupHeight as BSON int32 where every Studio Pro page stores int64; within one page CREATE (codec, int32) and ALTER's page-level SET (page mutator, int64) disagree. mx check is 0 errors either way (ako/mxcli#1264, mendixlabs/mxcli#675). Not only pages: every Integer property the codec writes (flow connection indices, TabIndex, layout-grid weights, ListView PageSize, DecimalPrecision, GlyphIcon Code, mapping MinOccurs/LevelOfParent, workflow thresholds via addInt32) came out int32", "cause": "modelsdk/gen binds the metamodel's Integer as property.Primitive[int32] (304+ bindings, 331 reached through the registry), and Primitive.BSONValue returned p.Get() unchanged, so the Go width became the stored width. The read side (DecodeInt32) already accepted int32/int64/double, which is why nothing failed on read. The backend's addInt32 helper is a Primitive[int32] too, so the raw-built mapping/workflow/rest elements inherited the same width", "file": "`modelsdk/property/primitive.go` (BSONValue widens int32 -> int64); reader `mdl/backend/modelsdk/mapping_read.go` (customHandlerFromRaw LevelOfParent); `modelsdk/codec/bsondoc.go` (GetInt32); `mdl/backend/pagemutator/mutator.go` (MaxFileSize default width); tests `modelsdk/codec/intwidth_1264_test.go`, `mdl/backend/modelsdk/int_width_1264_test.go`", "insight": "**Census before choosing the scope, and the census settles it**: an earlier finding said 'keep a stored int's width (int32 vs int64)', which reads as if Studio Pro stored some Integers as int32 and argued for an exception list. Walking 15,354 Studio Pro-authored units (a pristine `mx create-project` 11.13.0 app plus the .mpr inside eight .mpk packages, Mendix 10.6.4-11.13.0) found 208,038 integer values with typed-array markers excluded, all int64 and 0 int32 (the only other numbers: 154 doubles, all NewSortIndex, which gen binds float64). Every one of the 137 gen Integer properties that occurs is int64-only, so the blanket widening needs no exceptions; the 'keep the width' rule exists only for documents an OLDER mxcli wrote. Corroborated by canon's studiopro_property_defaults.json: when `mx convert` fills an absent Integer it writes $numberLong. **Enumerate the class from the registry, not from a list**: TestEncoder_EveryIntegerPropertyIsInt64 walks codec.DefaultRegistry (new TypeNames()) and sets every Primitive[int32] it finds - 331 properties on 173 types, more than a static grep of gen found (304 on 162), with a >=300 floor against a vacuous pass. **Widening the writer breaks narrow readers of mxcli's OWN output**: customHandlerFromRaw read LevelOfParent with Int32OK only, so it was already wrong for every Studio Pro document (ancestor binding read as source 'parent', level -1) and would have become wrong for mxcli's too; TestCustomHandler_AncestorLevelRoundTrips is the encode->read round trip that catches it. Sweep `.(int32)`, `Int32OK()`, `.Int32()` (panics on int64 - one test did) and `!= int32(` interface comparisons, then ignore the `[0]` marker probes. Five tests pinned int32 on the encoded side; each asserted a width Studio Pro never writes (GlyphIcon Code 570/570 int64, FileManager MaxFileSize 26/26, DecimalPrecision 4,856/4,856). **canon is width-blind** (renderScalar formats with %v), so int32-vs-int64 never counted as a change: no Replaced->Unchanged shift and no new churn, but also no migration - an mxcli-written int32 stays on disk until the element is next genuinely rewritten. mx check accepts both widths, so the e2e evidence is a BSON census of the written project on a fixed and a fault-forced build (pristine 11.13.0 app, the bug-test plus eight doctype scripts): fault-forced 2,970 int32 values on 67 (type, key), fixed 0, mx check 0 errors on BOTH. The sharpest symptom is not on CREATE: under the old build a title-only describe->edit->exec of a Studio Pro page (MyFirstModule.Home_Web) narrowed all 154 of its integers to int32, so every genuine edit of a Studio Pro document carried a whole-document width diff; the fixed build keeps 154/154 int64", "refs": ["ako/mxcli#1264", "mendixlabs/mxcli#675", "#661", "#713"]}
2 changes: 1 addition & 1 deletion .claude/skills/implement-mdl-feature.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ Example design for REST call:
$response = call rest service get 'http://api.example.com/data' (
Headers: ('Content-Type': 'application/json', 'Accept': 'application/json'),
Authentication: basic (Username: $username, Password: $password),
Body: template '{"key": "{1}"}' with ({1} = $value),
Body: template '{{"key": "{1}"}' with ({1} = $value),
Timeout: 30,
) returns string
on error rollback;
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/mendix/rest-client/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ $response = call rest service get 'https://api.example.com/users/{1}' with (
-- POST with body
$response = call rest service post 'https://api.example.com/items' (
Headers: ('Content-Type': 'application/json'),
Body: template '{"name": "test"}',
Body: template '{{"name": "test"}', -- a literal { is written {{
) returns string;

-- With basic auth
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/mendix/write-microflows/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ log error 'Error message';

-- With node name
log info node 'OrderService' 'Processing order';
log warning node 'ValidationService' 'Invalid data detected';
log warning node 'ValidationService' 'Item {1} is invalid' with ({1} = $Nr); -- a { is {n} or {{ (CE0712, pitfalls §19)

-- With variables (use concatenation)
log info node 'OrderService' 'Order processed: ' + $OrderNumber;
Expand Down
43 changes: 43 additions & 0 deletions .claude/skills/mendix/write-microflows/reference/pitfalls.md
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,49 @@ declare $Label string = 'Customer: ' + $Customer/Name;
declare $Label string = if $Customer/Name != empty then 'Customer: ' + $Customer/Name else 'Customer: ';
```

### 19. A Brace in a Template Is a Placeholder or `{{`

In a template's text a `{` opens a numbered placeholder or is escaped as `{{`; a `}`
needs no escape. `{number}`, `{}`, `{ 1 }` and an unescaped JSON object fail the build
with CE0712 (CE0713/0714/0716/0717/0720 for the other malformed placeholders), in every
template slot — log, show message, validation feedback, send email, a REST call's URL and
body, a Java action's text template, workflow texts, a dynamic text and a button caption.
`check` reports it as MDL-TEMPLATE02 with mxbuild's code (ako/mxcli#1397).

```mdl
-- ❌ WRONG: CE0712 — a name is not a placeholder, and the JSON brace is not escaped
log info node 'Orders' 'Item {number} not found';
$R = call rest service post 'https://example.com/x' (Body: template '{"name": "{1}"}' with ({1} = $Name)) returns String;

-- ✅ CORRECT
log info node 'Orders' 'Item {1} not found' with ({1} = $Number);
$R = call rest service post 'https://example.com/x' (Body: template '{{"name": "{1}"}' with ({1} = $Name)) returns String;
```

An expression is not a template: in `'{name} = ' + $Name` the brace is plain text.

### 20. `replaceAll`'s Pattern Is a Regex; Its Replacement Is Plain Text

The second argument of `replaceAll`, `replaceFirst` and `isMatch` is a regular
expression; the replacement is inserted as written — a `\` or `$` in it is literal
(measured on the Mendix runtime and the nanoflow client). Under `mdl 1;` a string
literal is stored as written; without the header `\\` is one backslash.

```mdl
mdl 1;
-- escape a text for JSON — backslashes first, then quotes
create microflow Mod.ToJsonString ($S: String) returns String
begin
declare $T String = replaceAll($S, '\\', '\\'); -- regex \\ matches one \, inserts \\
return replaceAll($T, '"', '\"'); -- inserts \"
end;
```

A regex that ends in a lone backslash (`'\'`) builds and fails every time it runs
(PatternSyntaxException); `check` warns MDL-REGEX01. The `*Between` functions
(`secondsBetween`, `minutesBetween`, …) return the absolute difference, whichever date
comes first — compare the dates for the direction.

## Implicit Variable Creation (CE0111 Duplicate Variable)

These statements **implicitly create a new variable** with the name on the left side:
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

### Changed

- **SEC011 names the widget whose data source fails on every page** — it uses the catalog's new `WidgetName` (ako/mxcli#1879) instead of guessing; before, a page with two data widgets over the same entity was reported as "A data source on page …" with no way to tell which grid to fix.
- **Concord support is removed; `--mcp-check` and `--mcp-run` use Studio Pro's own MCP server** — the optional second MCP server (Concord) and the `--mcp-concord`, `--mcp-concord-dial` and `--mcp-save` flags are gone. PED writes each change to disk as it lands, so there is nothing to save. `--mcp-check` runs a project-wide `ped_check_errors` (Studio Pro 11.15+), and `--mcp-run` starts the app through the `mendix_app` handler and prints its URL (11.16+).
- **The first command on a project says it is building the unit index** (ako/mxcli#1806) — after an upgrade to the `unit-index-2.idx` format, or on a fresh clone, one command opens every unit file to build the index, which on Windows is a visible stall. It now prints one line on stderr (`Building the unit index for this project's N units (once; …)`); `-q` / `MXCLI_QUIET` keep it silent.

Expand All @@ -41,6 +42,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

- **A rewrite gives a stored list the marker Studio Pro writes** (ako/mxcli#1640) — a list an older mxcli stored with the wrong typed-array marker kept it through every later rewrite, because a rewrite copies much of a stored document as bytes (an unchanged pluggable widget's Type and Object, an existing element's unchanged properties). A Data grid 2's `SortBar.SortItems` stayed 3 where Studio Pro stores 2, and `drop page` plus the same MDL was the only way out. Every write now applies the measured owner/key markers to the whole unit, and lists without a measured marker are left as they are. A populated nanoflow parameter list (on a call nanoflow action or a nanoflow data source) is now written with 2, as Studio Pro does. mx check never reported either; the effect was a document that differed from Studio Pro's at the value level.
- **MPR v2: a unit written twice in one `modelsdk` write transaction commits the second write** — writing a unit A → B → A in one `WriteTransaction` committed B. The second write was compared with the committed file rather than with the transaction's own first write, which sits in a temp file until commit, so it matched A and was skipped as unchanged. It is now compared with the pending write. No mxcli command or `modelsdk` call writes a unit twice in one transaction, so this was reachable only by using `WriteTransaction` directly.
- **Integer properties are stored as 64-bit integers, as Studio Pro stores them** (ako/mxcli#1264, mendixlabs/mxcli#675) — every Integer property mxcli wrote, a page's canvas and popup sizes among them, was a 32-bit BSON integer where Studio Pro writes 64-bit, so an edit of a Studio Pro document changed the width of all its numbers. `mx check` accepts both. A document an older mxcli wrote is not migrated, and reads accept either width. Reading a mapping's ancestor binding (`LevelOfParent`) from a Studio Pro document no longer gives level -1.
- **`check` catches a template brace the build rejects** (ako/mxcli#1397, mendixlabs/mxcli#1515) — `'Item {number} not found'` in a log message, or `Body: template '{}'` in a REST call, passed `check` and failed the build with CE0712. MDL-TEMPLATE02 now reports mxbuild's code for every template slot, and exec refuses the same texts. A workflow text or caption with an escaped `{{1}}` is no longer refused.
- **`check` warns on a `replaceAll` pattern that ends in a lone backslash** (ako/mxcli#1397, mendixlabs/mxcli#1515) — it builds, then fails on every call at runtime. MDL-REGEX01 covers `replaceAll`, `replaceFirst` and `isMatch`. The replacement is literal text in Mendix, so a backslash there is not flagged; `mxcli syntax microflow expressions` explains both.

## [0.29.0] - 2026-10-30

Expand Down
5 changes: 4 additions & 1 deletion cmd/mxcli/syntax/features_integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,10 @@ func init() {
"-- template '{1}' with the expression as its parameter — what Studio Pro\n" +
"-- stores — and describe prints that form. An expression has no {n} to\n" +
"-- fill, so one followed by WITH (...) is refused (MDL-REST05).\n" +
"-- `Body: TEMPLATE '$Payload'` is the literal text $Payload.\n\n" +
"-- `Body: TEMPLATE '$Payload'` is the literal text $Payload.\n" +
"-- In a template a `{` opens a numbered placeholder or is escaped as `{{`;\n" +
"-- a JSON body is '{{\"name\": \"{1}\"}' (a `}` needs no escape). '{\"a\": 1}'\n" +
"-- and '{}' fail the build with CE0712 — check reports MDL-TEMPLATE02.\n\n" +
"RETURNS String -- the response body as a string\n" +
"RETURNS response -- the whole System.HttpResponse object\n" +
"RETURNS Module.MyFile -- store the body in a file document\n" +
Expand Down
Loading
Loading