From 3e4472162e8cc17233a441d2e1cebd9efd6d3562 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 14:13:42 +0000 Subject: [PATCH 1/2] learn(design-spacing, module-review): crumbs are links; LOOK asks "can I get back?" (#232) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit design-spacing.md §3 prescribed the page-header crumb as one plain-text dynamictext, which every wireframe and page then copied. The scaffold now builds crumbs from linkbuttons with SHOW_PAGE targets (current page alone as text). module-review.md §4c, the one LOOK question list, gains "can I get back?" and "does every tile go somewhere?". Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw --- CHANGELOG.md | 1 + skills/design-spacing.md | 10 +++++++++- skills/module-review.md | 5 +++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff60ea83..be082344 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m ## Unreleased +- learn(design-spacing, module-review): **breadcrumbs are links, and the LOOK asks "can I get back?" and "does every tile go somewhere?"** ([#232](https://github.com/MendixMau/mxcli-project-toolkit/issues/232), the still-open part). `design-spacing.md` §3 prescribed the page-header crumb as one plain-text `dynamictext`, so every wireframe and page copied it and `page-fidelity.js` scored the text crumb as correct. The scaffold now builds the crumb from `linkbutton`s with `SHOW_PAGE` targets, the current page alone as text, plus a rule that each crumb's target is in the wireframe's binding table and that a stepper says per step whether it is navigable. `module-review.md` §4c (the one LOOK question list) gains the two questions. Both files are baseline: +171 words (75,028 → 75,199). Field evidence: an 81-script Stage-5 build where every gate went green and a user found text-only crumbs and a menu entry opening one record instead of a list. — MendixMau - process(build): **Stage 5 drafts the ready set in parallel and applies serially.** The runbook dispatch row said "sequential within a module", which serialised drafting even for independent scripts; it now points to `iterative-build-loop.md` → Gate cost, the single statement of the rule. The unit is the build-plan row: every `BUILD` row whose "Depends on" rows are all applied, minus overlapping writers (two rows that replace, alter or drop+create the same document never share a ready set, and no row is drafted while it overlaps a queued unapplied one; `brd-to-build-plan.md` has Produces name every document a row writes), runs at once, its scripts in order, one `mdl-agent` per script. Applies stay one at a time through `bin/exec.sh`, and the ready set is recomputed after each. A queued draft made before a sibling apply landed is re-checked with `mxcli diff --format struct` (preflight STOP rule 23); any removal line (checked against `describe` per BUG-146) or STOP rule 21 target means re-draft, never patch, because a stale restatement of a shared document is exactly rule 21's silent seed-call loss and passes `check --references`. Report `ready set N, drafted N, applied k`; the `mdl-agent` template says a drafter never applies and never depends on an unapplied draft. Field evidence: none yet. — MendixMau - fix(platform-link): **CLI commits now show their changed documents in Studio Pro History.** Studio Pro's History → Model changes pane reads a `ModelChanges` array from the commit's `refs/notes/mx_metadata` note. Trap 6c's hand-written template set it to `[]`, so every mxcli/agent commit showed "Model changes 0" (field project: 270 of 270 notes empty). New `project-bin/mx-metadata-note.py` (stdlib only) computes the array from the commit's changed `.mxunit` files, in Studio Pro's entry shape: Status, UnitID, UnitType, UnitName and Module (walked up the `.mpr` Unit tree). It has `changes` / `write` / `backfill` / `sync ` subcommands. `install-project-hooks.sh` now also installs post-commit and post-rewrite hooks that write the note and never block, and `--check` covers them, so `sync-project.sh` rolls them out to wired projects. Notes are published with `mx-metadata-note.py sync ` after the branch push. Push config is never touched. With mxcli v0.26+ in the project root the hooks call `mxcli git note --write` instead (mendixlabs/mxcli#1337); the Python script is the fallback for older pins, and on 160 field commits both wrote the same 3,594 entries. Trap 6c rewritten. Measured against Studio Pro's own notes, 51 commits in 5 apps: 3387/3389 entries exact on all five fields. Field run on a 357-commit Mendix 11.15.0 app: 158 notes backfilled and pushed, and Studio Pro History lists the documents per revision. Review fixes: an earlier revision added `HEAD` + the notes ref to `remote..push`. It was dropped because any push refspec replaces `push.default`. A branch tracking a differently named upstream got a new same-named branch on Team Server where `simple` refuses, a missing local notes ref failed the whole push, and nobody knows whether Studio Pro's own push reads that config. The installer now only fetches the remote's notes (non-forced). It removes that exact `HEAD` + notes pair if present, and leaves and reports any other entry. `sync` handles a remote with no notes ref (it used to exit 1) and retries a rejected push once after re-merging. A note that already lists changes (Studio Pro's) is never overwritten by `write`/`backfill` without `--force`, and `sync` keeps a colleague's non-empty note over ours. The mxcli probe greps `git note --help` for `--write`. Hooks and docs call the script through `resolve_py`. Fixture `tests/wave2/test-mx-metadata-note.sh` (40 cases, over real captured template units; single- and two-tree layouts, amend, backfill and `--force`, push exit codes identical to a no-hooks clone, leftover-refspec cleanup, sync to an empty remote and its retry, notes fetch at install, a diverged notes merge; green under `init.defaultBranch` master and main). — field project (Mendix 11.15 app), MendixMau - learn(fixture-seeding): **explore the data structure first, choose rows by use case, scan actions for resets before any run.** Two seeding runs on public template apps: seeding raised journey passes 33 -> 58 and 20 -> 51 of the same sets (FAIL 93 -> 68, 163 -> 132; INVALID unchanged), and a "Reset all demodata" journey wiped accounts so 104 of 127 journeys reported `The username or password you entered is incorrect`, identical on empty and seeded databases. Skill gains three questions a seed must answer, a use-case checklist, a destructive-action scan, a generic traps table with symptoms, measured expectations and a denominator checklist; routing trigger now fires on seed/test/demo data and empty pages. Independent review fixes: object ids follow the Mendix bigint pattern with a business-key guard (no uuids), associations are seeded where the page's path stores them, four new traps (enum names, inheritance chain, calculated attributes, view/external entities), and the port-offset and container-label traps moved to `harness-architecture.md`. — MendixMau diff --git a/skills/design-spacing.md b/skills/design-spacing.md index 6deb1346..7404c056 100644 --- a/skills/design-spacing.md +++ b/skills/design-spacing.md @@ -97,7 +97,11 @@ row 8). Before any content section, every non-popup page gets: ``` container ctnPageHead (Class: 'page-head spacing-outer-bottom-large') { - dynamictext txtCrumb (Content: 'Home / / ', Class: 'crumb') + container ctnCrumb (Class: 'crumb') { + linkbutton lnkCrumbHome (Caption: 'Home', Action: SHOW_PAGE .) + linkbutton lnkCrumbArea (Caption: '', Action: SHOW_PAGE .) + dynamictext txtCrumbHere (Content: '') // the current page: text, not a link + } dynamictext txtTitle (Content: '', RenderMode: H1) // optional, when the wireframe has one: dynamictext txtSub (Content: '', Class: 'page-sub') @@ -106,6 +110,10 @@ container ctnPageHead (Class: 'page-head spacing-outer-bottom-large') { } ``` +- **A crumb is a link.** Every crumb but the last opens its page, and the wireframe's binding + table names that target; only the current page is text. Plain-text crumbs (`'Home / / + '` in one `dynamictext`) passed every gate on an 81-script build and were found by a + user clicking (#232). The same holds for a stepper: say per step whether it is navigable. - Exactly one `RenderMode: H1` per page — it is also the a11y "one h1" check in `design-audit.js`. Section titles are H2/H3, never a bare `dynamictext` at body size. - Use the project design system's header component/class (`.page-head` or equivalent) when one diff --git a/skills/module-review.md b/skills/module-review.md index 4b8c1683..31d02d86 100644 --- a/skills/module-review.md +++ b/skills/module-review.md @@ -138,6 +138,11 @@ For each page, ask and answer in writing: heights cover them (`learned-detection-gaps.md`). - **Submit every New/Edit form with required fields empty.** A visible validation message must appear. A 4xx/5xx with no user-facing feedback is high-severity silent failure, not a pass. +- **Can I get back?** From each page, return to where you came from without the browser's Back + button: every crumb but the current page links to its page, and so does every stepper step the + wireframe marks navigable (`design-spacing.md` §3). A crumb shown as plain text is a finding. +- **Does every tile go somewhere?** Click every crumb, tile, card and menu entry. Each opens + what its label says: a "Tasks" menu entry opens a list of tasks, not one record's page. - **Does every View/Edit/action button point at the *current* page?** Confirm with `DESCRIBE PAGE Module.Page` — superseded pages from an earlier build script survive as live targets and nothing mechanical notices. From e35c88340161e5f466ef65918ea65ed38f60dad2 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 14:38:33 +0000 Subject: [PATCH 2/2] learn(design-spacing): StyleGallery crumb separators, trimmed LOOK questions (review of #371) Crumb scaffold uses the gallery classes (crumbs, btn-link, crumb-sep) and points at 18-breadcrumbs.mdl for close-page vs show-page. The LOOK questions point at design-spacing.md section 3 and wiring-sweep.md instead of restating them; the oneshot example shows a linked crumb. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VJgWP5vEoAsNsJYqCDMGNw --- CHANGELOG.md | 2 +- skills/design-spacing.md | 16 +++++++++------- skills/module-review.md | 8 +++----- skills/oneshot-page-structure-patterns.md | 6 +++++- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be082344..78bc179e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m ## Unreleased -- learn(design-spacing, module-review): **breadcrumbs are links, and the LOOK asks "can I get back?" and "does every tile go somewhere?"** ([#232](https://github.com/MendixMau/mxcli-project-toolkit/issues/232), the still-open part). `design-spacing.md` §3 prescribed the page-header crumb as one plain-text `dynamictext`, so every wireframe and page copied it and `page-fidelity.js` scored the text crumb as correct. The scaffold now builds the crumb from `linkbutton`s with `SHOW_PAGE` targets, the current page alone as text, plus a rule that each crumb's target is in the wireframe's binding table and that a stepper says per step whether it is navigable. `module-review.md` §4c (the one LOOK question list) gains the two questions. Both files are baseline: +171 words (75,028 → 75,199). Field evidence: an 81-script Stage-5 build where every gate went green and a user found text-only crumbs and a menu entry opening one record instead of a list. — MendixMau +- learn(design-spacing, module-review): **breadcrumbs are links, and the LOOK asks "can I get back?" and "does every tile go somewhere?"** ([#232](https://github.com/MendixMau/mxcli-project-toolkit/issues/232), the still-open part). `design-spacing.md` §3 prescribed the page-header crumb as one plain-text `dynamictext`, so every wireframe and page copied it and `page-fidelity.js` scored the text crumb as correct. The scaffold now builds the crumb the way the StyleGallery does (`crumbs` container, `btn-link` link buttons, `crumb-sep` separators), with the current page and non-page sections as text, plus a rule that each crumb's target is in the wireframe's binding table, that an ancestor still open underneath is reached by close page (pointer to `examples/stylegallery/.../18-breadcrumbs.mdl`), and that a stepper says per step whether it is navigable. `module-review.md` §4c (the one LOOK question list) gains the two questions, the second pointing at `wiring-sweep.md` for the does-it-do-anything half. The ✅ example in `oneshot-page-structure-patterns.md` (on-demand) now shows a linked crumb. Baseline: +131 words (75,028 → 75,159). Field evidence: an 81-script Stage-5 build where every gate went green and a user found text-only crumbs and a menu entry opening one record instead of a list. — MendixMau - process(build): **Stage 5 drafts the ready set in parallel and applies serially.** The runbook dispatch row said "sequential within a module", which serialised drafting even for independent scripts; it now points to `iterative-build-loop.md` → Gate cost, the single statement of the rule. The unit is the build-plan row: every `BUILD` row whose "Depends on" rows are all applied, minus overlapping writers (two rows that replace, alter or drop+create the same document never share a ready set, and no row is drafted while it overlaps a queued unapplied one; `brd-to-build-plan.md` has Produces name every document a row writes), runs at once, its scripts in order, one `mdl-agent` per script. Applies stay one at a time through `bin/exec.sh`, and the ready set is recomputed after each. A queued draft made before a sibling apply landed is re-checked with `mxcli diff --format struct` (preflight STOP rule 23); any removal line (checked against `describe` per BUG-146) or STOP rule 21 target means re-draft, never patch, because a stale restatement of a shared document is exactly rule 21's silent seed-call loss and passes `check --references`. Report `ready set N, drafted N, applied k`; the `mdl-agent` template says a drafter never applies and never depends on an unapplied draft. Field evidence: none yet. — MendixMau - fix(platform-link): **CLI commits now show their changed documents in Studio Pro History.** Studio Pro's History → Model changes pane reads a `ModelChanges` array from the commit's `refs/notes/mx_metadata` note. Trap 6c's hand-written template set it to `[]`, so every mxcli/agent commit showed "Model changes 0" (field project: 270 of 270 notes empty). New `project-bin/mx-metadata-note.py` (stdlib only) computes the array from the commit's changed `.mxunit` files, in Studio Pro's entry shape: Status, UnitID, UnitType, UnitName and Module (walked up the `.mpr` Unit tree). It has `changes` / `write` / `backfill` / `sync ` subcommands. `install-project-hooks.sh` now also installs post-commit and post-rewrite hooks that write the note and never block, and `--check` covers them, so `sync-project.sh` rolls them out to wired projects. Notes are published with `mx-metadata-note.py sync ` after the branch push. Push config is never touched. With mxcli v0.26+ in the project root the hooks call `mxcli git note --write` instead (mendixlabs/mxcli#1337); the Python script is the fallback for older pins, and on 160 field commits both wrote the same 3,594 entries. Trap 6c rewritten. Measured against Studio Pro's own notes, 51 commits in 5 apps: 3387/3389 entries exact on all five fields. Field run on a 357-commit Mendix 11.15.0 app: 158 notes backfilled and pushed, and Studio Pro History lists the documents per revision. Review fixes: an earlier revision added `HEAD` + the notes ref to `remote..push`. It was dropped because any push refspec replaces `push.default`. A branch tracking a differently named upstream got a new same-named branch on Team Server where `simple` refuses, a missing local notes ref failed the whole push, and nobody knows whether Studio Pro's own push reads that config. The installer now only fetches the remote's notes (non-forced). It removes that exact `HEAD` + notes pair if present, and leaves and reports any other entry. `sync` handles a remote with no notes ref (it used to exit 1) and retries a rejected push once after re-merging. A note that already lists changes (Studio Pro's) is never overwritten by `write`/`backfill` without `--force`, and `sync` keeps a colleague's non-empty note over ours. The mxcli probe greps `git note --help` for `--write`. Hooks and docs call the script through `resolve_py`. Fixture `tests/wave2/test-mx-metadata-note.sh` (40 cases, over real captured template units; single- and two-tree layouts, amend, backfill and `--force`, push exit codes identical to a no-hooks clone, leftover-refspec cleanup, sync to an empty remote and its retry, notes fetch at install, a diverged notes merge; green under `init.defaultBranch` master and main). — field project (Mendix 11.15 app), MendixMau - learn(fixture-seeding): **explore the data structure first, choose rows by use case, scan actions for resets before any run.** Two seeding runs on public template apps: seeding raised journey passes 33 -> 58 and 20 -> 51 of the same sets (FAIL 93 -> 68, 163 -> 132; INVALID unchanged), and a "Reset all demodata" journey wiped accounts so 104 of 127 journeys reported `The username or password you entered is incorrect`, identical on empty and seeded databases. Skill gains three questions a seed must answer, a use-case checklist, a destructive-action scan, a generic traps table with symptoms, measured expectations and a denominator checklist; routing trigger now fires on seed/test/demo data and empty pages. Independent review fixes: object ids follow the Mendix bigint pattern with a business-key guard (no uuids), associations are seeded where the page's path stores them, four new traps (enum names, inheritance chain, calculated attributes, view/external entities), and the port-offset and container-label traps moved to `harness-architecture.md`. — MendixMau diff --git a/skills/design-spacing.md b/skills/design-spacing.md index 7404c056..7d409404 100644 --- a/skills/design-spacing.md +++ b/skills/design-spacing.md @@ -97,9 +97,11 @@ row 8). Before any content section, every non-popup page gets: ``` container ctnPageHead (Class: 'page-head spacing-outer-bottom-large') { - container ctnCrumb (Class: 'crumb') { - linkbutton lnkCrumbHome (Caption: 'Home', Action: SHOW_PAGE .) - linkbutton lnkCrumbArea (Caption: '', Action: SHOW_PAGE .) + container ctnCrumb (Class: 'crumbs') { + linkbutton lnkCrumbHome (Caption: 'Home', Action: SHOW_PAGE ., Class: 'btn-link') + dynamictext txtCrumbSep1 (Content: '›', Class: 'crumb-sep') + linkbutton lnkCrumbArea (Caption: '', Action: SHOW_PAGE ., Class: 'btn-link') + dynamictext txtCrumbSep2 (Content: '›', Class: 'crumb-sep') dynamictext txtCrumbHere (Content: '') // the current page: text, not a link } dynamictext txtTitle (Content: '', RenderMode: H1) @@ -110,10 +112,10 @@ container ctnPageHead (Class: 'page-head spacing-outer-bottom-large') { } ``` -- **A crumb is a link.** Every crumb but the last opens its page, and the wireframe's binding - table names that target; only the current page is text. Plain-text crumbs (`'Home / / - '` in one `dynamictext`) passed every gate on an 81-script build and were found by a - user clicking (#232). The same holds for a stepper: say per step whether it is navigable. +- **A crumb is a link**, its target named in the wireframe's binding table. Only the current page + and a section that is not a page are text. An ancestor still open underneath is reached with + close page, not `SHOW_PAGE`: `examples/stylegallery/mdlsource/gallery/18-breadcrumbs.mdl`. + A stepper says per step whether it is navigable. - Exactly one `RenderMode: H1` per page — it is also the a11y "one h1" check in `design-audit.js`. Section titles are H2/H3, never a bare `dynamictext` at body size. - Use the project design system's header component/class (`.page-head` or equivalent) when one diff --git a/skills/module-review.md b/skills/module-review.md index 31d02d86..caf554f2 100644 --- a/skills/module-review.md +++ b/skills/module-review.md @@ -138,11 +138,9 @@ For each page, ask and answer in writing: heights cover them (`learned-detection-gaps.md`). - **Submit every New/Edit form with required fields empty.** A visible validation message must appear. A 4xx/5xx with no user-facing feedback is high-severity silent failure, not a pass. -- **Can I get back?** From each page, return to where you came from without the browser's Back - button: every crumb but the current page links to its page, and so does every stepper step the - wireframe marks navigable (`design-spacing.md` §3). A crumb shown as plain text is a finding. -- **Does every tile go somewhere?** Click every crumb, tile, card and menu entry. Each opens - what its label says: a "Tasks" menu entry opens a list of tasks, not one record's page. +- **Can I get back?** Without the browser's Back button, per `design-spacing.md` §3 crumbs. +- **Does every tile go somewhere?** `wiring-sweep.md` proves each click does something; judge + where it lands: a "Tasks" menu entry opens a list of tasks, not one record's page. - **Does every View/Edit/action button point at the *current* page?** Confirm with `DESCRIBE PAGE Module.Page` — superseded pages from an earlier build script survive as live targets and nothing mechanical notices. diff --git a/skills/oneshot-page-structure-patterns.md b/skills/oneshot-page-structure-patterns.md index bb708218..f5ef13f8 100644 --- a/skills/oneshot-page-structure-patterns.md +++ b/skills/oneshot-page-structure-patterns.md @@ -59,7 +59,11 @@ column "headerCol" { -- ✅ Each element has room to be spaced column "headerCol" { - container "cBreadcrumb" { dynamictext "txtBreadcrumb" (Content: 'Home / List') } + container "cBreadcrumb" (Class: 'crumbs') { -- crumbs are links: design-spacing.md §3 + linkbutton "lnkCrumbHome" (Caption: 'Home', Action: SHOW_PAGE "Module"."Home", Class: 'btn-link') + dynamictext "txtCrumbSep" (Content: '›', Class: 'crumb-sep') + dynamictext "txtCrumbHere" (Content: 'List') + } container "cTitle" { dynamictext "txtTitle" (Content: 'My List') } container "cSubtext" { dynamictext "txtSubtext" (Content: 'A short description') } }