Skip to content
Merged
3 changes: 3 additions & 0 deletions .claude/skills/fix-issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -512,3 +512,6 @@ extracting `OffsetExpression`/`LimitExpression`.
| A hand-curved sequence flow does not survive a rewrite — `DESCRIBE` output is identical before and after the curve is drawn in Studio Pro, and re-running unchanged MDL straightens the edge | Mendix stores NO waypoints. A flow's shape is two bezier control vectors on its `Microflows$BezierCurve` line (`OriginControlVector`/`DestinationControlVector`, `"x;y"`). Both writers already emitted them and the legacy parser already read them, but nothing could SET them, so they defaulted to `"0;0"` and every rebuild flattened the curve — measured by patching `"40;-90"` into the stored line and re-executing the same script, which returned `"0;0"` | `mdl/ast/ast_microflow.go` (`FlowCurve`, `ActivityAnnotations.Curve`/`InvalidCurves`), `mdl/visitor/visitor_microflow_statements.go` (`parseCurveAnnotation`, `annotationPointValue`), `mdl/executor/cmd_microflows_builder_annotations.go` (`curveByOrigin`, `applyFlowCurves`, and the `mergeStatementAnnotations` copy), `cmd_microflows_builder_graph.go` (the one call), `cmd_microflows_show_helpers.go` (`emitCurveAnnotation`), `mdl/backend/modelsdk/microflow.go` (read the Line back) | **Check what the storage can represent before designing the syntax** — the request asked for "edge waypoints", which do not exist; `SequenceFlow` has two control vectors, so `@curve(from: (x, y), to: (x, y))` is the only shape that maps. Same lesson as #872's anchors. **Look for an existing annotation shape before touching the grammar**: `name: (x, y)` is already `annotationParenValue`, so `@curve` needed ZERO grammar changes. **Record against the ACTIVITY and stamp flows in one pass** — threading a curve alongside the anchor would mean editing all seven sites that create a flow (`previousStmtAnchor`, `nextFlowAnchor`, branch and loop variants) and missing one silently straightens that edge; `applyPendingAnnotations` already runs at every activity, so there is exactly one place to get right. **A new field on `ActivityAnnotations` must be copied in `mergeStatementAnnotations`** — it is an explicit field-by-field copy, so the first cut parsed the curve and wrote nothing. **Unit tests that call the function directly do NOT prove the wiring**: deleting both call sites left `TestEmitCurveAnnotation` and `TestApplyFlowCurves…` green, which is the "a test that only passes against fixed code" trap in its purest form — `TestCurveReachesTheFlowThroughTheBuilder` (MDL text → real builder) and `TestCurveIsEmittedByTheAnnotationEmitter` (through `emitObjectAnnotations`) fail when unwired. **Still open**: a curve drawn in Studio Pro is preserved only once the script names it, since a rebuilt flow has no stable identity to match on; DESCRIBE now surfaces it so it can be captured. Tests `mdl/executor/cmd_microflows_curve_test.go`, example `mdl-examples/bug-tests/884-annotations-and-lint-planes.mdl`. upstream #884 |
| `mxcli run --local --ensure-db` fails on a freshly built `mxcli init` dev container — there is no PostgreSQL service to start and no `postgres` superuser — even though `psql` is on PATH | The generated Dockerfile installed `postgresql-client` **only**. `EnsureDatabase` starts a local service (`service postgresql start`) and provisions the role + database through `sudo -u postgres psql`, both of which need the **server** package. `psql` being present makes the container look correctly provisioned | `cmd/mxcli/tool_templates.go` (`generateDockerfile`) | Install `postgresql` alongside `postgresql-client`. **Generalisable**: when a feature shells out to a service, assert the *server* package in the image template, not the client that happens to satisfy a `LookPath` check — the CI/web image having the server is what hid this (`/usr/lib/postgresql/16` is present there but not in the generated dev container). Guarded by `TestGenerateDockerfile_PostgresServer`, which asserts the server package for both the docker and podman variants |
| The implicit merge that closes a split — the end-if join — is placed by the layout pass and cannot be moved; it routinely lands on top of a neighbouring activity, and `@position` does not address it | The statement's own `@position` belongs to the SPLIT, so the merge had no annotation of its own. Three split builders computed `mergeX/centerY` with no override (`addIfStatement`, `addEnumSplit`, `addStructuredInheritanceSplit`), and DESCRIBE emitted nothing for it, so even a hand-moved merge was recomputed on the next exec | `mdl/ast/ast_microflow.go` (`ActivityAnnotations.Merge`), `mdl/visitor/visitor_microflow_statements.go` (`case "merge"`), `mdl/executor/cmd_microflows_builder_annotations.go` (`mergePosition`, and the `mergeStatementAnnotations` copy), the three split builders, `cmd_microflows_show_helpers.go` (`emitMergeAnnotation`, `commonMergeAfter`) | **Authoring without the DESCRIBE half is not a fix** — the first attempt shipped `@merge` writing correctly and was REVERTED, because the describer drops it and the layout pass then recomputes the merge on the next exec; that is the same round-trip data loss as #872/#881/#882, introduced by the change meant to help. **Find the relationship from data already in scope rather than threading a map**: the describer's `splitMergeMap` is not available at `emitObjectAnnotations`, and threading it would mean editing ten-plus call sites (the multi-site trap); `commonMergeAfter` walks the split's branches to the nearest merge reachable from ALL of them, using the `flowsByOrigin` and `activityMap` already passed in. **Bound any walk over a flow graph** — a retry loop makes it cyclic, so the walk carries a per-branch visited set and a node cap, pinned by `TestCommonMergeAfterTerminatesOnACycle`. **One helper for every site that places the merge**, so the override cannot be honoured at one split type and ignored at another. **Test the WIRING, not the helper**: `mergePosition` and `emitMergeAnnotation` called directly pass with every call site removed — `TestMergeReachesTheCanvasThroughTheBuilder` (MDL text → real builder) and `TestMergeIsEmittedByTheAnnotationEmitter` (through `emitObjectAnnotations`) fail when unwired, verified by removing each. Tests `mdl/executor/cmd_microflows_merge_test.go`, example `mdl-examples/bug-tests/884-annotations-and-lint-planes.mdl`. upstream #884 |
| `DESCRIBE PAGE` output will not re-parse for any page carrying a **pluggable widget**: `mxcli check` on it fails with `extraneous input ':'` / `extraneous input '('` from the first widget onward. Separately, a boolean property the author set is missing from the description entirely | Two independent defects in the same path. **Emit**: explicit properties were written with a raw `%s`, so every string lost its quotes — a JSON `spec: {"a": 1}` then broke the parse at its first brace. **Read**: `extractExplicitProperties` skipped any value of `"true"`/`"false"` as a "common default", so booleans never reached the output | `mdl/executor/cmd_pages_describe_output.go` (`explicitPropValue`, `isBareLiteral`), `mdl/executor/cmd_pages_describe_pluggable.go` (`buildPropertyValueTypeMap`, `extractExplicitProperties`), `mdl/executor/cmd_pages_describe.go` (`rawExplicitProp.ValueType`) | **Fixing one half alone is worse than the bug.** Quote without emitting booleans and the description re-parses cleanly while silently dropping a property — a wrong page that validates. Both halves ship together or neither. **Quote by the DECLARED type, never the value's shape**: `ValueType.Type` sits in the widget's `Type.ObjectType.PropertyTypes`, the same array `buildPropertyTypeKeyMap` already walks for `PropertyKey` and throws away; a String property holding `"30"` or `"true"` is indistinguishable from a number once it is a string in BSON, and must still come back quoted. Where no type is declared, fall back to the value's shape and quote anything not plainly numeric or boolean — quoting is the safe direction, since an unquoted arbitrary string may not parse at all. **The round trip is the test, not the output**: describe → `check` → `exec` → describe must be byte-identical and leave `mx check` at 0 errors. Tests `mdl/executor/cmd_pages_describe_pluggable_roundtrip_test.go`; example `mdl-examples/bug-tests/pluggable-describe-roundtrip.mdl`, verified end to end on 11.12.1. **Uncovered while verifying, NOT fixed**: giving a property in a conditionally shown group a non-default value writes a widget Mendix rejects with CE0463 — ProgressCircle's `showLabel: true` and `labelType: 'percentage'` both do it with no DESCRIBE involved, while the same widget's General-group properties take non-default values happily. Reported in mxcli-ledger FINDINGS #104 |
| `DESCRIBE PAGE` output will not re-parse for any page carrying a **pluggable widget**: `mxcli check` on it fails with `extraneous input ':'` / `extraneous input '('` from the first widget onward. Separately, a boolean property the author set is missing from the description entirely | Two independent defects in the same path. **Emit**: explicit properties were written with a raw `%s`, so every string lost its quotes — a JSON `spec: {"a": 1}` then broke the parse at its first brace. **Read**: `extractExplicitProperties` skipped any value of `"true"`/`"false"` as a "common default", so booleans never reached the output | `mdl/executor/cmd_pages_describe_output.go` (`explicitPropValue`, `isBareLiteral`), `mdl/executor/cmd_pages_describe_pluggable.go` (`buildPropertyValueTypeMap`, `extractExplicitProperties`), `mdl/executor/cmd_pages_describe.go` (`rawExplicitProp.ValueType`) | **Fixing one half alone is worse than the bug.** Quote without emitting booleans and the description re-parses cleanly while silently dropping a property — a wrong page that validates. Both halves ship together or neither. **Quote by the DECLARED type, never the value's shape**: `ValueType.Type` sits in the widget's `Type.ObjectType.PropertyTypes`, the same array `buildPropertyTypeKeyMap` already walks for `PropertyKey` and throws away; a String property holding `"30"` or `"true"` is indistinguishable from a number once it is a string in BSON, and must still come back quoted. Where no type is declared, fall back to the value's shape and quote anything not plainly numeric or boolean — quoting is the safe direction, since an unquoted arbitrary string may not parse at all. **The round trip is the test, not the output**: describe → `check` → `exec` → describe must be byte-identical and leave `mx check` at 0 errors. Tests `mdl/executor/cmd_pages_describe_pluggable_roundtrip_test.go`; example `mdl-examples/bug-tests/pluggable-describe-roundtrip.mdl`, verified end to end on 11.12.1. Reported in mxcli-ledger FINDINGS #104 |
| Authoring a pluggable widget property that lives in a **conditionally shown group** writes a widget Mendix rejects with **CE0463**, while the same widget's other properties take non-default values happily. On ProgressCircle both `showLabel: true` and `labelType: 'percentage'` do it; `showLabel: false` and General-group properties are clean. No DESCRIBE involved | Two gaps on the same axis. **Serialization**: #574 nulls the TextTemplate of a HIDDEN conditional property, but left a VISIBLE one null — and Mendix stores an empty `Forms$ClientTemplate` there. Null and empty are each invalid in the other's state, so nulling hidden ones was only half the rule. **Extraction**: the editorConfig reader did not understand a ternary's ELSE branch (`cond ? (…) : hidePropertiesIn([…])`), so the `showLabel` gate was never seen and `labelText` read as visible whenever `labelType` was `"text"` — its default | `mdl/backend/widgetobj/builder.go` (`ApplyVisibilityRules`, `bsonFieldIsNil`), `mdl/executor/editorconfig_extract.go` (`parseGuard` `:` case, `ternaryCondition`, `trailingExpr`) | **Fixing one gap alone inverts the bug rather than closing it** — filling visible templates without the missing gate made `showLabel: false` fail where `true` had, because mxcli still thought labelText was visible. Measured both ways round before and after; a fix that moves which case fails is not a fix. **Let Mendix say what the shape should be**: `mx update-widgets` on a COPY of the failing project reconciles the widget, and diffing that against mxcli's output named the single meaningful path (`Object/Properties[N]/Value/TextTemplate` null vs `Forms$ClientTemplate`) out of 969. **The good/bad control does the isolation for free**: authoring the same widget with the boolean both ways gave two documents differing in exactly ONE path, so no other candidate needed testing. **Only CONDITIONAL properties are filled** — Studio Pro's convention for an unset TextTemplate is not uniform (a DataGrid custom-content column stores null for `tooltip` and an empty template for `exportValue`, per `emptyClientTemplateRules`), so filling every unset one would trade this bug for its mirror image. **The extractor's preamble matters**: the ternary is preceded by a whole `switch`, and walking back past the `?` to the function start yields a fragment with an unbalanced `}` that parses to nothing and looks like "unsupported shape" — hence `trailingExpr`. Regression signal: the widgetdemo showcase applies with **0 CE0463** (its 4 CE1613 are a pre-existing attribute reference). Tests `mdl/backend/widgetobj/widget_visibility_test.go`, `mdl/executor/editorconfig_extract_test.go`; example `mdl-examples/bug-tests/pluggable-describe-roundtrip.mdl` now exercises the group. Reported in mxcli-ledger FINDINGS #104 follow-on |
4 changes: 2 additions & 2 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:

- uses: actions/setup-go@v7
with:
go-version: '1.26.5'
go-version: '1.26.6'

- name: Cache ANTLR4 JAR
uses: actions/cache@v6
Expand Down Expand Up @@ -58,7 +58,7 @@ jobs:

- uses: actions/setup-go@v7
with:
go-version: '1.26.5'
go-version: '1.26.6'

- uses: oven-sh/setup-bun@v2

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/push-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version: '1.26.5'
go-version: '1.26.6'
- name: Cache ANTLR4 JAR
uses: actions/cache@v6
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-go@v7
with:
go-version: '1.26.5'
go-version: '1.26.6'
- uses: oven-sh/setup-bun@v2
- name: Cache ANTLR4 JAR
uses: actions/cache@v6
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Changed

- **Go toolchain 1.26.5 → 1.26.6** for GO-2026-6218 (`net/url`), GO-2026-6090 (`crypto/tls`), GO-2026-6089 (`net/http`), GO-2026-6088 (`encoding/xml`), GO-2026-5972 (`encoding/asn1`) and GO-2026-5026 (`net/http`, via `golang.org/x/net/idna`). All six are standard-library advisories fixed in go1.26.6; no mxcli code changed. Bumped in `go.mod` and in all three workflows (`push-test`, `release`, `nightly`) together, so released binaries are not still linked against the vulnerable standard library.

## [0.17.0] - 2026-08-10

Headline: **A full Mendix build-and-test loop that fits on an iPad** — you can now design, build, run, observe, and debug a multi-app Mendix solution end-to-end from Claude Code on the web, on a phone or tablet, with no local IDE. Two capabilities make it possible: an **external browser preview** that reverse-tunnels a locally-running app out to a public URL from an egress-only container, and a **short agentic feedback loop** — a warm Docker-free runtime, sub-second microflow unit tests, live log/metric/trace observation, and a name-based microflow debugger — so an agent gets an answer in seconds instead of a build round-trip.
Expand Down
1 change: 1 addition & 0 deletions cmd/mxcli/lsp_completions_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading