Skip to content

Sync ako/mxcli: incremental catalog refresh (5.7 s → 0.7 s), E015 string-function-vs-empty warning, Windows bootstrap under Git Bash - #1682

Merged
ako merged 13 commits into
mendixlabs:mainfrom
ako:main
Oct 30, 2026
Merged

ako merged 13 commits into
mendixlabs:mainfrom
ako:main

Conversation

@ako

@ako ako commented Oct 30, 2026

Copy link
Copy Markdown
Collaborator

Syncs ako/mxcli:main into upstream: 6 commits from fork PRs ako#1816, ako#1817 and ako#1820.

Fixes #1680. Refs #580.

Incremental catalog refresh

Problem: every catalog refresh rebuilt every table from every document after any save. On a 4,753-unit app that was about 4.5 s of decoding and inserting, and 5.6–8 s end to end. This is the second step after the unit store.

Fix:

  • Row ownership: each catalog row is recorded under the document it came from.

  • What an incremental refresh rebuilds: it loads the cached catalog and deletes only:

    • the rows of the changed documents;
    • the rows of documents whose refs name a changed document;
    • the project-wide rows.

    It then rebuilds those.

  • Full-build fallback: anything the plan can't vouch for falls back to a full build. That covers folder, module, settings, security and navigation changes, another mxcli build or catalog level, and FORCE.

Refresh after one entity edit Before After
refresh catalog full 5.6–5.8 s 0.70 s
refresh catalog full source 7.3–7.9 s 0.71 s

Correctness:

  • Equivalence: TestCatalog_IncrementalBuildEqualsFullBuild and its fast-mode twin compare every table with a forced full build, across 19 kinds of edit. Each step runs a second time with no change.

  • Ref coverage: cross-document reads are recorded, and coverage is checked. It is 100% on the fixture and 99.4% on two real projects.

    • The trace found that inheritance splits had no refs edge to their entity, so show references missed those flows too. They now get a new split edge.
  • Performance guards: three tests fail if a rule of the incremental build is broken:

    • every row must belong to a document;
    • the decode budget;
    • no whole-project listings in the passes.

    Breaking one of these would leave the catalog correct but slow, so no correctness test would notice.

  • Docs: ADR-0014 records the decision and its downsides. /mxcli-dev:review gains a "Catalog builder passes" checklist.

Notes:

  • Row order and the internal Id columns of refs and permissions may differ from a full build.
  • An incremental refresh prints one incremental: N units rebuilt, M carried over line instead of per-table counts.
  • The Windows gain isn't measured.

E015: a String function's result compared with empty (refs #580)

The problem: if trim($Value) != empty then … passes check, exec and mx check, but is always true. These functions return '', never empty. Measured on 10.18.5 with mxcli test --local:

expression empty '' ' '
trim '' '' ''
toLowerCase / toUpperCase / substring / replaceAll '' '' text
toString(<empty Integer>) '' — —
$v + '' 'null' '' text
$v (control) empty '' text

The new warning:

  • E015 string-func-vs-empty is a warning, because the model is valid. It fires only for the measured functions.
  • It is skipped when the same and/or chain also compares the call with '', the guard idiom. Otherwise it was noise on a Studio Pro-authored fixture.

Not a warning: + with an empty operand gives the text null. That is documented as pitfall #18 instead, because whether an operand is empty can't be known at check time.

Not measured: Mendix 11.x and nanoflows.

init: the bootstrap script downloads the Windows asset under Git Bash (#1680)

The problem: the script built the release asset name from uname -s. Under Git Bash, MSYS2 and Cygwin that gives mingw64_nt-…, never windows, so the download returned 404. The hook runs with || true, so the session started without mxcli and nothing said why.

The fix:

  • MINGW*, MSYS* and CYGWIN* now map to windows, with the .exe suffix.
  • The binary is kept as ./mxcli.exe on Windows.
  • A stale Linux ./mxcli is removed first. Otherwise Git Bash resolves ./mxcli to that file.

Tests: they run the generated script with a fake uname and curl, for Git Bash (64- and 32-bit), MSYS2, Cygwin and Windows on ARM, with Linux and macOS as the controls.

Validation

make ci-local passes, except for two cmd/mxcli/docker TestSyncJavaDependencies_* tests that fail the same way on main in the authors' environment

claude and others added 13 commits October 30, 2026 13:10
…y (E015)

`if trim($Value) != empty` passed check, exec and mx check, and is always
true: trim, toLowerCase, toUpperCase, substring, replaceAll and toString
return '' for an empty input, never empty. Measured on the Mendix 10.18.5
runtime with `mxcli test --local`, the bare variable as the control.

E015 is a warning: the model is valid and builds, only the logic is wrong.
The fix it suggests, comparing with '', also covers an empty input.

Pitfalls gain the E015 case and a second measured one that check cannot
warn about: `+` renders an empty operand as the text 'null'.

Refs mendixlabs#580

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82rJKSgq9KUoUNdLnrL
The SessionStart bootstrap script built the release asset name from
`uname -s`, which is MINGW64_NT-10.0-26200 (or MSYS_NT-/CYGWIN_NT-) on
Windows, so it requested mxcli-mingw64_nt-10.0-26200-amd64 and got a
404. The hook runs under `|| true`, so the session then started without
mxcli.

Detect the platform once at the top of the script: map MINGW*/MSYS*/
CYGWIN* to windows with an .exe suffix, and keep the binary path in
$MXCLI (./mxcli, or ./mxcli.exe on Windows) for every guard, link,
download and invocation. On Windows, a non-executable ./mxcli (the
Linux build `mxcli new` leaves) is removed before installing
./mxcli.exe, since Git Bash would otherwise resolve ./mxcli to it.

Fixes mendixlabs#1680

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82tr6TGuPpuRmehciwH
`trim($v) != empty and trim($v) != ''` has a dead first half, but the
'' comparison decides it and the condition does what it says. E015
reported it, which failed TestExprCheckRoundTrip_DescribeProducesZeroHints
on the Studio Pro-authored fixture (FeedbackModule.VAL_Feedback): noise on
real projects, not the silent wrong result the rule exists for.

The check now runs over the finished tree and is skipped when the same
and/or chain compares the same call with ''. A guard on another call or
in another chain still reports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82rJKSgq9KUoUNdLnrL
exprcheck: warn when a String function's result is compared with empty (E015)
Every refresh rebuilt every catalog table from every unit after any save:
about 4.5 s of decoding and inserting on a 4,753-unit project, 5.6-8 s end
to end. Each row is now recorded under the unit it came from (a wrapper on
the build's transaction; passes mark their owner with own()), and the next
build loads the cached catalog, deletes the rows of the changed units, the
units whose refs name them and the project-wide rows, and rebuilds only
those, listing and decoding only those through a unit-scoped backend view.
Anything the plan cannot vouch for falls back to a full build. A refresh
after one edit on that project now takes 0.7 s, with or without the source
index.

Tests compare every table with a forced full build over 19 kinds of edit
in full and fast mode; guards count unowned rows, units decoded per
incremental refresh and whole-project listings. ADR-0014 records the
decision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82ttaGzay5CwiC2V7cf
…es (#1804)

An incremental build rebuilds a document when a unit its refs name changes,
so refs must cover every other document a pass reads for it. The lookups
that read other documents (the member-ref index, association ends, entity
and flow names by ID) now report each name they read per document, and
DependencyCoverage checks every read the way the plan would.
TestCatalog_RefsCoverEveryCrossDocumentRead holds the fixture at 100%;
TestCatalog_RefCoverageOfProject measures a real project on demand (99.4%
on FactoryManagement and EVORA - ICX).

The trace found that a flow branching on a specialization (`split type $x
when M.E`) had no edge to the entity, which `show references` missed too:
it gets a `split` edge now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82ttaGzay5CwiC2V7cf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oK82ttaGzay5CwiC2V7cf
fix(init): bootstrap script downloads the Windows asset under Git Bash
Rebuild only the changed units on a catalog refresh (#1804)
@ako
ako merged commit 763288f into mendixlabs:main Oct 30, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants