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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m

## Unreleased

- fix(doctor, preflight, platform-link): **a clean `fix property-sets --dry-run` on Mendix below 10.24.24 is no longer reported as "every unit has the property names Studio Pro writes".** mxcli's property table starts at 10.24.24, so on 10.24.18 (mxcli v0.27.0) the dry-run printed "Every unit already has the property names Studio Pro writes" while Mendix's `mx diff` exited 129 on a unit mxcli had written (mendixlabs/mxcli#1595). `doctor.sh --property-sets`, `--if-stale` and `--quick` now WARN "NOT PROOF" and name `mx diff <base>.mpr <mine>.mpr out.json` (exit 0) as the proof; the receipt records `unproven`; exit stays 0. `learned-detection-gaps.md` and `platform-link.md` said v0.26.0 closed the gap. Fixture P2b (11 cases). Baseline-routed, +18 words. — saingge
- new(model-scope): **a write's whitespace-only units can now be found and put back.** mxcli v0.27.0's `alter entity … rename attribute` rewrote 135 units in 20 modules of a Mendix 10.24.18 app that never referenced the attribute; the only change in each was the line breaks between bracketed XPath conditions, and `mxcli diff-local` showed nothing (mendixlabs/mxcli#1596). New `project-bin/model-scope.py`: `report` sorts each changed unit into real or noise (every differing field is an XPath constraint that is equal once whitespace outside string literals is removed) and prints the real changes per module; `restore` checks the noise units out of `HEAD` and updates their ContentsHash rows through `mxpatch.py`; `check --staged` exits 1 when noise is staged (`MODEL_SCOPE_OK=1` lets one commit through). Not wired into `exec.sh` or the pre-commit hook yet. Fixture `tests/wave2/test-model-scope.sh`, with a golden before/after capture of a real v0.27.0 rename on a public model (`fixtures/model-scope/golden-v0.27.0/`) — saingge
- fix(graph-sweep, lint): **the orphan list now says a flow used only by a pluggable widget's datasource or action property may have no catalog edge, and prints the one `mxcli search` that finds the page (it needs a `REFRESH CATALOG FULL SOURCE` catalog; this sweep reads a FULL one, so the note says to rebuild).** The caveat named only layout snippet calls, so a live flow read as "strong evidence of deadness" (`graph-sweep.sh` comment, `review-report.js` orphan detail, and the `lint-that-actually-runs.md` QUAL004 row, which only said the catalog "likely misses" the ref). Observed on a Mendix 10.24 app with mxcli v0.27.0: 5 live flows used only as a Gallery's or a third-party widget's microflow datasource had 0 `refs` rows, `show references to` said "no references found", `describe page` printed each, and QUAL004 flagged each. A Gallery with a microflow datasource and no microflow `onClick` had its ref (15 of 15); with both, only the `onClick` ref (2 of 2); the two Data grid 2 microflow datasources had theirs. The QUAL004 row now states that and links [mendixlabs/mxcli#1513](https://github.com/mendixlabs/mxcli/issues/1513) (open; reported for Data grid 2 on v0.26.0); the ledger entry and the pending-issue draft now say they are filed. Pinned by `test-catalog-edge-guard.sh` case 1 (fails on 393d253). — saingge
- fix(bug-lookup): **`bin/bug-lookup.sh` also searches the project's own `bug-logs/**.md` drafts, marked `(project draft: <file>:<line>)`, after the ledger's output.** It searched only the toolkit ledger, so a bug a session had already written down in the project was invisible to the lookup run before the next experiment on the same error; the ledger's output and ranking are unchanged (drafts are ranked in a separate pass), `--full` and `--index` stay ledger-only, and `MXTK_PROJECT_DIR=` turns drafts off. Field: a Mendix 10.24 app with mxcli v0.27.0, 43 files in `bug-logs/`; the query "cannot be spliced" listed 0 project drafts at 393d253 and lists 3 (3 distinct files) now, each pointing at a `## ` line; the project was not written. Pinned by group P of `tests/wave2/test-bug-lookup.sh` (3 of its checks fail on 393d253) on synthetic drafts; the run under /bin/bash 3.2.57 passes, other awks not run. — saingge
Expand Down
41 changes: 36 additions & 5 deletions bin/doctor.sh
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,9 @@ probe_runs() {
# The result is recorded in <project>/.claude/.property-sets-receipt (machine-local, like the
# doctor receipt): line 1 "<date> <time> <result> <detail>", line 2 "mxcli: <--version line>".
# Line 2 is what --if-stale and --quick compare: the check is owed again when mxcli changes.
# Only a settled answer is recorded — clean, needs, failed, or unsupported (the support probe
# Only a settled answer is recorded — clean, unproven, needs, failed, or unsupported (the support probe
# ran and the subcommand is absent). An error or a timeout is never written, and --if-stale
# skips the run only on a recorded clean/unsupported, so a one-off failure (locked model,
# skips the run only on a recorded clean/unproven/unsupported, so a one-off failure (locked model,
# unreadable version, a probe that crashed) is re-checked on the next sync instead of being
# repeated from the receipt until mxcli changes.
#
Expand Down Expand Up @@ -254,8 +254,28 @@ ps_classify() {
PS_DETAIL="exit $1: $(printf '%s\n' "$2" | grep -v -e '^[[:space:]]*$' -e '^WARNING: This is a' | head -1)"
fi
}
# ps_below_floor <version> — 0 when the Mendix version is older than 10.24.24, where mxcli's
# property table starts (mendixlabs/mxcli#1595), so a clean dry-run there proves nothing. 1 when
# it is not older, or the version is not a plain a.b.c[.d].
# Deliberately not gated on the mxcli version: a later release that still has the defect must not
# go silently green. Retire this check (and the warning) when mendixlabs/mxcli#1595 is fixed.
ps_below_floor() {
case "$1" in [0-9]*.[0-9]*.[0-9]*) ;; *) return 1 ;; esac
_pf_a="${1%%.*}"; _pf_r="${1#*.}"; _pf_b="${_pf_r%%.*}"; _pf_r="${_pf_r#*.}"; _pf_c="${_pf_r%%.*}"
case "$_pf_a$_pf_b$_pf_c" in *[!0-9]*) return 1 ;; esac
[ "$_pf_a" -lt 10 ] && return 0
[ "$_pf_a" -gt 10 ] && return 1
[ "$_pf_b" -lt 24 ] && return 0
[ "$_pf_b" -gt 24 ] && return 1
[ "$_pf_c" -lt 24 ]
}
# ps_unproven_warn <detail> <version> — the warning for a clean dry-run below the floor.
ps_unproven_warn() {
warn "property sets: $1 — NOT PROOF: this model (Mendix ${2:-?}) is below 10.24.24, where mxcli's property table starts, so units mxcli writes can still lack keys (mendixlabs/mxcli#1595)"
note "the proof is Mendix's own: mx diff <base>.mpr <mine>.mpr out.json with this version's mx; exit 0 is clean."
}
ps_write_receipt() {
case "$PS_RESULT" in clean|needs|failed|unsupported) ;; *) return 0 ;; esac
case "$PS_RESULT" in clean|unproven|needs|failed|unsupported) ;; *) return 0 ;; esac
[ -n "$PS_VER" ] || return 0
[ -n "$PROJECT_DIR" ] && [ -d "$PROJECT_DIR" ] || return 0
mkdir -p "$PROJECT_DIR/.claude" 2>/dev/null || return 0
Expand Down Expand Up @@ -300,7 +320,13 @@ ps_check() {
fi
ps_classify "$_psk_rc" "$PS_OUT"
case "$PS_RESULT" in
clean) ok "property sets: $PS_DETAIL — every unit has the property names Studio Pro writes" ;;
clean)
_psk_mv="$(mxtk_model_version "$MPR" 2>/dev/null || true)"
if ps_below_floor "$_psk_mv"; then
PS_RESULT=unproven; ps_unproven_warn "$PS_DETAIL" "$_psk_mv"
else
ok "property sets: $PS_DETAIL — every unit has the property names Studio Pro writes"
fi ;;
needs|failed)
warn "property sets: $PS_DETAIL — Studio Pro's merge/diff can throw on these"
printf '%s\n' "$PS_OUT" | grep -E '^ (NEEDS|FAILED) ' | head -10 | while IFS= read -r l; do note "$l"; done
Expand Down Expand Up @@ -335,7 +361,7 @@ ps_apply() {
ps_check
}
ps_exit_code() {
case "$PS_RESULT" in clean) echo 0 ;; not-run) echo 2 ;; unsupported) echo 3 ;; needs|failed) echo 1 ;; *) echo 4 ;; esac
case "$PS_RESULT" in clean|unproven) echo 0 ;; not-run) echo 2 ;; unsupported) echo 3 ;; needs|failed) echo 1 ;; *) echo 4 ;; esac
}

# --property-sets / --fix-property-sets: only this check, then exit. Stands alone so
Expand Down Expand Up @@ -374,6 +400,9 @@ if [ -n "$PS_MODE" ]; then
clean|unsupported)
ok "already checked for $PS_VER: $(sed -n 1p "$(ps_receipt)")"
exit "$(ps_exit_code)" ;;
unproven)
ps_unproven_warn "already checked for $PS_VER: $(sed -n 1p "$(ps_receipt)")" "$(mxtk_model_version "$MPR" 2>/dev/null || true)"
exit "$(ps_exit_code)" ;;
esac
fi
fi
Expand Down Expand Up @@ -1302,6 +1331,8 @@ if [ -n "$PROJECT_DIR" ]; then
_psq="$(sed -n 1p "$(ps_receipt)")"
case "$_psq" in
*" clean "*|*" clean") ok "property sets: last check for this mxcli was clean ($_psq)" ;;
*" unproven "*) warn "property sets: last check for this mxcli was clean, but the model is below Mendix 10.24.24, so that proves nothing (mendixlabs/mxcli#1595)"
note "run: mx diff <base>.mpr <mine>.mpr out.json; exit 0 is clean" ;;
*" unsupported "*) note "property sets: this mxcli cannot check them (v0.26.0+ needed)" ;;
*) warn "property sets: last check for this mxcli: $_psq"
note "repair: bin/doctor.sh --fix-property-sets $PROJECT_DIR" ;;
Expand Down
2 changes: 1 addition & 1 deletion skills/learned-detection-gaps.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Every row was found in the field: **a green result only certifies what its rung
| A pluggable **combo box filter with `lazyLoading: true`** whose options data source also sets the **preset value** | rungs 1–4, lint, and every scripted check — the model is valid and the list *is* filtered | rung 6, by eye, **before touching the filter**: the list is already narrowed by the preset while the filter looks empty (no value, no clear button) until its menu is first opened | Lazy loading defers the options source, so the preset it sets is not rendered until first open. Turn lazy loading off for short lists (≤10 options). Before the build, check every pluggable property that changes *when* data loads (lazy loading, load-on-open), on any widget that should show a value on page load | field run (jopterhorst), 2026-10, [#299](https://github.com/MendixMau/mxcli-project-toolkit/issues/299) |
| A widget **inserted into an existing container** whose theme pins `height` on **positional row classes** (`widget-row-N` and kin) | rungs 1–4, lint, and every scripted check — the MDL insert is correct and the widget renders | rung 6, by eye: the next row overlaps the inserted widget and covers part of it (here the filter's clear button) | The theme sized rows by position, not content, so a taller row 1 runs under row 2. Before an insert into an existing container, grep the theme SCSS for fixed `height` on that container's row/position classes; if found, add a `:has(<new widget>)` rule adjusting both rows | field run (jopterhorst), 2026-10, [#299](https://github.com/MendixMau/mxcli-project-toolkit/issues/299) |
| A **`Class` (or `Style`) on a `SNIPPETCALL`** — `alter page … set (Class: 'x') on <snippetcall>` | rungs 1–4, lint, **and DESCRIBE**, which shows the class on the call | rung 6, a DOM probe: `document.querySelector('.x')` is null on every page that carries it | A snippet call renders **no element of its own**; the snippet's widgets are inlined into the parent, so the class has nothing to sit on. Put the class on the container around the call, or on the snippet's own top-level container. Found when a stepper marker on 6 snippet calls changed nothing on screen with every gate green | field run, mxcli v0.26.0, 2026-10-14, [#336](https://github.com/MendixMau/mxcli-project-toolkit/issues/336) |
| Units **mxcli < v0.26** wrote | rungs 1–6 | Studio Pro merge/diff: *"… do not have the same properties"* | after mxcli upgrades, before merging: `bin/doctor.sh --property-sets <project>`; `platform-link.md` | #295 |
| Units **mxcli < v0.26** wrote, and any mxcli's on **Mendix below 10.24.24** | rungs 1–6 | Studio Pro merge/diff: *"… do not have the same properties"* | after mxcli upgrades, before merging: `bin/doctor.sh --property-sets <project>`; `platform-link.md`. Below 10.24.24 "clean" proves nothing; `mx diff` exit 0 does | #295, mxcli#1595 |

## Operating rules that fall out of this table

Expand Down
6 changes: 5 additions & 1 deletion skills/platform-link.md
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,11 @@ runtime all pass. Studio Pro's merge and diff then throw — *"Objects with ID
same properties"* — once Studio Pro has saved such a document, and the Version Control panel
fails. Field case: 78 changed units written with v0.24 on a production app, with no gate able to
see them. mxcli v0.26.0 completes the set on every unit it writes, but only on units it rewrites,
so a project upgraded mid-flight still carries the old ones until the repair runs.
so a project upgraded mid-flight still carries the old ones until the repair runs. **Not on
Mendix 10.24 below 10.24.24, through mxcli v0.27.0:** its property table starts at 10.24.24, so on
10.24.18 a unit it writes can still lack keys, and `fix property-sets --dry-run` (hence
`doctor --property-sets`) calls every unit complete (mendixlabs/mxcli#1595). There, the proof is
Mendix's own `mx diff <base>.mpr <mine>.mpr out.json` against the base branch: exit 0 is clean.
`sync-project.sh` runs the read-only check after every toolkit pull until it comes back clean
for the installed mxcli. After that it runs again only when mxcli changes. Errors and timeouts
are never recorded, so the next run re-checks. `doctor --quick`, which exec.sh runs when the mxcli
Expand Down
31 changes: 31 additions & 0 deletions tests/wave2/test-property-sets.sh
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,37 @@ grep -qx '/.claude/.property-sets-receipt' "$P/.gitignore" 2>/dev/null && ok "re
printf '%s' "$OUT" | grep -q "added /.claude/.property-sets-receipt to the project's .gitignore" && ok "the .gitignore append is announced" || bad "silent .gitignore append" "$OUT"
grep -q -- 'property-sets -p .*/App.mpr --dry-run' "$P/.stub-log" && ok "the check is the --dry-run" || bad "dry-run not used" "$(cat "$P/.stub-log")"

echo "== P2b a clean dry-run below Mendix 10.24.24 is not proof (mendixlabs/mxcli#1595) =="
. "$TK/bin/lib/portable.sh"; require_py
setver() { # <project> <version> — give the stub project's .mpr a readable _ProductVersion
rm -f "$1/App.mpr"
"$PY" -c 'import sqlite3,sys; c=sqlite3.connect(sys.argv[1]); c.execute("create table _MetaData (_ProductVersion text)"); c.execute("insert into _MetaData values (?)",(sys.argv[2],)); c.commit()' "$1/App.mpr" "$2"
}
P="$(mkproj below)"; echo clean > "$P/.stub-state"; setver "$P" 10.24.18.103793
OUT="$("$DOCTOR" --property-sets "$P" 2>&1)"; RC=$?
[ "$RC" -eq 0 ] && printf '%s' "$OUT" | grep -q "WARN property sets:.*NOT PROOF.*Mendix 10.24.18.103793.*mxcli#1595" \
&& ok "10.24.18 clean: exit 0 but a WARN saying it proves nothing" || bad "10.24.18 clean: rc=$RC" "$OUT"
printf '%s' "$OUT" | grep -q "every unit has the property names Studio Pro writes" && bad "10.24.18 still prints the false green" "$OUT" || ok "10.24.18: no 'every unit has the property names' line"
printf '%s' "$OUT" | grep -q "mx diff <base>.mpr <mine>.mpr" && ok "names mx diff as the proof" || bad "no mx diff pointer" "$OUT"
sed -n 1p "$P/.claude/.property-sets-receipt" | grep -q " unproven " && ok "receipt records unproven, not clean" || bad "receipt" "$(cat "$P/.claude/.property-sets-receipt" 2>&1)"
n1="$(grep -c -- '--dry-run' "$P/.stub-log")"
OUT="$("$DOCTOR" --property-sets --if-stale "$P" 2>&1)"; n2="$(grep -c -- '--dry-run' "$P/.stub-log")"
[ "$n1" -eq "$n2" ] && printf '%s' "$OUT" | grep -q "WARN property sets: already checked.*NOT PROOF" && ! printf '%s' "$OUT" | grep -q "ok already checked" \
&& ok "--if-stale: not re-run, and still a WARN, never an ok" || bad "--if-stale unproven ($n1 -> $n2)" "$OUT"
OUT="$("$DOCTOR" --quick "$P" 2>&1)"
printf '%s' "$OUT" | grep -q "WARN property sets: last check for this mxcli was clean, but the model is below Mendix 10.24.24" && ok "--quick: WARN, not the ok line" || bad "--quick unproven" "$(printf '%s' "$OUT" | grep -i -A2 'property')"
for v in 10.24.23 9.24.2; do
P="$(mkproj below-$v)"; echo clean > "$P/.stub-state"; setver "$P" "$v"
OUT="$("$DOCTOR" --property-sets "$P" 2>&1)"
printf '%s' "$OUT" | grep -q "NOT PROOF" && ok "$v: below the floor, warned" || bad "$v not warned" "$OUT"
done
for v in 10.24.24 10.25.1 11.12.1; do
P="$(mkproj at-$v)"; echo clean > "$P/.stub-state"; setver "$P" "$v"
OUT="$("$DOCTOR" --property-sets "$P" 2>&1)"
printf '%s' "$OUT" | grep -q "ok property sets: Checked 3 unit(s).*every unit has the property names" && ! printf '%s' "$OUT" | grep -q "NOT PROOF" \
&& ok "$v: at or above the floor, still ok" || bad "$v wrongly warned" "$OUT"
done

echo "== P3 units need completing =="
P="$(mkproj needs)"
OUT="$("$DOCTOR" --property-sets "$P" 2>&1)"; RC=$?
Expand Down
Loading