Skip to content
Closed
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 @@ -17,6 +17,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are.

## Unreleased

- docs(lint): **`process/lint-backlog.md` — the lint rollout plan, on disk.** Twelve rule candidates mined from `skills/` with the evidence each rests on, the catalog field each needs, and five rollout rules (two or three rules per PR, ship as warning, one field run before merge, promote after two clean projects, blind rules say so). Plus what is not lintable and lives in a skill (theme, logging, scheduled events) and four brain contradictions to settle first. Until today this plan existed only in chat. — MendixMau
- perf(routing): **CLAUDE.local.md routing carries triggers only.** The `baseline` view (the copy loaded into every session of every project) now cuts each row's "when" cell at its first " — " and replaces the three-paragraph preamble with two lines pointing at `ROUTING.md` for the full text; `readme-baseline` (README, runbook) is unchanged. Fresh scaffold: CLAUDE.local.md 17.5 KB → 11.7 KB (−33%). Field run 2026-10-07 (existing app, one main session per run, 2 runs per side, an entity + 2 microflows + 2 pages): 10/10 steps applied each side, new mx errors 5 vs 5 (the same CE2421 page-binding slip in both), cost $1.39 vs $1.38; the slim lanes opened the MDL pre-flight skill as often or more. Also drops two stray merge-artifact lines from this section. — MendixMau
- perf(ui-loop): **fewer mxbuilds, no fixed sleeps, legible screenshots.** Field run 2026-10-07 (a requirements-driven build): `exec.sh` was 22% of tool time at two mxbuilds per exec, journeys and page waits slept a fixed time, and screenshots were 11.5% of re-read tokens. `exec.sh` now skips the pre-flight baseline mxbuild when the model is byte-identical to its last *clean* pass: the verification stamp gains an `errors:` field (`model-stamp.sh check --clean` accepts only `errors: 0`, so a delta-gate pass over pre-existing errors never counts; `SKIP_BASELINE=0` is unchanged). New `tests/e2e/settle.js` waits for the page's own state (no visible Mendix progress/loading indicator, DOM size stable) instead of a fixed time; `journey-runner.js` uses it after each action (an explicit `settleMs` still wins) and `page-audit.js` per page. Probed in a real browser: on a page that loads in 1.8 s the old 1200 ms sleep and networkidle+600 both captured it half-loaded, settle waited for it; on a fast page settle took 775 ms vs 1200. `SETTLE_MODE=fixed` restores the old waits for an A/B run. `page-audit.js` also saves a viewport shot `page-audit-<Page>.top.png` beside the full-page one, and `ui-loop.md` says to look at viewport shots: a tall full-page shot reaches the model at ~1024 px high, text unreadable. `shrink-image-read.sh` now works off macOS (ImageMagick or Pillow, python when jq is absent; it was a silent no-op without `sips`). — MendixMau
- perf(ui-loop): **a stale app reloads instead of restarting, journeys stop sleeping, worst pages are looked at first.** Field run 2026-10-07, existing app. `test-stack-up.sh` records which model the running app was built from (`.claude/loop/served-model`); when the model has changed since, it hot-reloads with `mxcli docker reload` (adding `--skip-check` when the exec.sh gate already passed this model) and restarts only if that fails: 93 s instead of 179 s. `ui-loop.md` and the script header carry the measured table and point at `mxcli run --local --watch` as the fast loop (~18 s per page change). `journey-runner.js` waits on page state after a combobox action instead of fixed pauses, and a combobox step now opens the widget it names: it used to open the page's first combobox, so on a form with three every step but the first failed. `page-audit.js` clicks the first copy of a nav item a user could actually click, not just the first match: with the sidebar collapsed, every in-nav page faulted while the same item sat in the top menu bar. It also writes `page-audit-look-order.txt`, all pages worst first (`module-review.md` §4a: order, not selection, every page is still looked at). `design-audit.js` drops its fixed 1.2 s wait. Same 7-step journey on the same app: 56 s with `SETTLE_MODE=fixed`, 24 s with settle, 11/11 pass both ways, 3 runs each. — MendixMau
Expand Down
2 changes: 2 additions & 0 deletions lint-rules/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ are excluded at the gate (`bin/lint-gate.sh -e`, from a per-project
version this directory shipped on 11 Aug: one violation per microflow instead of one per
activity, plus four action types allowlisted. On PROJECT-C that is 399 rows -> 51 findings.

Candidates that are not rules yet, and the order they ship in: `process/lint-backlog.md`.

## A rule that matches nothing now says so

`CONV010` and `CONV020` can only identify a page action by its name — `refs_to()` reports the
Expand Down
94 changes: 94 additions & 0 deletions process/lint-backlog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Lint backlog — best practices the brain already knows, turned into rules one batch at a time

**Created:** 2026-10-08
**Purpose:** the single list of lint-rule candidates, where each came from, what blocks it, and
where it is in the rollout. Chat is where this plan lived until today; this file is where it lives
now. Every rule PR links a row here and moves it.

## Why rules at all

A skill is read by whoever happens to load it; a rule runs on every model, every time, and says a
number. `lint-rules/README.md` has the mechanics (header, blindness finding, how rules reach a
project). This file has the *what* and the *order*.

## Rollout rules

1. **Two or three rules per PR**, never a pack. Each with Starlark unit fixtures under
`tests/lint-rules/` (the UX001/UX002 suite is the template), so the logic is proven before the
rule meets a model.
2. **Ships as a warning.** A new rule runs at warning severity on its first projects. The gate
fails only on errors, so a noisy rule produces counts, not blocked sessions.
3. **One field run before merge.** The rule runs on one real model; the PR cites the count and
three sample findings. Vocabulary (field names, type strings) is probed there, never assumed —
see "Probe, don't trust" in `lint-rules/README.md`.
4. **Promote after two clean projects.** Warning becomes error, the README row moves from
*candidate* to *proven*.
5. **Blind rules say so.** Every rule emits a `_rule` finding when its probe fails; the gate reports
it. A rule that silently matches nothing is the one defect this whole effort exists to prevent.

## Status legend

`idea` → `drafted` (PR open, fixtures pass) → `warning` (merged, on first projects) → `proven`
(error severity) · `blocked: <why>` · `skill` (not lintable, lives in a skill instead)

## Batch 1 — incident evidence and known catalog fields

| # | Rule | Category | Evidence | Needs | Status |
|---|---|---|---|---|---|
| 1 | Error silently swallowed: activity error handling set to continue/custom and no Log activity in the flow | quality | `learned-microflow-patterns.md` error-handling rules; CONV013/014 cover only the *presence* of handling on external calls | `error_handling_type` value strings (probe) | idea |
| 2 | Inline `style` on a widget, or a class not present in the compiled design system | design | `learned-css-that-never-applied.md`, `learned-stylegallery.md` (real: an entire app rendered unstyled) | `style`, `class_name`; class list from `themesource/<module>/web/main.scss` via config | idea |
| 3 | Page or `ACT_` flow with no entry point (QUAL004 exempts `ACT_`) | quality | `wiring-sweep.md`, obligation check "wiring" | `refs_from` / refs table | idea |
| 4 | Retrieve, delete, REST or Java call inside a loop body (CONV011 covers commit only) | performance | `microflow-loop-antipatterns.md` | `parent_loop_id` / `loop_depth` actually populated (brain says activities were flat 2026-09-15 — probe) | idea |

## Batch 2 — strong evidence, one unconfirmed field each

| # | Rule | Category | Evidence | Needs | Status |
|---|---|---|---|---|---|
| 5 | Page-triggered external call left on default rollback handling, no handler | quality | same as 1 | `error_handling_type` | idea |
| 6 | Custom error handler that neither logs nor gives feedback | quality | `learned-popup-feedback-pattern.md` | `error_handling_type`, `log_*` | idea |
| 7 | Page-triggered commit or delete without refresh | correctness | `learned-page-patterns.md` | refresh flag on commit (unconfirmed) | blocked: field unconfirmed |
| 8 | DataGrid 2 database source without a sort | correctness | `learned-dg2-patterns.md` | datasource sort in projection (unconfirmed) | blocked: field unconfirmed |
| 9 | Page top container missing the shell/page-column class | design | `ui-preflight-pages.md`, `oneshot-page-structure-patterns.md` | `class_name`, `depth`; class name per project config | idea |

## Batch 3 — security and navigation

| # | Rule | Category | Evidence | Needs | Status |
|---|---|---|---|---|---|
| 10 | Module role mapped to no user role (CONV008 checks the reverse) | security | `security-is-not-a-later-script.md` | roles in projection | idea |
| 11 | Widget-bound entity with zero access rules at production level | security | SEC001 variant scoped to entities pages actually touch | entity access in projection | idea |
| 12 | Navigation item without icon | design | `learned-sidebar-collapse-icons.md` | navigation in projection (unconfirmed) | blocked: field unconfirmed |

## Cheap extras (one afternoon each, when a batch has room)

Microflow not in a folder · non-persistent entity committed to the database · many-to-many
without a junction entity · `VAL_` flow not returning Boolean · log level ERROR with an empty
message · error handler that only re-raises.

## Not lintable from the catalog — lives in a skill, maybe a shell check

| Topic | Where | Check |
|---|---|---|
| Theme: never edit `themesource/atlas_core`, overrides in the app theme or one UI module; variables over literals in SCSS | `design-artifacts.md` (to add) | grep for hex literals outside `custom-variables.scss`; diff of `atlas_core` against the shipped version |
| Theme: design properties over free-text classes; must exist in `design-properties.json` (CE6083) | `learned-mdl-preflight.md` row 24 | already a STOP row |
| Logging conventions: node name = module, message codes | `mdl-cookbook-microflows.md` | prose; rule 1 above covers the absence |
| Scheduled events: idempotent, batched, disabled per environment | nothing yet | `app-analysis.md` counts them; best practice to write |
| Localization: hard-coded captions | nothing yet | needs a research pass first |

## Brain contradictions to settle before the rule that touches them

- Sidebar caption-less vs icon+label: `learned-page-patterns.md` vs `learned-sidebar-collapse-icons.md` (blocks 12).
- Empty state required (`ui-preflight-pages.md`) vs MDL cannot write one (`learned-mdl-cannot-express.md`).
- `_Dto` suffix standard vs avoid Dto, both in `learned-microflow-patterns.md`.
- mxbuild blind to grants (`iterative-build-loop.md`) vs CE0106 (`learned-file-upload-widget.md`).

## Still to research (not needed to start)

Mendix's own published rule set (Application Quality Monitor), its performance and
error-handling pages, naming conventions. Output: a mapping table against the ids above and
a second backlog section. Forum only to weight by incident frequency.

## History

- 2026-10-08 — file created from the UX001/UX002 work and a brain scan of `skills/`; no rule from
this list exists yet. UX001/UX002 themselves are `drafted` (PR open, 35 unit cases, field run
pending).
Loading