diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d0d412c..de689981 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. ## Unreleased +- docs(lint): **Mendix quality-source review: 19 new lint-backlog rows (Batch 4) plus seven skill additions.** Recommender, QSM/AQM, ATS, Menditect and docs.mendix.com mapped against shipped rules; indexes, scheduled events, Atlas 4 tokens, accessibility floor, workflow targeting, test isolation and rollback semantics added to their skills; record in `process/quality-sources-review-2026-10-08.md`; no rule probed on a model yet — MendixMau - 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-.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 diff --git a/process/lint-backlog.md b/process/lint-backlog.md index e5f0503d..024317ba 100644 --- a/process/lint-backlog.md +++ b/process/lint-backlog.md @@ -58,6 +58,46 @@ project). This file has the *what* and the *order*. | 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 | +## Batch 4 — Mendix quality-source review + +Sources: Mendix Best Practice Recommender, QSM/AQM public docs, ATS public docs, Menditect public docs, +docs.mendix.com; reviewed 2026-10-08. Rule ids are placeholders. Lintability was read from the mxcli lint +source (Starlark accessors), and no rule below has been probed on a real model yet, so every "Needs" cell +is still the vocabulary probe from rollout rule 3. The full review is +`process/quality-sources-review-2026-10-08.md`. Suggested PR grouping: {13, 14, 15} · {16, 17, 27} · +{21, 22} · {18, 23, 28} · {19, 20, 29} · {24, 25, 26}. + +| # | Rule | Category | Evidence | Needs | Status | +|---|---|---|---|---|---| +| 13 | Anonymous role hygiene: anonymous = admin role; anonymous mapped to an Administration module role; a module role shared by anonymous and another user role (SEC010) | security | Recommender MXS005, MXS008, MXS009 | `project_security()` anonymous/admin role names; `user_roles().is_anonymous`; `role_mappings()` | idea | +| 14 | Anonymous role can create, delete or write members of a persistable entity (SEC011) | security | Recommender MXS006 | `permissions()` access_type vocabulary (probe) | idea | +| 15 | Entity access grants default rights to new members (SEC012) | security | General Development Best Practices → Security | `permissions().default_member_access_rights` values (probe) | idea | +| 16 | Generalization deeper than two levels (PERF001) | performance | Recommender MXP009; General Dev BP; Community perf #9 | `entities().generalization`; how System ancestors count | idea | +| 17 | Data view / list view nested two or more levels (PERF002) | performance | Recommender MXP011 | `widgets()` widget_type strings, parent_widget_id | idea | +| 18 | Commit without events on an entity with event handlers or validation rules (CORR001) | correctness | Setting Up Data Validation ("set With events to Yes") | `activities_for()` with_events / commit_type values; `entities().has_event_handlers` | idea | +| 19 | Page title empty or default (UX003) | design | Accessibility introduction → meaningful page titles | `pages().title` default value (probe) | idea | +| 20 | Page URL starts with a parameter, or two URLs conflict (CONV021) | correctness | General Dev BP → URLs | `pages().url` | idea | +| 21 | Microflow prefix outside the Mendix list; MPR001 checks only the leading capital (CONV022) | naming | Naming Conventions BP prefix table | `microflows().name`; prefix list as config | idea | +| 22 | Event-handler and scheduled-event microflow names match their trigger (CONV023) | naming | Naming Conventions BP (BCO_…ARO_, SCE_) | `entity_event_handlers()` moment/event; `scheduled_events()` microflow_name | idea | +| 23 | Microflow returns System.Error / HttpResponse / SoapFault (ERR002) | correctness | Error Handling in Microflows | `microflows().return_type` spelling (probe) | idea | +| 24 | Binary attribute instead of System.Image / FileDocument specialisation (DESIGN002) | design | Configuring a Domain Model | `attributes_for().data_type` value for binary (probe) | idea | +| 25 | User role holds more than one module role of the same module (SEC013) | security | App Setup BP → user roles | `role_mappings()` | idea | +| 26 | Exact-duplicate access rules on one entity (PERF003) | performance | Recommender MXP010 | `permissions()` grouped by entity + xpath_constraint | idea | +| 27 | Non-persistable entity associated with System.User / System.Session (PERF004) | performance | Recommender MXP008 | `associations()` from/to; `entities().entity_type` | idea | +| 28 | Log node empty or not the module name (LOG001) | quality | General Dev BP → logging; Logging refguide | `activities_for().log_node_expression` shape (literal vs constant) | idea | +| 29 | Action buttons and inputs left on generated widget names (TEST001) | quality | ATS community + harness locator practice (mx-name) | `widgets().name`, widget_type | idea | +| 30 | Index on sort / XPath / OData-key attributes | performance | Recommender MXP003, MXP007, MXP016 | an `indexes()` accessor (does not exist; upstream) | blocked: no index accessor | +| 31 | Scheduled event not in UTC | correctness | Scheduled Events refguide | `scheduled_events().time_zone` values | skill (rule later, maybe) | + +### Notes from the 2026-10-08 review + +- CONV005 demands a `SNIPPET_` prefix, while Mendix's naming-conventions page says `SNIP_`. Every + Mendix-conventional app fails CONV005; settle before touching naming rules 21 and 22. +- MPR001's prefix regex accepts any capitalised prefix (its prefix list is a comment, not a check), and the + list names `SCH_`/`SE_` where Mendix uses `SCE_`. +- Stock rule ids are now MPR001-012 in the mxcli source; the generated project CLAUDE.md tables still say + MDL001-007. + ## 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 diff --git a/process/quality-sources-review-2026-10-08.md b/process/quality-sources-review-2026-10-08.md new file mode 100644 index 00000000..400645eb --- /dev/null +++ b/process/quality-sources-review-2026-10-08.md @@ -0,0 +1,143 @@ +# Quality-source review — 2026-10-08 + +Review of Mendix quality-tooling sources (Best Practice Recommender, QSM/AQM public docs, ATS public docs, +Menditect public docs, docs.mendix.com) against what the toolkit already enforces. Lintability was read from +the mxcli lint source and no rule has been probed on a real model. Scan quotes were paraphrased, thresholds +for QSM/Sigrid and Omnext rule text were not captured, and long pages were cut off, so "no source" means +"not captured". The 19 proposed rows are in `process/lint-backlog.md` (Batch 4); the section letters below +keep the original review's lettering. + +## B. Already covered + +| Practice | Source(s) | Covered by | +|---|---|---| +| Commit (and create/change-with-commit) inside a loop, batch at 1000 | aqm b1 MXP004/005, b6 #25; men S2 | CONV011 (stock); `microflow-loop-antipatterns.md`, `mdl-cookbook-microflows.md` | +| Retrieve / delete / REST / Java call inside a loop | aqm b6 #26-27; men S1 | LOOP001 (Batch 1, in-flight); backlog row 4 | +| Error handling on integration and Java calls | aqm b4; men S1; docs S1 | CONV013 (stock) | +| Avoid Continue error handling | docs S1 | CONV014 (stock); ERR001 (in-flight) | +| Always log the error, even when the process continues | aqm b4; men S1; docs S1, S2 | ERR001 (in-flight); backlog rows 1, 5, 6 | +| Calculated attributes: on pages, unused, in general | aqm b1 MXP001/002, b4, b6 #4; docs S4 | CONV017 bans them outright (stricter than MXP001/002) | +| Event handlers: use with caution | aqm b4, b6 #5; men S1 | CONV016 | +| Validation via VAL_ flows, not entity rules | docs S12; aqm b5 VAL_ | CONV015; backlog extra "VAL_ returns Boolean"; `mdl-cookbook-microflows.md` | +| Microflow size 25 elements / complexity | aqm b4; men S1, S22 | QUAL003 (25), CONV009 (15), QUAL001 McCabe | +| Annotate complex microflows (>10 activities or >2 decisions) | aqm b4; men S1 | UX002 (in-flight, threshold 8 activities); CONV012 split captions; QUAL002 docs | +| Unused / excluded documents | aqm b4; men S1 | QUAL004 orphaned elements; backlog row 3 (ACT_/page entry points) | +| Anonymous users enabled (MXS003) | aqm b2 | SEC004 guest access | +| Anonymous unconstrained read (MXS010), anonymous read on sensitive entities (MXS004, partly) | aqm b2 | SEC007; SEC008 for PII | +| Security overview complete / entity without access rules | aqm b4; men S1 | SEC001, MPR007 nav page roles, backlog row 11 | +| Strict mode / XPath on every access rule | aqm b6 #38 (adjacent) | SEC005, CONV007 | +| Module role mapping (each module role → one user role) | aqm b7 #10 (reverse direction) | CONV008; backlog row 10 | +| Naming: PascalCase entities/pages, ENUM_, page suffixes, booleans | aqm b5 | MPR001, CONV003, CONV004, CONV001 (see the SNIP_ conflict in F) | +| Folder organisation | aqm b5 Folders | CONV018; `module-folder-convention.md` | +| Domain model size per module, app size | aqm b4 App size | MPR003 per module (app-wide ceiling not checked, see C-29) | +| ACT_ flows delegate, page actions give feedback | aqm b5 page microflows | CONV010, CONV020 (shipped) | +| Data changes through microflows; business key | community perf, docs S3 | ARCH002, ARCH003 (shipped) | +| Cross-module data access, module coupling (QSM "architecture") | men S6/S7 | ARCH001; graph builtins; `module-dependency-review.md`, `layering-review.md` | +| Writes late in the flow (MXP014) | aqm b1; men S2 | `microflow-preflight.md` (not lintable, see C-30) | +| Secrets in constant defaults / exposed to client | aqm b7 #11-13 | `project-bin/constants-audit.sh` + `learned-constants-and-secrets.md` | +| Missing translations, required captions | docs S11 i18n | QUAL005, QUAL006 | +| Studio Pro warnings, consistency | aqm b4; men S1 | `mx check` in `bin/exec.sh` / gates | +| Overlapping activities, canvas layout | aqm b4 flow layout | MPR008, MPR011, `microflow-preflight.md` → Layout | +| Workflow enum branches carry an Empty outcome | docs S10 (adjacent) | `workflow-structure-rules.md` | +| Confirm before destructive action | docs S9 descriptive buttons (adjacent) | UX001 (in-flight) | +| Unit testing exists, test levels | ats S24-S26; men S10, S17 | `testing-shape.md`, `learned-db-assertions.md` | +| REST: log the response, then rethrow | docs S2 | `rest-integration-first-time-right.md` | + +## C. Candidates (deduplicated, by priority) + +Severity is `warning` for every proposed rule (lint-backlog rule 2). Rule IDs are placeholders. + +| id | practice | sources | lintable | proposal | prio | notes | +|---|---|---|---|---|---|---| +| C-01 | Anonymous user role is the administrator role | aqm b2 MXS005 | yes: `project_security()` anonymous_user_role, admin_user_role, enable_guest_access | Rule **SEC010 AnonymousRoleHygiene** (security), check 1 of 3 | P1 | Mendix calls it "almost certainly a configuration error"; zero false-positive risk; one comparison | +| C-02 | Anonymous user role mapped to an Administration module role | aqm b2 MXS009 | yes: `user_roles()` is_anonymous, module_roles | SEC010, check 2 | P1 | Lets visitors manage accounts (also covers most of MXS007, which is not lintable on its own) | +| C-03 | A non-System module role mapped to both anonymous and another user role | aqm b2 MXS008 | yes: `role_mappings()` + `user_roles().is_anonymous` | SEC010, check 3 | P1 | Granting the logged-in role silently grants the anonymous visitor too; FP only for deliberate public modules → `SKIP_MODULES` | +| C-04 | Anonymous role can create, delete or write a member of a persistable entity | aqm b2 MXS006 | yes: `permissions()` access_type, member_name; `entities()` entity_type; role mapping | Rule **SEC011 AnonymousWrite** (security) | P1 | Probe the access_type vocabulary (CREATE/DELETE/WRITE spelling). Sign-up forms are the legitimate case, so report and let the project waive | +| C-05 | Entity access grants default rights to new members | aqm b4 Default rights; men S1 | yes: `permissions()` default_member_access_rights | Rule **SEC012 DefaultMemberRights** (security) | P1 | New attributes silently inherit read/write; probe the value strings (None/ReadOnly/ReadWrite) | +| C-06 | Generalization deeper than 2 levels | aqm b1 MXP009, b4, b6 #9; men S1/S2; docs S4 | yes: `entities()` generalization (walk the chain) | Rule **PERF001 InheritanceDepth** (performance) | P1 | Decide whether System.User → Administration.Account counts as a level (probably not: count from the first non-System ancestor). Joins grow per level | +| C-07 | Data view / list view nested 2+ levels deep | aqm b1 MXP011; men S2 | yes: `widgets()` widget_type, parent_widget_id | Rule **PERF002 NestedDataContainers** (performance) | P1 | Mendix's own Recommender check. Probe the widget_type strings (DataView, ListView, Gallery?). `learned-page-patterns.md` already prefers a Context source for nested views, so cite it in the suggestion | +| C-08 | Commit without events on an entity that has event handlers or validation rules | docs S12 | yes: `activities_for()` action_type, with_events, commit_type, entity_ref; `entities()` has_event_handlers, validation_rule_count | Rule **CORR001 CommitSkipsEntityLogic** (correctness) | P2 | Matters on existing apps (CONV015/016 keep new ones clean). FP: deliberate bulk imports → suggestion text names the trade-off | +| C-09 | Page title empty or left as default | docs S8 Meaningful page titles | yes: `pages()` title | Rule **UX003 PageTitle** (design) | P2 | Accessibility and the browser tab. Probe what "default" looks like (empty string or "Page"?) | +| C-10 | Page/microflow URL starts with a parameter, or two URLs conflict (same segment count, parameters in the same places) | aqm b4 URLs; men S1 | yes: `pages()` url | Rule **CONV021 PageUrlShape** (correctness) | P2 | Pairs with CONV019 (navigation pages addressable). Normalise `{x}` to `{}` before comparing | +| C-11 | Microflow uses an UPPER_ prefix outside the Mendix list | aqm b5 (full prefix table) | yes: `microflows()` name | Rule **CONV022 MicroflowPrefixVocabulary** (naming), list configurable | P2 | MPR001's regex lets any capitalised prefix through: its prefix list is a comment, not a check. Missing from it: SCE_, CAL_, OEN_, OLE_, WFA_/WFS_/WFC_, TEST_/UT_, CWS_/CRS_/POS_, DCM_, BSD_, HCH_ | +| C-12 | Event-handler microflow prefix matches its moment+event (BCO_/ACO_/BCR_/ACR_/BDE_/ADE_/BRO_/ARO_); scheduled-event microflow is SCE_ and named after its event | aqm b5 | yes: `entity_event_handlers()` moment, event, microflow; `scheduled_events()` name, microflow_name | Rule **CONV023 LifecycleMicroflowNames** (naming) | P2 | Mechanical and precise. Tells a reader that a flow fires implicitly | +| C-13 | Microflow returns System.Error / HttpResponse / SoapFault | docs S1 | yes: `microflows()` return_type (probe its spelling) | Rule **ERR002 ErrorObjectReturned** (correctness) | P2 | Runtime supports one System.Error instance; returning one to a page/nanoflow breaks. Rare but cheap | +| C-14 | Binary attribute instead of a System.Image / FileDocument specialisation | docs S3 | yes: `attributes_for()` data_type | Rule **DESIGN002 BinaryAttribute** (design) | P2 | Also a migration smell (legacy BLOB columns). Low FP | +| C-15 | User role holds more than one module role of the same module | aqm b7 #10 | yes: `role_mappings()` | Rule **SEC013 OneModuleRolePerModule** (security) | P2 | Complements CONV008. FP: marketplace modules with additive roles → vendor exclusion at the gate | +| C-16 | Duplicate access rules on one entity (same role set and XPath) | aqm b1 MXP010; men S2; docs S4 | yes for exact duplicates; partly for "overly varied" (`permissions()` xpath_constraint) | Rule **PERF003 DuplicateAccessRules** (performance) | P2 | Each rule adds an OR branch to every query | +| C-17 | Non-persistable entity associated with System.User or System.Session | aqm b1 MXP008; men S2 | yes: `associations()` + `entities()` entity_type | Rule **PERF004 SessionCachedNPE** (performance) | P2 | Recommender check; FP when the NPE really is a per-session cache, which is exactly what Mendix warns about | +| C-18 | Log node empty, or not the module name | aqm b4 Log node; men S1; docs S7 | yes: `activities_for()` log_node_expression, module_name | Rule **LOG001 LogNodeIsModule** (quality) | P2 | Configurable: accept a constant or `'Module'` literal. Supports ERR001 (a log nobody can filter is half a trace) | +| C-19 | Widgets left on generated names (`actionButton3`, `textBox12`) | ats community "unique widget names", third-party "mx-name over generated IDs" | yes: `widgets()` name, widget_type | Rule **TEST001 StableWidgetNames** (quality), action buttons and inputs only | P2 | The e2e harness and ATS both locate by mx-name. Existing apps will report many; MDL-built pages report few. Ship scoped to buttons first | +| C-20 | Index on sort, XPath and OData-key attributes of large entities | aqm b1 MXP003/007/016, b6 #3, #13-17; men S2; docs S4 | **no**: no index accessor, no record counts | Skill (E-1) + upstream ask for `indexes()` | P2 | The most-cited performance item across all scans, and the toolkit has no check at all ("none" in the best-practices index) | +| C-21 | Scheduled events: chunked commits, idempotent, ≤10 in parallel per node, rename resets enabled state, UTC, no session edits, ProcessedQueueTask cleanup | aqm b6 #33; docs S6 | partly: `scheduled_events()` time_zone, on_overlap, interval_seconds | Skill (E-2); later maybe a rule on time_zone ≠ UTC | P1 (skill) | Backlog already lists "Scheduled events … nothing yet". These scans give the content | +| C-22 | Atlas 4 theming: `$use-css-variables: true`, globals in `:root`, `var(--x)` not `$x`, color-mix() not darken() | docs S5 | no (SCSS files; shell grep possible) | Skill (E-3) | P2 | `design-artifacts.md` maps tokens to Atlas **SASS** variables (`$brand-primary`). That is Atlas 3 shape and breaks on Atlas 4 apps | +| C-23 | Accessibility: alt text, no positive tabindex, labels, contrast 4.5:1 text / 3:1 non-text, visible focus, link text | docs S8 | no (no alt, tabindex or caption fields); contrast checkable in ds.css | Skill (E-4); contrast check is a ds.css shell candidate | P2 | No toolkit skill states contrast or focus rules today | +| C-24 | Workflow targeting: group targeting by default, group tokens not Name, guard empty target lists, return type | docs S10 | no (workflow XPath not in `xpath_expressions()` document types) | Skill (E-5) | P2 | Empty target list fails the workflow at runtime | +| C-25 | Test design: independent tests, teardown undoes changes, keep rollback on, no fixed sleeps | ats S7, S17, S24, S25, third-party | no | Skill (E-6) | P2 | Only `e2e-harness-base.md` mentions sleeps; nothing states teardown/isolation | +| C-26 | Error-handling semantics: do not commit rolled-back objects; external calls are not reversed by rollback; Continue only on call/loop | docs S1 | no | Skill (E-7) | P2 | Behaviour facts agents get wrong; they fit the existing rollback section | +| C-27 | Unit-test microflow shape: UT_/TEST_, returns nothing/Boolean/String, 0 params or one UnitTestContext | ats S24, S27; aqm b5 | partly: name, return_type, parameter_count (no parameter type) | Rule **TEST002 UnitTestShape** (correctness), only when a UnitTesting module exists | P3 | Wrong shape = test silently not discovered | +| C-28 | More than 9 action buttons on a page | docs S9 | partly: `widgets()` widget_type per container (no visibility) | Rule **UX004 ButtonCount** (design) | P3 | Conditional visibility inflates the count | +| C-29 | App beyond 3000 microflows / 750 entities | aqm b4; men S1 | yes: counts | Rule **DESIGN003 AppSize** (design), info-level | P3 | One finding per app; useful mainly in `app-analysis.md` | +| C-30 | CUD activities near the start event | aqm b1 MXP014 | no: no activity order/position | stays in `microflow-preflight.md` | P3 | Upstream ask: expose sequence index | +| C-31 | Negating access-rule pair (`not()` / `= false()` only difference) | aqm b1 MXP013; docs S4 | partly: `parse_xpath()` on xpath_constraint | Rule PERF005 | P3 | Parser edge cases; low frequency | +| C-32 | XPath `not` / `!=` and costly parts left of `and` | aqm b1 MXP015, b6 #34; docs S4 | partly: `xpath_expressions()` + `parse_xpath()`; no volumes | Rule PERF006, or skill note | P3 | Only matters at 100k+ rows; noisy without volume data | +| C-33 | Two associations between the same entity pair | aqm b6 #11 | yes: `associations()` | Rule DESIGN004 | P3 | Many legitimate (CreatedBy/AssignedTo → User); exclude System targets | +| C-34 | Nested `if` inside one expression | aqm b4; men S1 | partly: split condition_expression only | Rule QUAL007 | P3 | Change/assign expressions not exposed | +| C-35 | Drop-down for an enum of 2-5 values (use radio buttons) | docs S9 | partly: widget_type + attribute_ref; enum name per attribute unclear | Rule UX005 | P3 | Design preference, not a defect | +| C-36 | Pop-up opened from a pop-up | docs S9 | partly: `pages()` has no layout; `refs_from()` page→layout may work | Rule UX006 after probing refs | P3 | Real UX defect, but needs a probe first | +| C-37 | Debug never in production, Critical reserved, own log node names | docs S7 | no (runtime levels are environment config) | Skill note in `mdl-cookbook-microflows.md` logging | P3 | | +| C-38 | Marketplace modules not modified | aqm b4; men S1 | no (needs a reference MPK) | Skill note in `existing-app-assurance.md` | P3 | | + +## E. Skill-only additions (non-lintable P1/P2) + +- **E-1 → `modularize-domain.md`** (indexes; also update the "none" cell in `mendix-best-practices-index.md`): + "Index every attribute used as a sort item, in an XPath on an entity expected past ~10k rows, or as a + published OData key. Lead with the most selective attribute, keep indexes under 3 attributes (max 5), + never two indexes with the same leading attribute, and skip Boolean/enum (MXP003/007/016)." +- **E-2 → `microflow-loop-antipatterns.md` (new 'Scheduled events' section; route from `learned-microflow-patterns.md`)**: + "A scheduled-event flow commits in batches, survives a rerun after a crash (idempotent) and finishes + well inside its interval. No more than 10 events run at once per node. Renaming an event resets its + enabled state per environment, so re-check it after deploy. Use UTC, never change the session object, and + clean System.ProcessedQueueTask." +- **E-3 → `design-artifacts.md`** (Atlas mapping table): + "On Atlas 4 (Atlas Core ≥4), map tokens to CSS custom properties in `:root` with + `$use-css-variables: true` first in `custom-variables.scss`. Use `var(--x)`, not `$x`, and `color-mix()`, not + darken()/lighten(). Declare only the values you override. The SASS-variable table is Atlas 3 only." +- **E-4 → `ui-preflight-pages.md`** (and the wireframe checklist in `design-artifacts.md`): + "Every page has a meaningful title, every input a visible label, every informative image alt text, and every + decorative image `alt=''`. Text contrast is ≥4.5:1, buttons and inputs ≥3:1, focus is always visible and never + colour-only, there is no positive tabindex, and link text names its destination." +- **E-5 → `learned-workflow-patterns.md`**: + "Target user tasks at workflow groups by default, and constrain on group tokens `[%WorkflowGroup_X%]`, not + Name. A targeting XPath or microflow that returns 0 objects fails the workflow, so guard the empty case. A due + date is a stored deadline, not a reminder." +- **E-6 → `testing-shape.md`** (and `e2e-harness-base.md`): + "Tests share no data and run in any order. Setup only opens and logs in. Teardown undoes what the test changed. + Keep the Unit Testing rollback on. Wait for a condition, never a fixed sleep. Locate by mx-name and nothing else." +- **E-7 → `learned-microflow-patterns.md`** (rollback section): + "A rollback does not undo REST/SOAP/Java calls already made. Do not commit an object the error flow just + rolled back, because Mendix no longer sees it as changed. Continue is only allowed on a call-microflow or a + loop, and the failing activity's own changes are always lost." + +## F. Not worth it / out of scope + +| Item | Source | Reason | +|---|---|---| +| Convert eligible microflows to nanoflows (MXP006) | aqm b1 | Eligibility needs a client-side call-site analysis; high FP; offline/security trade-offs | +| Scheduled-event interval divisors, parameter-free flow, unique user-task names | docs S6, S10 | Studio Pro / `mx check` already refuse these; a rule duplicates a CE | +| Line crossings, left-to-right layout | aqm b4 | Cosmetic; MPR008/MPR011 cover the harmful part | +| Input validation on all microflow inputs | aqm b4 | Not checkable without data-flow analysis; VAL_/CONV015 route covers intent | +| Microflow parameter count | aqm b4 | No threshold stated anywhere; would be invented | +| Technical attributes with leading underscore; entities singular | aqm b5 | "Technical" and "plural" are not decidable from the model | +| App/config naming, configuration DB passwords | aqm b7 | Settings/Portal concerns; constants-audit covers secrets | +| Keep up with Mendix releases; community-supported components; open-source health | aqm b4; men S6/S7 | Portfolio/dependency scanning (QSM/Snyk), not model lint; `jar_dependencies()` has no vulnerability data | +| QSM/Sigrid star ratings, ISO 25010 maintainability metrics | aqm b8; men S7-S9 | No thresholds were published or reachable, and benchmark recalibration makes them unportable | +| ATS tool mechanics (recorder, selectors, SOAP CI API, schedules, licensing) | ats S3-S22 | About the end-of-sale tool, not the app model | +| Maia units, network hosts, Logic/Workflow Recommender | docs S13; men S4/S5 | Product usage, not quality practice | +| Denormalise, archive, more App Engines, proxy in front | aqm b6 #7-8, #43-44 | Architecture/ops judgement, no model signal | +| Line length ≤9 words, single-column forms, green forward buttons | docs S9 | Style guidance; belongs in the wireframe review loop, not lint | + +**Contradictions surfaced (settle before the touching rule):** +- Snippet prefix: the Mendix Naming Conventions say `SNIP_`, but stock CONV005 enforces `SNIPPET_`. Every Mendix-conventional app fails CONV005. +- Microflow prefixes: MPR001 lists `SCH_`/`SE_` and its regex accepts any capitalised prefix. Mendix says `SCE_` and has about 15 prefixes that MPR001 does not list (C-11). +- Size threshold: Mendix says 25 elements, CONV009 says 15 and QUAL003 says 25, and both rules ship. The index explains this, but anyone reading only a finding sees two thresholds. diff --git a/skills/design-artifacts.md b/skills/design-artifacts.md index 55661da0..ef7c21a8 100644 --- a/skills/design-artifacts.md +++ b/skills/design-artifacts.md @@ -191,7 +191,7 @@ Contents of `ds.css` + what `design-system.html` renders: - **Tokens as CSS custom properties:** brand ramp, accent, status (reserved), spacing, radius, type scale, shadow, motion — plus a full **light + dark** set. Dark mode is *selected* (its own steps), never an automatic flip. The selector depends on the theme machinery: on a project using **mxcli's theme system (≥ v0.20.0)**, the dark palette lives at `:root.theme-dark` (Atlas ships that class slot on ``; mxcli's `--variant auto` follows `prefers-color-scheme` before first paint AND honours the class, and `theme switcher install` provides the user toggle) — declare dark *after* Mendix's own `_theme-dark.scss` or the app reverts to stock blue when the class lands. On a hand-themed Atlas project, use `[data-theme="dark"]` and avoid bare `prefers-color-scheme` — Atlas's own toggle conflicts with media-query-only dark mode. Either way the design system's *steps* stay selected, not auto-flipped. - **Data colors** come from the `dataviz` skill's validated palette — do not hand-pick chart/KPI colors; run its validator if you swap any. - **Components the app actually needs** (derive from the source screens, not a generic kit): buttons, inputs + validation, table/data grid with row actions, KPI/stat tiles, badges/status pills, dialog, toast, nav shell, plus any product-specific pieces. -- **An Atlas mapping table:** each token → its Atlas SCSS variable (`$brand-primary`, `$background-color`, `$font-color`, `$border-radius-*`, `$spacing-*`, `$font-family-base`, success/warning/danger). This table is what the build phase turns into `theme/web/custom-variables.scss` + design properties. +- **An Atlas mapping table:** each token → its Atlas SCSS variable (`$brand-primary`, `$background-color`, `$font-color`, `$border-radius-*`, `$spacing-*`, `$font-family-base`, success/warning/danger). This table is what the build phase turns into `theme/web/custom-variables.scss` + design properties. **The SASS-variable table is Atlas 3 only.** On Atlas 4 (Atlas Core >=4), map tokens to CSS custom properties in `:root` with `$use-css-variables: true` first in `custom-variables.scss`; use `var(--x)`, not `$x`, and `color-mix()`, not `darken()`/`lighten()`; declare only the values you override. **Render it and look at it** (headless-screenshot both light and dark) — the eye catches what code review misses. This is non-negotiable per the `dataviz` procedure. @@ -336,6 +336,7 @@ with failures here does not pass to the build loop. - **Render every wireframe and look** (both themes). - **Coverage check** each faithful wireframe against its source screenshot: every visible field/section present, right widget type, right state. Gaps become explicit sub-tasks — the same discipline as the build loop's Step 11, applied one stage earlier so the spec is complete before building. +- **Accessibility pass:** see `ui-preflight-pages.md` -> "Accessibility floor"; check it on the wireframe, not after the build. --- diff --git a/skills/learned-microflow-patterns.md b/skills/learned-microflow-patterns.md index 8be1f444..82e48a79 100644 --- a/skills/learned-microflow-patterns.md +++ b/skills/learned-microflow-patterns.md @@ -113,6 +113,8 @@ broken, and a database assertion alone would have called both working.** Neither the check; the pair is. It is the argument for keeping the journey rung and the OQL rung as two separate rungs rather than trusting whichever one is cheaper to run. +**Rollback semantics.** A rollback does not undo REST/SOAP/Java calls already made. Do not commit an object the error flow just rolled back, because Mendix no longer sees it as changed. Continue is only allowed on a call-microflow or a loop, and the failing activity's own changes are always lost. Scheduled-event flows: see `microflow-loop-antipatterns.md` -> "Scheduled-event flows". + --- ## Every Required Attribute Is Set BEFORE the Commit — One COMMIT, as Late as Possible diff --git a/skills/learned-workflow-patterns.md b/skills/learned-workflow-patterns.md index aa858660..f9fc6f90 100644 --- a/skills/learned-workflow-patterns.md +++ b/skills/learned-workflow-patterns.md @@ -253,6 +253,8 @@ TARGETING MICROFLOW MyModule."SUB_TargetReviewers" Such a microflow takes `$Workflow: System.Workflow` and `$Context: `, and returns `List of System.User`. +**Targeting defaults.** Target user tasks at workflow groups by default, and constrain on group tokens `[%WorkflowGroup_X%]`, not Name. A targeting XPath or microflow that returns 0 objects fails the workflow, so guard the empty case. A due date is a stored deadline, not a reminder. + --- ## 6. Syntax reference: the workflow definition diff --git a/skills/mendix-best-practices-index.md b/skills/mendix-best-practices-index.md index 417addde..d6c2f73e 100644 --- a/skills/mendix-best-practices-index.md +++ b/skills/mendix-best-practices-index.md @@ -36,7 +36,7 @@ own Best Practice Recommender rules, which Studio Pro also reports. | Validation in `VAL_` flows, not entity rules | [Setting Up Data Validation](https://docs.mendix.com/refguide/setting-up-data-validation/), [Validation Rules](https://docs.mendix.com/refguide/validation-rules/) | §D Maintainability | `mdl-cookbook-microflows.md` | CONV015, MPR004 | | Event handlers and calculated attributes | [Best Practice Recommender](https://docs.mendix.com/refguide/performance-best-practices/) MXP001, MXP002 | §E Performance | `modularize-domain.md` | CONV016, CONV017 | | Entity access rules and XPath row scoping | [Access Rules](https://docs.mendix.com/refguide/access-rules/), [Implement Best Practices for App Security](https://docs.mendix.com/howto/security/best-practices-security/) | §C Security | `security-is-not-a-later-script.md` | SEC001, SEC004–SEC009, CONV006, CONV007, CONV008 | -| Indexes for sort and XPath attributes | [Indexes](https://docs.mendix.com/refguide/indexes/), Recommender MXP003, MXP007, MXP016 | §E Performance | `modularize-domain.md` | none | +| Indexes for sort and XPath attributes | [Indexes](https://docs.mendix.com/refguide/indexes/), Recommender MXP003, MXP007, MXP016 | §E Performance | `modularize-domain.md` | none (backlog 30, blocked: no index accessor) | | Module boundaries, cross-module data on pages | [Community Best Practices for App Performance](https://docs.mendix.com/refguide/community-best-practices-for-app-performance/) → Domain Model | §F Architecture | `modularize-domain.md`, `module-dependency-review.md` | ARCH001, ARCH002, ARCH003, MPR003, DESIGN001 | | Pages: nesting, grids, forms | [Community Best Practices for App Performance](https://docs.mendix.com/refguide/community-best-practices-for-app-performance/) → Pages, Recommender MXP011 | §I UI Best Practices | `ui-preflight-pages.md`, `learned-dg2-patterns.md`, `learned-page-patterns.md` | MPR005, MPR006, MPR009, MPR010, MPR012 | | Navigation page security | [Access Rules](https://docs.mendix.com/refguide/access-rules/) | §C Security | `security-is-not-a-later-script.md` | MPR007 | diff --git a/skills/microflow-loop-antipatterns.md b/skills/microflow-loop-antipatterns.md index cf709251..42aa4f91 100644 --- a/skills/microflow-loop-antipatterns.md +++ b/skills/microflow-loop-antipatterns.md @@ -111,6 +111,9 @@ finding on an enabled-reachable microflow is written first and sized first. Reac never on its own make a finding `high`, because nothing is running it today. Note the event by name in the row, so that switching it on is a decision somebody makes with this in front of them. +### Scheduled-event flows (what the flow itself must do) +A scheduled-event flow commits in batches, survives a rerun after a crash (idempotent) and finishes well inside its interval. No more than 10 events run at once per node. Renaming an event resets its enabled state per environment, so re-check it after deploy. Use UTC, never change the session object, and clean `System.ProcessedQueueTask`. + ## Say what a loop body is, in the section, in words The reader of section 4 is a Mendix developer who has never seen this tool, or a manager who diff --git a/skills/modularize-domain.md b/skills/modularize-domain.md index 93c6693b..ee3f5115 100644 --- a/skills/modularize-domain.md +++ b/skills/modularize-domain.md @@ -67,6 +67,9 @@ If a candidate clears **none**, it does **not** get its own module. - One module mixing a customer-facing access domain with back-office data. - Distinct teams editing the same module and colliding. +### Indexes +Index every attribute used as a sort item, in an XPath on an entity expected past ~10k rows, or as a published OData key. Lead with the most selective attribute, keep indexes under 3 attributes (max 5), never create two indexes with the same leading attribute, and skip Boolean and enumeration attributes (Recommender MXP003/007/016). + --- ## Step 3: Default to One Module + Folders diff --git a/skills/testing-shape.md b/skills/testing-shape.md index c12550c3..bdbfef12 100644 --- a/skills/testing-shape.md +++ b/skills/testing-shape.md @@ -283,6 +283,7 @@ human looking at the screenshot, not by the suite. - **Phone viewports:** a target can be attached and rendered but **below the fold**, where `isVisible()` is false and the click is skipped. `scrollIntoViewIfNeeded()` before the visibility check. +- **Test isolation.** Tests share no data and run in any order. Setup only opens and logs in. Teardown undoes what the test changed. Keep the Unit Testing rollback on. Wait for a condition, never a fixed sleep. Locate by mx-name and nothing else (`e2e-harness-base.md` applies this to the harness). - **Do not scale up a speed multiplier** that divides hand-tuned waits. A pluggable combobox needing ~1200 ms to open its dropdown fails at 150 ms, and the resulting failures look completely real. diff --git a/skills/ui-preflight-pages.md b/skills/ui-preflight-pages.md index 5156845b..cfbf2658 100644 --- a/skills/ui-preflight-pages.md +++ b/skills/ui-preflight-pages.md @@ -193,6 +193,8 @@ rule applies and that MCP will handle it as a follow-up. --- +**Accessibility floor.** Every page has a meaningful title, every input a visible label, every informative image alt text, and every decorative image `alt=''`. Text contrast is >=4.5:1, buttons and inputs >=3:1, focus is always visible and never colour-only, there is no positive tabindex, and link text names its destination. + ### Step 5 — Run the shell check before you exec (mechanical, seconds) Steps 1–4 are judgement and they are the substance. This step is the part a script can