diff --git a/CHANGELOG.md b/CHANGELOG.md index c2390840..00c531b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,49 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m ## Unreleased +- learn(page-patterns, file-upload-widget): **the last two unpromoted traps from the 2026-10-12 retro**: a snippet's top-level text has no context object, so wrap it in a DataView on the snippet parameter (`learned-page-patterns.md`); and on the Nova theme the File Uploader's three icon variables point at `/resources/images/` files nothing ships, so the icons are blank with three 404s while every gate stays green (`learned-file-upload-widget.md` traps table). The other retro facts were already covered: watch-mode and calculated attributes (`ui-loop.md`), Datagrid 2 / Atlas input traps (lint UI001), licence session cap. On-demand files, 0 baseline words. — retro 2026-10-12, Nova trap from AlvinnYeo (#282) +- learn(microflow-patterns): **an object saved only through `commitInSeparateDatabaseTransaction` is still new in the user's request**; after browser Back the runtime checks Create on it, and a role without Create gets "Attempt to illegally add an object to the state" (HTTP 560) on every server-backed widget of the page. Fix: `rollback $X refresh` before the microflow ends; never grant Create or `commit` the request's copy. Added beside "Commit / Change / Rollback" (on demand; `rules-microflow.md` re-rendered). The proposed `learned-detection-gaps.md` register row is baseline text and waits for its A/B. — AlvinnYeo (#565) +- learn(mendix-agent-ui): **new section "Stock popup variant (`layout-chat-popup`)"**: the popup header text is the popup page's Title; its `h4` is invisible by default (same colour as the header background) and is fixed with a token-scoped rule in `main.scss`, never a literal or `!important`; the width is a shared `!default` variable set in `custom-variables.scss`, and it also widens the sidebar chat. One checklist line added. The routing trigger is unchanged (that would add words to the mdl agent stub). On-demand file, 0 baseline words. — joshuamoesa (#574) +- learn(brd-to-build-plan): **Step 0 says a widget-only marketplace package (e.g. Charts) installs as `widgets/.mpk` and never appears in `SHOW MODULES`**, so its build-plan row is verified by the file (`ls widgets/Charts.mpk`), not by `SHOW MODULES` — a row checked that way never passed and needed a manual acceptance. On-demand file, 0 baseline words. — jopterhorst (#510) +- fix(platform-link): **trap 3 no longer says no API creates a first cloud environment.** With a PAT, Deploy API v1 `POST /api/1/apps {"ProjectId": …}` created the Free App sandbox (the v4 calls still 404/405); the trap now names that call, links the file's own Free App section that quotes it, and keeps deploys manual (`POST /api/1/apps//packages` → `500 BUILD_FAILED` on a Free App). On-demand file, 0 baseline words. — jopterhorst (#516) +- fix(preflight, detection-gaps, exec): **mxcli v0.27+ `exec` is all or nothing; STOP #22, the detection-gaps register row, `iterative-build-loop.md`'s gate invariants and `exec.sh`'s failed-exec message no longer say it is "not transactional".** STOP #22 keeps its rule (`create or modify` everywhere, one owner per document, from-empty replay) and now says why on v0.27+: any write failure rolls back the whole run, so one non-idempotent statement, even placed last, fails every re-run and drops every edit in it. `exec.sh` stops printing "PARTIAL APPLICATION LIKELY" and says which behaviour applies by version; the gate still runs. Baseline 63,877 → 63,872 words; `skills/generated/rules-*.md` re-rendered. — AlvinnYeo (#539) +- fix(doctor): **the look-hook check splits `settings.json` per `"command"` before testing**, so a single-line file holding both the installed and a relative look hook is flagged, and **a hand-written look-hook variant now gets "edit it by hand"** instead of only `--hooks-only`, which never rewrites it (part of #502). L8–L9 in `tests/wave2/test-look-hook-cwd.sh`. From the PR #501 review — MendixMau +- fix(status): **`bin/status.sh` WATCHes when the lint debt is git-ignored** — `lint debt is not versioned: git-ignored .claude/lint-baseline.json (.gitignore:2 '/.claude/'), …` for the baseline, `lint-acceptances.tsv` and project-local `lint-rules/`, so a `/.claude/` line in a project's `.gitignore` no longer silently drops the ratchet and the record of accepted rises from git (#523). A WATCH line only, nothing blocks. New fixture `tests/wave2/test-status-lint-unversioned.sh`. Reported by AlvinnYeo +- fix(lint): **the toolkit's CONV010 rule (`conv010_act_microflow_content.star`) now allows `NanoflowCallAction`**, so an ACT_ nanoflow that delegates to a SUB_ nanoflow is no longer flagged as business logic — the same delegation the rule already allows for microflows, and what mxcli's stock copy allows (#512). Reproduced and verified with mxcli v0.28.0 on a scratch copy of the public `testdata/pedapp` model: 1 CONV010 finding before, 0 after. Projects get the rule on the next `bin/sync-project.sh` (unedited copies refresh); their CONV010 count can only drop. Reported by jopterhorst +- fix(doctor): **when mxbuild stops before checking the model, the gate self-test now quotes mxbuild's own reason** (`mxbuild stopped before checking the model (baseline run): An error occurred while compiling Java actions (release version 25 not supported)`) instead of "gate cannot read mxbuild's error file"; `mxtk_mxbuild_why` appends the compiler's `error:` line from `details`, so exec.sh's "mxbuild says" line gains it too. The Build toolchain section WARNs when the Java major does not match a Mendix 11 model (11.14+ → JDK 25, ≤11.13 → JDK 21) and, when `JAVA_HOME` is exported in the shell (which beats both the per-model rule and toolkit.env), names the fix (#576). A `MXTK_JAVA_HOME` override that beats an environment `JAVA_HOME` is not in this change. Cases 12–13 in `tests/wave2/test-doctor-gate-selftest.sh`. Field report, macOS, Studio Pro 11.15.0, mxcli v0.28.0 — MendixMau +- fix(doctor): **the gate self-test no longer copies Studio Pro's `.mpr.lock` into its scratch copy**, so with the project open in Studio Pro the known-bad injection is no longer refused and the self-test proves both halves instead of reporting NOT RUN (#577). Case 11 in `tests/wave2/test-doctor-gate-selftest.sh`. Field report, macOS, Studio Pro 11.15.0, mxcli v0.28.0 — MendixMau +- fix(facts-lock): **identifiers under `name` in `domainEntities` / `entities` / `pages` / `microflows` / `nanoflows` / `integrations` arrays are now read, WARN-only: a respelling across BRDs prints `WARN name-keyed entities/paymentplan — 2 spellings …`** (#519, warn-only per the maintainer's lean). They are never frozen and change no exit code, so no project that passes today fails after a sync; whether they become lock facts is a separate call. The empty-lock line also says how many `name`-keyed identifiers were seen. Fixture `test-facts-lock.sh` (the issue's two-file repro; 2 fail on master). — AlvinnYeo (#519) +- fix(exec): **the mxbuild-log prune keeps the newest 30 by their time-stamped file name, not by mtime** (#505). Logs written within one clock tick tied on mtime and `ls -t` broke the tie by name ascending, so the oldest could survive; `test-exec-gate-log.sh` case C flaked under parallel load for the same reason. Case C now gives the oldest-named log the newest mtime, so it fails deterministically on master. — maintainer (#505, PR #498 review) +- fix(conformance): **`conformance-check.sh` measures the single-file multi-BRD ledger that `brd-to-build-plan.md` writes: BRD-prefixed pointers (`F002/modules/0`) and the statuses `not built` and `blocked ()`** (#515). It kept only rows whose pointer started with `/`, so such a ledger exited 2 "an index, not a ledger", and the two statuses would have read UNKNOWN-STATUS; both now measure as not-built. Unprefixed and per-BRD ledgers are keyed as before (baselines unchanged). `review-module.sh`'s "the ledger names no reachable *.brd.json" for an ids-only ledger is not fixed here. Fixture `test-conformance-per-brd-ledger.sh` case 10 (fails on master). — jopterhorst (#515) +- fix(coverage-preflight): **a per-module ledger file (`architecture/modules//coverage-ledger.md` or `-coverage-ledger.md`) whose pointers all carry a BRD prefix is measured only against the BRDs those prefixes name; the rest are listed as another module's, like the directory form** (#543, option 1 of the triage). Before, every BRD in the project was measured against it, so BRDs only other modules claim read fully UNCLAIMED (6 of 8 BRDs, ~8,700 false leaves in the reporter's run). A ledger with any `/slash` pointer, the project-wide ledger, and one whose prefixes name no BRD are measured as before. Leaves of a shared BRD that another module claims still read UNCLAIMED (option 2, not built). Only removes false findings; no verdict goes to FAIL. Fixture `test-coverage-preflight-ledger-dir.sh` cases 7–8 (case 7 fails on master). — AlvinnYeo (#543) +- fix(verify-module): **the journeys rung walks the folder form `journeys//*.journey.json` (all files, one runner call, positive control the same) when `journeys/.journey.json` is absent, instead of FAULT "no journeys/.journey.json"** (#542). The single file still wins when both exist; `controls/` is not passed as journeys. A journey whose persona is not `TEST_USER` reads INVALID with the runner's own remedy; per-persona runs are left for a follow-up. Fixture `test-verify-module-446.sh` V10 (fails on master). — AlvinnYeo (#542) +- fix(verify-module, page-scope): **the page-scope rung's budget is `VM_TMO_PAGESCOPE` (default 900 s, was a fixed 300), and a timed-out rung's whole process tree is killed, so page-scope's Python child no longer survives the watchdog and writes `page-scope.json` after the design audit read the old one** (#541). The watchdog killed only the PID it started; descendants are now listed and killed (where `pgrep` exists; Git Bash keeps the old single-PID kill). `page-scope.sh` writes to a temp file and renames it. Measured by the reporter: ~90 pages at ~4.8 s each, the audit measured 88 pages while the orphan later wrote 94. Fixture `test-verify-module-446.sh` V9 (fails on master; V3 fails on master too in this container, unrelated). — AlvinnYeo (#541) +- fix(gate-check): **build-ready now names a `{{PLACEHOLDER}}` whose description spans lines and is still in an agent stub, as a WARN** (#509). The per-line grep never matched a two-line placeholder, so a stub with mdl-agent's four-line `PROJECT_LOCAL_SOURCES_OF_TRUTH` untouched read "no unfilled placeholders". WARN, not FAIL, so no existing project flips; whether it should block is a maintainer call. Fixture `test-bug03-gates.sh` T18 (on the real templates). — jopterhorst (#509) +- fix(page-shell): **`check-page-shell.sh` reads `create page if not exists "M"."P"` as page `P`, skips that placeholder when the same file declares the page again for real, and reports a declaration it cannot parse as "page declaration not read" instead of a missing wireframe** (#517). The name `sed` printed the whole line on a non-match, so the declaration text went on as the page name into a false "has no wireframe" STOP. Fixture `test-page-shell-body.sh` cases 9–10 (4 asserts fail on master). — AlvinnYeo (#517) +- test(journeys): **`test-journey-back-no5xx.sh` now walks a journey in a real headless Chromium against a stand-in app on localhost** (#564, follow-up from the independent review): a button that POSTs to `/xas/` and gets 560 behind a page that still lands reads `reached PASS` and `no HTTP 5xx FAIL` naming the 560 (the issue's shape), Back lands home with no 5xx, and Back on a first page throws "Back left the app". `journey-runner.js` exports `runJourney` and `results` for such fixtures. — AlvinnYeo (#564) +- fix(design-audit): **the a11y evidence entry names the widget even when its `mx-name-*` class sits past the first 120 characters of the tag** (#561, follow-up from the independent review). The tag was cut to 120 characters before the widget name was read, and Mendix puts `mx-name-*` last in a long class list: a captured delete button's tag is 267 characters with the class at 183, and it read `widget: null`. The name is now read from the full tag; the shown tag is still cut to 120. Second axe-core 4.10.2 capture added, with the capture script and both source pages (`tests/wave2/fixtures/design-audit-a11y/capture.js`), which reproduces the first capture byte for byte. — AlvinnYeo (#561) +- fix(e2e): **the last sites that called `isVisible({ timeout })` as if it waited now wait through `helpers.visibleWithin()`: journey-runner's `ui.checks` loop (documented as 4 s per widget, measured instant), monkey.js `land()`, its re-land check and its blank-page oracle, full-app-walkthrough.js `vis()` / `expectAbsent` / `fill` / widget checks, and `gotoTarget()`'s nav anchor** (#536, follow-up from the independent review). A page or widget still rendering read as missing in 0 ms: a widget FAIL, a "no .mx-page" landing, a blank-page crash. Instant probes (the spinner checks, the login helpers) stay instant. `test-e2e-visible-wait.sh` gains 4 checks (fail on the previous commit); `test-monkey-oracle.sh`'s stand-in locator gains `waitFor`. — AlvinnYeo (#536) +- new(journeys): **`click` takes `row` (text of one grid row, substituted) and `in` (a container widget), so a grid that repeats one widget name per row clicks the row the journey means** (#148, one slice of the harvest). A bare `.mx-name-` click lands on whichever row rendered first. The row must show the text as whole words (`Order 1` never matches `Order 10`). No row says the text, or two rows do (`ambiguous`) → the step FAILs; it never falls back to a row. Without the fields the click is unchanged. Measured in a real headless Chromium (`test-journey-row-click.sh`, 5/5): the bare click hit row A, the scoped one row B, including a Data Grid 2 style `role=row` with `display:contents` and cells rendered with no whitespace between them. The rest of #148 (`signInAs`, `switch`, `expectErrors`, `radio`, `confirm`, `request`, `data.rebase`, `withinMs`) stays open: it needs control mutants and a scratch-app field run. — MendixMau (card-disbursement field run) +- new(journeys): **`ui.pseudoText: ["…"]` asserts text authored in CSS (`::before` / `::after` `content`), which `innerText` and so `textPresent` never see** (#276, finding 1 — the last open item). The reporter's journey for CSS empty-state sentences failed 3 of 17 steps on its first run with the feature fine. One `CSS content says "…"` row per string; elements that are not rendered are skipped; an unreadable page is INVALID. `validate-journeys.js` checks the array holds strings. Measured in a real headless Chromium on a localhost page (`test-journey-pseudo-text.sh`, 5/5): innerText lacks the sentence, pseudoText reads it. No control mutant for it yet. — MendixMau (retro 2026-10-12) +- new(journeys): **fix-loop reruns: `--rerun-failed[=]` walks only the journeys the last run did not pass, `--only=` walks the named ones** (#492). The journeys that `leave` what a chosen one `requires` are walked too (transitively), so declared state is rebuilt; findings go to `journey-findings-rerun.json`, never the full run's slot; the summary says the full run is still owed. An aborted or missing findings file, or an unknown id, exits 2. Measured on stand-in journeys (`test-journey-rerun.sh`, 13/13): one failed journey of three → 2 walked (it plus the journey that leaves its precondition). Journey-level only; rerunning from a failing *step* needs per-step state preconditions and stays open. — MendixMau (#476 split) +- new(journeys): **a journey can run as a phone (`"device": "iPhone 13"`, or `"viewport": {…, "isMobile": true}`), go offline and back (`{ "do": "offline" }` / `{ "do": "online" }`), and set a GPS fix per step (`"geolocation": { latitude, longitude, accuracy }`, `{{seed}}` allowed)** (#521). Before, every journey ran in one 1440x900 desktop window, so Mendix served the Responsive profile and the reporter's 20 phone journeys were all ruled INVALID; "offline" steps were labelled waits that never left the network; a nanoflow reading the position got "permission denied". One run is one device: a run mixing devices exits 2 before the browser opens; `JOURNEY_DEVICE` overrides; the device is measured after launch (exit 2 on mismatch) and written to the findings and each walk. A journey still offline at its end is put back online (`setup … back online at the end INVALID` if it walked every step); the sign-out always goes online first. A bad fix is INVALID and stops the journey, never sent as 0,0. Legacy `_geolocation` and the labelled `context.setOffline(...)` wait are honoured with a WARN. Journeys with none of the fields launch exactly as before. Written to the contract in the issue (the offered patch was not attached); `test-journey-device-net-geo.sh` drives a real headless Chromium against a localhost page (phone context 390x844 with touch; position denied → fix → next fix → denied after clear; offline reload fails until back online). No live Mendix run. — AlvinnYeo (contract and field evidence) +- fix(validate-journeys): **`offline` / `online` are accepted, `device` / `viewport` / `geolocation` are checked, and an unknown step key close to a known one (`spams`, `geolocaton`, `nmae`: one edit for keys under 5 letters, a swapped pair counting as one; two edits otherwise) is reported as a likely typo** (#522). Ordinary note keys (`note`, `node`, `url`) are not flagged. The runner ignores such a key and walks the step without the check the author meant. A literal latitude outside [-90, 90] or longitude outside [-180, 180] is reported; a `{{seed}}` is left to the placeholder rule. `notMeasurableWhen` / `skipAfterNotMeasurable` are not checked, so the issue's misspelt `notMeasureableWhen` example still passes silently: the toolkit runner does not read them (they come from the reporter's later runner revision). 9 self-test rows added. — AlvinnYeo +- fix(validate-journeys): **a `back` action's optional `timeout` must be a positive number of ms** (#522, the reporter's follow-up comment; `back` and `ui.no5xx` themselves landed with #564). Self-test row added (26 cases). The issue's `notMeasurableWhen` tag is not checked: the toolkit runner does not read it (`offline`/`online` landed with #521). — AlvinnYeo (#522) +- new(journeys): **a journey can press the browser's Back button (`{ "do": "back" }`) and opt a step into a 5xx check (`"ui": { "no5xx": true }`)** (#564). Back is a path no in-app click takes (the Mendix client rebuilds the page and re-sends its kept client state), so a defect there could not be pinned by a journey; and a page whose data sources all failed (HTTP 560 on `/xas/`) still drew its frame, so `ui.ready` read PASS. `back` throws when it leaves the app (login page, another origin), not on a `networkidle` timeout; `no5xx` records `no HTTP 5xx during the step` from the first action to the landing guard, before any bail-out, and is opt-in, so no existing journey changes result. `validate-journeys.js` accepts `back` and checks `ui.no5xx` is a boolean; `journey-examples.md` documents both. Ported from the reporter's local patch, which took a Back journey from 60 PASS / 2 FAIL (both the no5xx rows) on the defective build to 62/62 after the app fix. Fixture `tests/wave2/test-journey-back-no5xx.sh` (10 cases); the listener's response API checked in Chromium against a server answering 560. — AlvinnYeo (#564) +- fix(e2e): **the journey runner's `click` / `fill` / `combobox` actions and `helpers.js` `gotoTarget()` now wait for their element to become visible (new `helpers.visibleWithin(locator, ms)`), instead of `isVisible({ timeout })`, which Playwright ignores and answers at once** (#536, the sites #547 left). An element attached but still fading in, or inside a popup still opening, threw "not visible" in 0 ms and failed the step. No check gets stricter: a step that passed still passes; one that failed only because the element was a moment late now passes, and a truly missing element fails after its 6 s instead of at once. The instant probes elsewhere (monkey oracle, walkthrough, sign-in) are left as they are. Fixture `tests/wave2/test-e2e-visible-wait.sh` (stand-in locator, plus the issue's reproduction in Chromium where Playwright has a browser). — AlvinnYeo (#536) +- fix(journeys): **`--positive-control` no longer reports a mutant that never reached its check as "BROKEN PRECONDITION WENT GREEN": when nothing failed but a row was INVALID (a seed the previous mutant consumed), the control row reads `INVALID — mutant not run: ""`, and a journey that declares `consumes` gets a WARN up front** (#560). Mutants share one data set, so for a journey whose action consumes its seed state every mutant after the first that passed the action stopped at `seeds resolved = INVALID`, and three rungs read FAIL "went green" (7 expected, 2 proven) on a journey that was fine. The exit code and the proven count are unchanged (a control passes only when every row is PASS). A per-mutant reset hook and `--only-rung` (the issue's fixes 2–3) are not in this change. Fixture `test-journey-control-exit.sh` (+5 cases, 12/12). — AlvinnYeo (#560) +- fix(journeys): **a named control tagged in `outcome.label` can now be PROVEN: the outcome check is named ` end state: