From 4fe4fa623b8a569a7351f92d6f649afe26075868 Mon Sep 17 00:00:00 2001 From: Carles Zaragoza Date: Wed, 14 Oct 2026 22:37:37 +0200 Subject: [PATCH 1/4] learn(multilingual-app): source language, enabled languages, starting language, a switch a normal role can use New skills/learned-multilingual-app.md (ondemand, stages 3/5/6, mdl + review) and a LanguageSelector row in learned-mdl-cannot-express.md. Field run 2026-10-09 (Mendix 11.15.0, mxcli v0.25.0): translations correct, picker errored / blank / no-op with every gate green. System.User_Language is not grantable (mxcli check passes the grant, exec refuses it; Studio Pro does not offer the member), so the Marketplace LanguageSelector cannot work for a normal role; the recipe is a microflow on $currentUser + a reloading nanoflow + CSS on . Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 1 + README.md | 1 + ROUTING.md | 1 + agents/mdl-agent.md | 1 + agents/review-agent.md | 1 + bin/gate-check.sh | 6 +- bin/lib/skill-routing.tsv | 1 + skills/learned-mdl-cannot-express.md | 1 + skills/learned-multilingual-app.md | 173 +++++++++++++++++++++++++++ 9 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 skills/learned-multilingual-app.md diff --git a/CHANGELOG.md b/CHANGELOG.md index ff60ea83..0497400a 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(multilingual-app): **a second language is four decisions the translations never see — source language, enabled languages, the user's starting language, and a switch a normal role can use.** New `skills/learned-multilingual-app.md` (routed on demand, stages 3/5/6, mdl + review). Field run 2026-10-09 (Mendix 11.15.0, mxcli v0.25.0, an app authored in Spanish gaining ES + PT): 418 strings per language written correctly, and still the language picker raised an error, showed a blank label and changed nothing, with check, exec.sh and lint green. Causes: captions authored under the default `en_US` (so `en_US` is the authored language); demo users on that hidden source language; the Marketplace LanguageSelector writing `System.User_Language` from the browser, which a non-admin role cannot and which cannot be granted (mxcli `check` passes the grant, `exec` refuses it; Studio Pro does not offer the member, so the first fix — a Studio Pro hand step — was impossible); `login.html` outside the translation system. Recipe: a microflow sets `$currentUser`'s language, a nanoflow reloads, CSS highlights from ``; seeded users start in a visible language. `learned-mdl-cannot-express.md` gains the LanguageSelector row. Credit: Carles Zaragoza (field build). - 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/README.md b/README.md index 618fe870..bf552c2e 100644 --- a/README.md +++ b/README.md @@ -602,6 +602,7 @@ Every mxcli project has a `.ai-context/skills/` directory (bundled by `mxcli ini |---|---| | Dispatching a build step to a helper — generate its one-file pack (the brief's Build steps row, mxcli brain brief, live DESCRIBEs, example, folder) and hand over the path instead of a reading list. Measured: 25-38% fewer tool calls and 30-41% less time, same quality; the token saving is not proven | `project-bin/context-pack.sh` | | An exec.sh guard refused a write (not a permission prompt), or building a module with mxcli — verified, iterative, coverage-checklist gated; on a refusal: commit, wait or stop, never FORCE_EXEC | `skills/iterative-build-loop.md` | +| Adding a second language, a language picker or a translation script — and the moment a language picker errors, shows a blank label or changes nothing: source language, enabled languages, the user's starting language, a switch a non-admin role can use | `skills/learned-multilingual-app.md` | | After marking a module done, or any time "how much is built vs proven" is asked — renders build-plan.html from done- prefixes and verify-module.sh/improvement-register.md, kept as two honestly separate views; --json writes architecture/build-plan.json parsed from build-plan.md's Phase headings (a plan with no Phase headings gets no file) | `project-bin/build-plan-status.sh` | | Turning a client-derived Mendix app into a clean, shareable demo with zero client fingerprint — branding, data, custom widgets | `skills/anonymize-client-app-for-demo.md` | | Handing a headless-built model to a person — opening it in Studio Pro, a free sandbox, or a colleague's machine: the model travels, the demo data and runtime config (keys, an agent's bound model) do not, and each needs its own re-establish step | `skills/handoff-to-studio-pro.md` | diff --git a/ROUTING.md b/ROUTING.md index c99f42a7..8b746abf 100644 --- a/ROUTING.md +++ b/ROUTING.md @@ -140,6 +140,7 @@ picks the row up. That is the whole procedure — there is no second list to rem | Before any mxcli exec / exec.sh / --mcp write — ask or run? the knob decides | `bin/exec-approval.sh` | mdl,gate | 5,6 | baseline | | Dispatching a build step to a helper — generate its one-file pack (the brief's Build steps row, mxcli brain brief, live DESCRIBEs, example, folder) and hand over the path instead of a reading list. Measured: 25-38% fewer tool calls and 30-41% less time, same quality; the token saving is not proven | `project-bin/context-pack.sh` | mdl | 5 | ondemand | | An exec.sh guard refused a write (not a permission prompt), or building a module with mxcli — verified, iterative, coverage-checklist gated; on a refusal: commit, wait or stop, never FORCE_EXEC | `skills/iterative-build-loop.md` | mdl,gate | 5 | ondemand | +| Adding a second language, a language picker or a translation script — and the moment a language picker errors, shows a blank label or changes nothing: source language, enabled languages, the user's starting language, a switch a non-admin role can use | `skills/learned-multilingual-app.md` | mdl,review | 3,5,6 | ondemand | | After marking a module done, or any time "how much is built vs proven" is asked — renders build-plan.html from done- prefixes and verify-module.sh/improvement-register.md, kept as two honestly separate views; --json writes architecture/build-plan.json parsed from build-plan.md's Phase headings (a plan with no Phase headings gets no file) | `project-bin/build-plan-status.sh` | architect,gate,review | 4,5,6 | ondemand | | Turning a client-derived Mendix app into a clean, shareable demo with zero client fingerprint — branding, data, custom widgets | `skills/anonymize-client-app-for-demo.md` | mdl,review | 6 | ondemand | | Handing a headless-built model to a person — opening it in Studio Pro, a free sandbox, or a colleague's machine: the model travels, the demo data and runtime config (keys, an agent's bound model) do not, and each needs its own re-establish step | `skills/handoff-to-studio-pro.md` | test,review | 6,7 | ondemand | diff --git a/agents/mdl-agent.md b/agents/mdl-agent.md index 1aa9e31b..868b28f8 100644 --- a/agents/mdl-agent.md +++ b/agents/mdl-agent.md @@ -75,6 +75,7 @@ This table is your whole list. The baseline table in the project's CLAUDE.local. | `skills/mdl-cookbook-microflows.md` | Writing MDL microflow scripts | | `skills/build/mdl/oneshot-mdl-method.md` | Writing a single MDL script that takes a project from nothing to a working vertical slice | | `skills/learned-page-patterns.md` | Building and auditing Mendix pages, or starting a UI fix round across many pages | +| `skills/learned-multilingual-app.md` | Adding a second language, a language picker or a translation script | | `skills/oneshot-page-structure-patterns.md` | Generating a whole page tree in one script | | `skills/mendix-agents.md` | Building a Mendix AI agent | | `skills/mendix-agent-ui.md` | Embedding a copilot chat panel | diff --git a/agents/review-agent.md b/agents/review-agent.md index 02245473..815ad732 100644 --- a/agents/review-agent.md +++ b/agents/review-agent.md @@ -65,6 +65,7 @@ This table is your whole list. The baseline table in the project's CLAUDE.local. | `skills/app-analysis.md` | Building or refreshing the standing dossier of an EXISTING app before changing it: inventory, module dependency shape, loop risk patterns, each section with a verdict and a fault where nothing was measured (a standing document; nothing else in the toolkit reads it yet) | | `skills/module-dependency-review.md` | Judging module tangles, bidirectional pairs, cohesion and hubs from analysis/app-facts/dependencies.json; stock graph_module_* views split names on the first dot and are not trusted | | `skills/microflow-loop-antipatterns.md` | Reading what loop bodies do (LOOP_TQ, deferred commit, nested loop, REST in loop, transaction control per item, scheduled-event reachability) from described MDL; the catalog holds top-level activities only and cannot see inside a loop | +| `skills/learned-multilingual-app.md` | Adding a second language, a language picker or a translation script | | `project-bin/conformance-check.sh` | Running the ledger rung alone | | `project-bin/graph-sweep.sh` | Running the wiring rung alone | | `skills/fixture-seeding.md` | Seed data, test data, demo data or fixtures are needed, or pages come up empty | diff --git a/bin/gate-check.sh b/bin/gate-check.sh index 61f648b3..7b244812 100755 --- a/bin/gate-check.sh +++ b/bin/gate-check.sh @@ -1769,10 +1769,10 @@ stage_protocol_paths() { 0) echo "skills/interview-protocol.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-scope.md skills/source-triage.md skills/image-transcription.md skills/small-project-tier.md skills/existing-app-change.md skills/app-analysis.md skills/layering-review.md skills/module-dependency-review.md skills/microflow-loop-antipatterns.md skills/assess-migration.md skills/migration-pipeline.md skills/migrate-general.md skills/migrate-outsystems.md skills/source-os11.md skills/os-xml-schema.md skills/source-node-express-react.md skills/document-discovery.md skills/extractor-quality-loop.md skills/qa-loop-goal-pattern.md skills/mendix-epics-api.md skills/corpus-extraction-integrity.md skills/gate-check-file-locations.md" ;; 1) echo "skills/interview-protocol.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-extraction.md skills/dispatching.md skills/image-transcription.md skills/small-project-tier.md skills/migration-pipeline.md skills/source-os11.md skills/os-xml-schema.md skills/source-node-express-react.md skills/document-discovery.md skills/extractor-quality-loop.md skills/kb-generation.md skills/corpus-extraction-integrity.md" ;; 2) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-brd.md skills/checkpoints/checkpoint-architecture.md skills/dispatching.md skills/image-transcription.md skills/small-project-tier.md skills/kb-generation.md skills/brd-generation.md skills/brd-validation.md" ;; - 3) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-design.md skills/dispatching.md skills/small-project-tier.md skills/mendix-best-practices-index.md skills/layering-review.md skills/architecture-blueprint.md skills/modularize-domain.md skills/design-artifacts.md skills/interaction-model.md skills/brd-to-build-plan.md skills/workflow-structure-rules.md skills/learned-mdl-cannot-express.md" ;; + 3) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-design.md skills/dispatching.md skills/small-project-tier.md skills/mendix-best-practices-index.md skills/layering-review.md skills/architecture-blueprint.md skills/modularize-domain.md skills/design-artifacts.md skills/interaction-model.md skills/brd-to-build-plan.md skills/learned-multilingual-app.md skills/workflow-structure-rules.md skills/learned-mdl-cannot-express.md" ;; 4) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-build.md skills/agent-roles.md skills/dispatching.md skills/small-project-tier.md skills/module-brief.md skills/module-folder-convention.md skills/brd-to-build-plan.md skills/coverage-ledger.md skills/workflow-structure-rules.md skills/rest-integration-first-time-right.md skills/learned-constants-and-secrets.md" ;; - 5|build-ready) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/agent-roles.md skills/dispatching.md skills/module-brief.md skills/learned-mdl-preflight.md skills/module-folder-convention.md skills/learned-microflow-patterns.md skills/microflow-preflight.md skills/mendix-best-practices-index.md skills/ui-preflight-pages.md skills/design-spacing.md skills/learned-stylegallery.md skills/ui-loop.md skills/module-review.md skills/testing-shape.md skills/microflow-loop-antipatterns.md skills/iterative-build-loop.md skills/mdl-cookbook-microflows.md skills/build/mdl/oneshot-mdl-method.md skills/learned-page-patterns.md skills/oneshot-page-structure-patterns.md skills/mendix-agents.md skills/mendix-agent-ui.md skills/mendix-agent-setup.md skills/fixture-seeding.md skills/journey-proof.md skills/unhappy-path-testing.md skills/monkey-test.md skills/e2e-locators-that-lie.md skills/report-schema.md skills/harness-architecture.md skills/process-coherence-pass.md skills/lint-that-actually-runs.md skills/improvement-register.md skills/journey-examples.md skills/wiring-sweep.md skills/learned-workflow-patterns.md skills/workflow-structure-rules.md skills/rest-integration-first-time-right.md skills/bug-submission-checklist.md skills/empty-widget-triage.md skills/learned-sidebar-collapse-icons.md skills/learned-popup-navigation.md skills/learned-datagrid-customcontent-binding.md skills/learned-popup-feedback-pattern.md skills/learned-mdl-cannot-express.md skills/learned-css-that-never-applied.md skills/learned-detection-gaps.md skills/learned-dg2-patterns.md skills/learned-file-upload-widget.md skills/security-is-not-a-later-script.md skills/learned-local-db-confusion.md skills/full-harness-audit.md skills/test-result-audit.md skills/finding-disposition.md skills/preview-over-hub-tunnel.md skills/walking-skeleton.md skills/platform-link.md skills/teamserver-alignment.md skills/learned-constants-and-secrets.md" ;; - 6) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-cutover.md skills/dispatching.md skills/mendix-best-practices-index.md skills/module-review.md skills/testing-shape.md skills/existing-app-assurance.md skills/app-analysis.md skills/module-dependency-review.md skills/microflow-loop-antipatterns.md skills/qa-loop-goal-pattern.md skills/mendix-agent-setup.md skills/e2e-harness-base.md skills/learned-db-assertions.md skills/fixture-seeding.md skills/journey-proof.md skills/unhappy-path-testing.md skills/monkey-test.md skills/e2e-locators-that-lie.md skills/learned-skill-ux-audit.md skills/learned-skill-scope-delta.md skills/report-schema.md skills/harness-architecture.md skills/process-coherence-pass.md skills/e2e-evidence-report.md skills/record-demo-video.md skills/share-demo-package.md skills/lint-that-actually-runs.md skills/improvement-register.md skills/journey-examples.md skills/wiring-sweep.md skills/workflow-structure-rules.md skills/bug-submission-checklist.md skills/empty-widget-triage.md skills/anonymize-client-app-for-demo.md skills/learned-css-that-never-applied.md skills/learned-detection-gaps.md skills/learned-local-db-confusion.md skills/full-harness-audit.md skills/test-result-audit.md skills/finding-disposition.md skills/handoff-to-studio-pro.md skills/preview-over-hub-tunnel.md skills/platform-link.md skills/teamserver-alignment.md skills/learned-constants-and-secrets.md" ;; + 5|build-ready) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/agent-roles.md skills/dispatching.md skills/module-brief.md skills/learned-mdl-preflight.md skills/module-folder-convention.md skills/learned-microflow-patterns.md skills/microflow-preflight.md skills/mendix-best-practices-index.md skills/ui-preflight-pages.md skills/design-spacing.md skills/learned-stylegallery.md skills/ui-loop.md skills/module-review.md skills/testing-shape.md skills/microflow-loop-antipatterns.md skills/iterative-build-loop.md skills/mdl-cookbook-microflows.md skills/build/mdl/oneshot-mdl-method.md skills/learned-page-patterns.md skills/learned-multilingual-app.md skills/oneshot-page-structure-patterns.md skills/mendix-agents.md skills/mendix-agent-ui.md skills/mendix-agent-setup.md skills/fixture-seeding.md skills/journey-proof.md skills/unhappy-path-testing.md skills/monkey-test.md skills/e2e-locators-that-lie.md skills/report-schema.md skills/harness-architecture.md skills/process-coherence-pass.md skills/lint-that-actually-runs.md skills/improvement-register.md skills/journey-examples.md skills/wiring-sweep.md skills/learned-workflow-patterns.md skills/workflow-structure-rules.md skills/rest-integration-first-time-right.md skills/bug-submission-checklist.md skills/empty-widget-triage.md skills/learned-sidebar-collapse-icons.md skills/learned-popup-navigation.md skills/learned-datagrid-customcontent-binding.md skills/learned-popup-feedback-pattern.md skills/learned-mdl-cannot-express.md skills/learned-css-that-never-applied.md skills/learned-detection-gaps.md skills/learned-dg2-patterns.md skills/learned-file-upload-widget.md skills/security-is-not-a-later-script.md skills/learned-local-db-confusion.md skills/full-harness-audit.md skills/test-result-audit.md skills/finding-disposition.md skills/preview-over-hub-tunnel.md skills/walking-skeleton.md skills/platform-link.md skills/teamserver-alignment.md skills/learned-constants-and-secrets.md" ;; + 6) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-cutover.md skills/dispatching.md skills/mendix-best-practices-index.md skills/module-review.md skills/testing-shape.md skills/existing-app-assurance.md skills/app-analysis.md skills/module-dependency-review.md skills/microflow-loop-antipatterns.md skills/qa-loop-goal-pattern.md skills/learned-multilingual-app.md skills/mendix-agent-setup.md skills/e2e-harness-base.md skills/learned-db-assertions.md skills/fixture-seeding.md skills/journey-proof.md skills/unhappy-path-testing.md skills/monkey-test.md skills/e2e-locators-that-lie.md skills/learned-skill-ux-audit.md skills/learned-skill-scope-delta.md skills/report-schema.md skills/harness-architecture.md skills/process-coherence-pass.md skills/e2e-evidence-report.md skills/record-demo-video.md skills/share-demo-package.md skills/lint-that-actually-runs.md skills/improvement-register.md skills/journey-examples.md skills/wiring-sweep.md skills/workflow-structure-rules.md skills/bug-submission-checklist.md skills/empty-widget-triage.md skills/anonymize-client-app-for-demo.md skills/learned-css-that-never-applied.md skills/learned-detection-gaps.md skills/learned-local-db-confusion.md skills/full-harness-audit.md skills/test-result-audit.md skills/finding-disposition.md skills/handoff-to-studio-pro.md skills/preview-over-hub-tunnel.md skills/platform-link.md skills/teamserver-alignment.md skills/learned-constants-and-secrets.md" ;; 7) echo "skills/interview-protocol.md skills/change-intake.md skills/change-intake-detail.md skills/grill-mode.md skills/checkpoints/checkpoint-template.md skills/checkpoints/checkpoint-cutover.md skills/close-the-loop.md skills/share-demo-package.md skills/handoff-to-studio-pro.md skills/platform-link.md skills/teamserver-alignment.md skills/deploy-to-sandbox.md" ;; *) echo "" ;; # diff --git a/bin/lib/skill-routing.tsv b/bin/lib/skill-routing.tsv index edf47908..5757c351 100644 --- a/bin/lib/skill-routing.tsv +++ b/bin/lib/skill-routing.tsv @@ -153,6 +153,7 @@ iterative-build-loop skills/iterative-build-loop.md An exec.sh guard refused a w mdl-cookbook-microflows skills/mdl-cookbook-microflows.md Writing MDL microflow scripts — worked recipes mdl 5 ondemand build/mdl oneshot-mdl-method skills/build/mdl/oneshot-mdl-method.md Writing a single MDL script that takes a project from nothing to a working vertical slice — execution order, why it is deliberately non-idempotent, the instrument hierarchy, and the silent failures that pass every check mdl 5 ondemand build/mdl learned-page-patterns skills/learned-page-patterns.md Building and auditing Mendix pages, or starting a UI fix round across many pages — widget patterns, datasource shapes; for a round: SCSS first, four regressions to check mdl 5 ondemand build/pages +learned-multilingual-app skills/learned-multilingual-app.md Adding a second language, a language picker or a translation script — and the moment a language picker errors, shows a blank label or changes nothing: source language, enabled languages, the user's starting language, a switch a non-admin role can use mdl,review 3,5,6 ondemand build oneshot-page-structure-patterns skills/oneshot-page-structure-patterns.md Generating a whole page tree in one script — the structure patterns that survive it mdl 5 ondemand build/pages mendix-agents skills/mendix-agents.md Building a Mendix AI agent — the agent is runtime data not a model document, so JSON import, tool microflows, knowledge base chunk loading and the runtime wiring all sit outside MDL, and mxbuild stays green when they are wrong mdl,architect 5 ondemand build/agents mendix-agent-ui skills/mendix-agent-ui.md Embedding a copilot chat panel — the frame is yours, ConversationalUI owns the conversation; wireframe to tokens to snippet to page placement mdl 5 ondemand build/agents diff --git a/skills/learned-mdl-cannot-express.md b/skills/learned-mdl-cannot-express.md index 659b9945..0152832f 100644 --- a/skills/learned-mdl-cannot-express.md +++ b/skills/learned-mdl-cannot-express.md @@ -22,6 +22,7 @@ would have taken four minutes to find at Stage 3, when the design was still chea | A **file upload** control | nothing. `mxcli syntax page.widgets` prints the complete keyword set and there is no `filemanager`/`fileuploader`/`imageuploader` in it — `filemanager fm (…)` is a parse error, not an unknown property | ⛔ **not buildable** | | A **pluggable widget with a list-valued property** (an HTML element's `attributes`, a chart's `series`, a datagrid's `columns` written by hand) | nothing. On v0.20.0 a pluggable widget takes **scalar properties in parens and no body at all**: `pluggablewidget '' ( tagName: 'div' )` parses, and *every* body form is rejected with `mismatched input '' expecting '}'` — the object-list keywords (`attribute`, `event`) and the child-slot keywords (`tagcontentcontainer`) alike | ⛔ **not buildable** | | A page-level **boolean a button toggles** (a collapsible section, a show/hide) | page `variables:` exist and can be READ by `visible:`, but nothing in MDL writes one — that needs a nanoflow | ⛔ **not buildable** | +| A **language picker** a non-admin user can use (the Marketplace LanguageSelector) | no grant for `System.User_Language`: `grant read (User_Language) on entity Administration.Account …` passes `check` and `exec` refuses it (`has no member(s) User_Language`, v0.25.0); Studio Pro does not offer the member either, so the widget errors on pick | ⚠️ **workaround**: a microflow sets `$currentUser`'s language + a nanoflow reloads — `learned-multilingual-app.md` §4 | | A **gallery empty-placeholder** | no `emptyState`/`emptyplaceholder` block on `gallery`. On v0.24.0 the Data grid 2 `emptyplaceholder` slot fails mxbuild with CE0463 too, because the bundled definition fixes `showEmptyPlaceholder` to `none` (BUG-150) | ⚠️ **workaround**: a one-attribute non-persistent entity the page switches on | | An **aggregate over an association** inside a gallery/listview template | client expressions have no `count()`; XPath does, but a template expression is not XPath | ⚠️ **workaround**: denormalise, with ONE writer that recomputes rather than increments | | A **count badge on a tab** ("Documents 3") | nothing on v0.24.0: a tab page is written with Caption and Widgets only, `Badge:` is refused by `check`, and `update widgets` reaches only pluggable property bags. `Forms$TabPage.Badge` exists in the metamodel | ⛔ **not buildable** — say the count in the pane's lead line | diff --git a/skills/learned-multilingual-app.md b/skills/learned-multilingual-app.md new file mode 100644 index 00000000..9a64634b --- /dev/null +++ b/skills/learned-multilingual-app.md @@ -0,0 +1,173 @@ +# A second language — the four decisions that decide whether a user ever sees the translations + +**Applies to:** any mxcli project. +**Purpose:** adding a language to a Mendix app built with MDL: which language is the source, which +are enabled, which one a user starts in, and a language switch that works for a non-admin role. +Read it before the first translation script, and the moment a language picker errors, shows a blank +label, or changes nothing. +**Source:** field run 2026-10-09, a regulatory-QA demo app (Mendix 11.15.0, mxcli v0.25.0) authored +in Spanish, with Spanish and Portuguese added as a change request. Writing the strings went cleanly +(418 per language, `create or modify translations without marketplace`). Everything around them +failed — four separate failures, each green on `mxcli check`, `exec.sh` (mxbuild 0 errors) and +`mxcli lint`. Writing the strings themselves is mxcli's `translations` skill; this file is the rest. + +--- + +## The one-line finding + +> The translations were correct the whole time. What the user saw was decided by the source +> language, the user's language and the switch — none of which a gate looks at. + +| What the user saw | What was actually wrong | § | +|---|---|---| +| Picking a language raised an error and nothing changed | The Marketplace LanguageSelector writes `System.User_Language` from the browser; a normal role cannot, and it cannot be granted | 4 | +| The picker showed no text at all | The demo users were on `en_US`, which the picker's list leaves out | 3 | +| There is no English to offer | Captions were authored in Spanish under the default `en_US` | 1 | +| The login page stayed Spanish | `theme/web/login.html` is outside the translation system | 5 | + +--- + +## 1. Decide the source language before the first caption + +Every caption is stored under the project's **default language**, whatever language it is written +in. This app's captions were authored in Spanish under the default `en_US`, so `en_US` *is* Spanish +— a user on `en_US` sees Spanish, and there is no English to offer without retranslating. + +Changing the default later moves no text and nothing warns (`mx check` is 0 errors either way). +So, at Stage 3, ask the user one question and record the answer in `PROJECT.md`: *which language +are captions written in, and is that the language users get, or a hidden source?* + +When the authored language is not the default (the common case on a project that started from +`mxcli new`): keep the default as a hidden source, enable the real languages, and give the authored +language a translation that **copies its strings verbatim**. That same file is where Mendix's own +English platform texts — validation messages, paging, confirmation buttons — are translated; +otherwise they stay English in the Spanish UI. + +```sql +mdl 1; +alter settings language add or modify 'es_ES' ( CheckCompleteness: false ); +alter settings language add or modify 'pt_PT' ( CheckCompleteness: false ); + +create or modify translations without marketplace for es_ES ( + 'Guardar' as 'Guardar', -- authored in Spanish: copied as is + 'Go to first page' as 'Ir a la primera página' -- Mendix platform text: translated +); +``` + +`alter settings` is a STOP-table operation (`learned-mdl-preflight.md`): say it in chat before it +runs. Keep the dictionary in one generator (a small script that writes one `.mdl` per language), +not in hand-edited MDL: a renamed caption has to change in one place, and the scripts are +re-applied after every caption change — this project re-applied both the same day it wrote them, after the switch's captions changed. + +**Probe:** `./mxcli -p .mpr -c "DESCRIBE SETTINGS" | grep -E "DefaultLanguageCode|LANGUAGE add"` +— the default and the enabled list. `describe translations for ` ends with +`N source string(s), M translated, K to go`. + +## 2. Agree what is not translated + +Captions are model text; **data is not**. Product names, imported documents, generated rule +explanations, certificate and CSV contents stay in the language they were stored in. Ask the user +before translating (this project: *data stays Spanish*, recommended in chat, not contested) and +record it — or the first review reports a half-translated app. + +## 3. Start users in a language they can see + +A user's language is `System.User_Language`. A user on no language or on the hidden source gets +source text, and a picker that lists only the real languages has nothing to show as the current +one. That was this project's blank label. + +Set it where accounts are created; for seeded demo accounts, in after-startup, leaving a choice the +user already made alone: + +```sql +retrieve $Spanish from System.Language where Code = 'es_ES' first; +retrieve $Demo from Administration.Account + where [Name = 'demo.user@example.com' and not(System.User_Language/System.Language[Code != 'en_US'])] first; +if $Spanish != empty and $Demo != empty then + change $Demo (System.User_Language = $Spanish) commit without events; +end if; +``` + +## 4. The language switch: do not use the Marketplace LanguageSelector for a normal role + +**What failed.** LanguageSelector v1.1.3 reads `System.User_Language` from the session user and +commits a new value **from the browser**. For a non-admin role that member is not readable or +writable, and there is no way to make it so: + +- **mxcli** — `grant read (User_Language) on entity Administration.Account …` *passes `check`* + and is refused by `exec`, verbatim (recorded 2026-10-09, v0.25.0): + `entity Administration.Account has no member(s) User_Language`. The qualified + `System.User_Language` does not parse there (`mismatched input '.' expecting {',', ')'}`). + This is deliberate in mxcli: a `System.User` platform member must not appear in an access rule + (CE0066). +- **Studio Pro** — the first fix was written as a Studio Pro hand step (Account → access rule → + tick `User_Language`). The user tried it: **Studio Pro does not offer that member.** The hand + step was impossible, and a script had already shipped describing it. + +So do not write that hand step, and do not spend a session on the access rule. Replace the widget. + +**What works.** A microflow applies no entity access, so it may set the signed-in user's own +language; Mendix applies a language when the client loads, so the button reloads afterwards. + +```sql +mdl 1; +/** Sets the signed-in user's language to the System.Language with this code. Only touches the current user; ignores an unknown code. */ +create or modify microflow MyModule.SUB_SetLanguage ($Code: String) +begin + retrieve $Language from System.Language where Code = $Code first; + if $Language != empty and $currentUser != empty then + change $currentUser (System.User_Language = $Language) commit; + end if; +end; + +/** ES button: switch the user to Spanish, then reload so the language applies. */ +create or modify nanoflow MyModule.ACT_LanguageES () +begin + call microflow MyModule.SUB_SetLanguage(Code = 'es_ES'); + call javascript action NanoflowCommons.Reload(); +end; + +grant execute on microflow MyModule.SUB_SetLanguage to MyModule.User; +grant execute on nanoflow MyModule.ACT_LanguageES to MyModule.User; +``` + +One nanoflow per language, two buttons in the layout's top bar (`alter layout … { replace + with { container … { actionbutton … } } }` swaps the widget in place). Needs +NanoflowCommons for `Reload`. Label the buttons in their own words (`ES`, `PT`), not with +`System.Language.Description`, which is whatever the runtime stored and is usually English. +`mxcli lint` flags the reload call (CONV010, a UI step in a nanoflow); accept it in the baseline +with that reason. + +Highlight the active language from **``**, which Mendix writes from the user's language +— no data read, so no access question at all: + +```scss +html[lang^="es"] .lang-switch-es, +html[lang^="pt"] .lang-switch-pt { background: var(--color-primary); color: var(--color-on-primary); } +``` + +**Probe:** in the running app, after switching, `document.documentElement.lang` starts with the new +code. Measured here: `pt` after switching to `pt_PT`, with the menu reading *Histórico*. + +## 5. The login page is yours to translate + +`theme/web/login.html` is a static file: no Mendix translation reaches it and there is no user +language before sign-in. This project gave it `data-i18n` keys, a small inline script swapping them +and an `ES | PT` toggle remembered in `localStorage`. It does not set the user's language after +sign-in — §3 and §4 do. + +## 6. Verify in the running app — per language, per role + +A green gate proves none of §1–§5. **Sign in as a non-admin user** — the §4 failure does not +reproduce as an administrator — and for each enabled language: + +1. Switch; the page reloads in that language: menu, page titles, buttons, grid headers, and one + Mendix platform text (a validation message or the paging bar). +2. The active button is highlighted (`` changed). +3. Sign out and in again: the choice persisted. +4. A page with data shows the data in its stored language (§2). + +**Done means** 4 of 4 checks, for every enabled language × every user role, written down with a +screenshot each (this project: 2 languages × 2 roles, the screenshots in its demo guides). A +language showing source text everywhere is an enabled-language question first +(`describe settings`), before anything is re-translated. From 2d9a6baa616078416acba86b79e60120442719bc Mon Sep 17 00:00:00 2001 From: Carles Zaragoza Date: Thu, 15 Oct 2026 09:42:32 +0200 Subject: [PATCH 2/4] =?UTF-8?q?learn(multilingual-app):=20review=20fixes?= =?UTF-8?q?=20=E2=80=94=20scope=20the=20grant=20claim,=20add=20the=20Syste?= =?UTF-8?q?m.User=20module-role=20probe,=20drop=20the=20retired=20STOP=20#?= =?UTF-8?q?2=20note?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 'cannot be granted' -> 'on this project no grant route was found'; the System module's own rules (via the System.User module role) are named as the open question, with a DESCRIBE USER ROLE probe before replacing the widget (both roles here had System.User; the pick still failed). - STOP #2 (settings writes) retired 2026-09-30: no stop-and-ask for alter settings. - en_US left out of the picker was this project's own filter [Code != 'en_US']. - CONV010 quoted from the rule text; mxcli's DefaultLanguageCode alternative named. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 +- skills/learned-mdl-cannot-express.md | 2 +- skills/learned-multilingual-app.md | 40 +++++++++++++++++++--------- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0497400a..48d8693d 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(multilingual-app): **a second language is four decisions the translations never see — source language, enabled languages, the user's starting language, and a switch a normal role can use.** New `skills/learned-multilingual-app.md` (routed on demand, stages 3/5/6, mdl + review). Field run 2026-10-09 (Mendix 11.15.0, mxcli v0.25.0, an app authored in Spanish gaining ES + PT): 418 strings per language written correctly, and still the language picker raised an error, showed a blank label and changed nothing, with check, exec.sh and lint green. Causes: captions authored under the default `en_US` (so `en_US` is the authored language); demo users on that hidden source language; the Marketplace LanguageSelector writing `System.User_Language` from the browser, which a non-admin role cannot and which cannot be granted (mxcli `check` passes the grant, `exec` refuses it; Studio Pro does not offer the member, so the first fix — a Studio Pro hand step — was impossible); `login.html` outside the translation system. Recipe: a microflow sets `$currentUser`'s language, a nanoflow reloads, CSS highlights from ``; seeded users start in a visible language. `learned-mdl-cannot-express.md` gains the LanguageSelector row. Credit: Carles Zaragoza (field build). +- learn(multilingual-app): **a second language is four decisions the translations never see — source language, enabled languages, the user's starting language, and a switch a normal role can use.** New `skills/learned-multilingual-app.md` (routed on demand, stages 3/5/6, mdl + review). Field run 2026-10-09 (Mendix 11.15.0, mxcli v0.25.0, an app authored in Spanish gaining ES + PT): 418 strings per language written correctly, and still the language picker raised an error, showed a blank label and changed nothing, with check, exec.sh and lint green. Causes: captions authored under the default `en_US` (so `en_US` is the authored language); demo users on that hidden source language; the Marketplace LanguageSelector writing `System.User_Language` from the browser, which this project's non-admin role could not, with no grant route found (mxcli `check` passes the grant, `exec` refuses it; Studio Pro does not offer the member, so the first fix — a Studio Pro hand step — was impossible; a probe on the `System.User` module role now comes first); `login.html` outside the translation system. Recipe: a microflow sets `$currentUser`'s language, a nanoflow reloads, CSS highlights from ``; seeded users start in a visible language. `learned-mdl-cannot-express.md` gains the LanguageSelector row. Credit: Carles Zaragoza (field build). - 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/learned-mdl-cannot-express.md b/skills/learned-mdl-cannot-express.md index 0152832f..507965a8 100644 --- a/skills/learned-mdl-cannot-express.md +++ b/skills/learned-mdl-cannot-express.md @@ -22,7 +22,7 @@ would have taken four minutes to find at Stage 3, when the design was still chea | A **file upload** control | nothing. `mxcli syntax page.widgets` prints the complete keyword set and there is no `filemanager`/`fileuploader`/`imageuploader` in it — `filemanager fm (…)` is a parse error, not an unknown property | ⛔ **not buildable** | | A **pluggable widget with a list-valued property** (an HTML element's `attributes`, a chart's `series`, a datagrid's `columns` written by hand) | nothing. On v0.20.0 a pluggable widget takes **scalar properties in parens and no body at all**: `pluggablewidget '' ( tagName: 'div' )` parses, and *every* body form is rejected with `mismatched input '' expecting '}'` — the object-list keywords (`attribute`, `event`) and the child-slot keywords (`tagcontentcontainer`) alike | ⛔ **not buildable** | | A page-level **boolean a button toggles** (a collapsible section, a show/hide) | page `variables:` exist and can be READ by `visible:`, but nothing in MDL writes one — that needs a nanoflow | ⛔ **not buildable** | -| A **language picker** a non-admin user can use (the Marketplace LanguageSelector) | no grant for `System.User_Language`: `grant read (User_Language) on entity Administration.Account …` passes `check` and `exec` refuses it (`has no member(s) User_Language`, v0.25.0); Studio Pro does not offer the member either, so the widget errors on pick | ⚠️ **workaround**: a microflow sets `$currentUser`'s language + a nanoflow reloads — `learned-multilingual-app.md` §4 | +| A **language picker** a non-admin user can use (the Marketplace LanguageSelector) | no grant route found on one project for `System.User_Language`: `grant read (User_Language) on entity Administration.Account …` passes `check` and `exec` refuses it (`has no member(s) User_Language`, v0.25.0); Studio Pro does not offer the member either, so the widget errors on pick | ⚠️ **workaround**: a microflow sets `$currentUser`'s language + a nanoflow reloads — `learned-multilingual-app.md` §4 | | A **gallery empty-placeholder** | no `emptyState`/`emptyplaceholder` block on `gallery`. On v0.24.0 the Data grid 2 `emptyplaceholder` slot fails mxbuild with CE0463 too, because the bundled definition fixes `showEmptyPlaceholder` to `none` (BUG-150) | ⚠️ **workaround**: a one-attribute non-persistent entity the page switches on | | An **aggregate over an association** inside a gallery/listview template | client expressions have no `count()`; XPath does, but a template expression is not XPath | ⚠️ **workaround**: denormalise, with ONE writer that recomputes rather than increments | | A **count badge on a tab** ("Documents 3") | nothing on v0.24.0: a tab page is written with Caption and Widgets only, `Badge:` is refused by `check`, and `update widgets` reaches only pluggable property bags. `Forms$TabPage.Badge` exists in the metamodel | ⛔ **not buildable** — say the count in the pane's lead line | diff --git a/skills/learned-multilingual-app.md b/skills/learned-multilingual-app.md index 9a64634b..f269daa0 100644 --- a/skills/learned-multilingual-app.md +++ b/skills/learned-multilingual-app.md @@ -20,8 +20,8 @@ failed — four separate failures, each green on `mxcli check`, `exec.sh` (mxbui | What the user saw | What was actually wrong | § | |---|---|---| -| Picking a language raised an error and nothing changed | The Marketplace LanguageSelector writes `System.User_Language` from the browser; a normal role cannot, and it cannot be granted | 4 | -| The picker showed no text at all | The demo users were on `en_US`, which the picker's list leaves out | 3 | +| Picking a language raised an error and nothing changed | The Marketplace LanguageSelector writes `System.User_Language` from the browser; a normal role could not, and on this project no grant route was found | 4 | +| The picker showed no text at all | The demo users were on `en_US`, which this project's picker configuration left out of its list | 3 | | There is no English to offer | Captions were authored in Spanish under the default `en_US` | 1 | | The login page stayed Spanish | `theme/web/login.html` is outside the translation system | 5 | @@ -34,6 +34,8 @@ in. This app's captions were authored in Spanish under the default `en_US`, so ` — a user on `en_US` sees Spanish, and there is no English to offer without retranslating. Changing the default later moves no text and nothing warns (`mx check` is 0 errors either way). +mxcli's own fix for a wrong source is to change `DefaultLanguageCode` and re-run the `create` +statements that wrote the captions — workable while the scripts are the whole source of truth. So, at Stage 3, ask the user one question and record the answer in `PROJECT.md`: *which language are captions written in, and is that the language users get, or a hidden source?* @@ -54,8 +56,8 @@ create or modify translations without marketplace for es_ES ( ); ``` -`alter settings` is a STOP-table operation (`learned-mdl-preflight.md`): say it in chat before it -runs. Keep the dictionary in one generator (a small script that writes one `.mdl` per language), +Settings writes work from the CLI (`learned-mdl-preflight.md` STOP #2, retired 2026-09-30), so +this needs no stop-and-ask. Keep the dictionary in one generator (a small script that writes one `.mdl` per language), not in hand-edited MDL: a renamed caption has to change in one place, and the scripts are re-applied after every caption change — this project re-applied both the same day it wrote them, after the switch's captions changed. @@ -73,8 +75,9 @@ record it — or the first review reports a half-translated app. ## 3. Start users in a language they can see A user's language is `System.User_Language`. A user on no language or on the hidden source gets -source text, and a picker that lists only the real languages has nothing to show as the current -one. That was this project's blank label. +source text, and a picker whose list leaves that language out has nothing to show as the current +one. That was this project's blank label — observed with this widget configuration: the list was +`System.Language` filtered `[Code != 'en_US']`, so the demo users' own language was not in it. Set it where accounts are created; for seeded demo accounts, in after-startup, leaving a choice the user already made alone: @@ -91,20 +94,30 @@ end if; ## 4. The language switch: do not use the Marketplace LanguageSelector for a normal role **What failed.** LanguageSelector v1.1.3 reads `System.User_Language` from the session user and -commits a new value **from the browser**. For a non-admin role that member is not readable or -writable, and there is no way to make it so: +commits a new value **from the browser**. For this project's non-admin role the pick raised an +error and changed nothing, and **on this project no grant route was found**: - **mxcli** — `grant read (User_Language) on entity Administration.Account …` *passes `check`* and is refused by `exec`, verbatim (recorded 2026-10-09, v0.25.0): `entity Administration.Account has no member(s) User_Language`. The qualified `System.User_Language` does not parse there (`mismatched input '.' expecting {',', ')'}`). - This is deliberate in mxcli: a `System.User` platform member must not appear in an access rule - (CE0066). + This is deliberate in mxcli: its access-rule walk stops at `System.User`, so a platform member + listed in a rule on a specialization is CE0066. - **Studio Pro** — the first fix was written as a Studio Pro hand step (Account → access rule → tick `User_Language`). The user tried it: **Studio Pro does not offer that member.** The hand step was impossible, and a script had already shipped describing it. -So do not write that hand step, and do not spend a session on the access rule. Replace the widget. +What is **not** established is that a normal user can never write their own `User_Language`: that +member is governed by the System module's own access rules, which a user role receives through the +`System.User` module role. So before replacing the widget, run one probe: + +- `./mxcli -p .mpr -c 'DESCRIBE USER ROLE ""'` — does `ModuleRoles` include `System.User`? + (On this project it did, for both roles, and the pick still failed.) +- Is the picker reading the session user (`System.User`) rather than a specialization such as + `Administration.Account`? This widget takes no user binding; it uses the session user. + +If both hold and the pick still fails, do not write the Account hand step and do not spend a session +on the access rule. Replace the widget — the recipe below works either way. **What works.** A microflow applies no entity access, so it may set the signed-in user's own language; Mendix applies a language when the client loads, so the button reloads afterwards. @@ -135,8 +148,9 @@ One nanoflow per language, two buttons in the layout's top bar (`alter layout with { container … { actionbutton … } } }` swaps the widget in place). Needs NanoflowCommons for `Reload`. Label the buttons in their own words (`ES`, `PT`), not with `System.Language.Description`, which is whatever the runtime stored and is usually English. -`mxcli lint` flags the reload call (CONV010, a UI step in a nanoflow); accept it in the baseline -with that reason. +`mxcli lint` flags the nanoflow — CONV010, *"ACT_ microflows should only contain UI actions and +sub-microflow calls"*, raised here on the JavaScript `Reload` call; accept it in the baseline with +that reason, or name the nanoflow without the `ACT_` prefix. Highlight the active language from **``**, which Mendix writes from the user's language — no data read, so no access question at all: From aa1ab93ca830242bd0e6afe066af5c78218f9c69 Mon Sep 17 00:00:00 2001 From: MendixMau Date: Fri, 16 Oct 2026 10:30:28 +0800 Subject: [PATCH 3/4] Route the multilingual skill to the architect role too Source language is a Stage 3 design decision, so the architect stub gets the on-demand row. Routing re-rendered; baseline unchanged at 75225 words. Co-Authored-By: Claude Opus 5.5 --- ROUTING.md | 2 +- agents/architect-agent.md | 1 + bin/lib/skill-routing.tsv | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ROUTING.md b/ROUTING.md index a9aea6bb..f26b1b73 100644 --- a/ROUTING.md +++ b/ROUTING.md @@ -143,7 +143,7 @@ picks the row up. That is the whole procedure — there is no second list to rem | Before any mxcli exec / exec.sh / --mcp write — ask or run? the knob decides | `bin/exec-approval.sh` | mdl,gate | 5,6 | baseline | | Dispatching a build step to a helper — generate its one-file pack (the brief's Build steps row, mxcli brain brief, live DESCRIBEs, example, folder) and hand over the path instead of a reading list. Measured: 25-38% fewer tool calls and 30-41% less time, same quality; the token saving is not proven | `project-bin/context-pack.sh` | mdl | 5 | ondemand | | An exec.sh guard refused a write (not a permission prompt), or building a module with mxcli — verified, iterative, coverage-checklist gated; on a refusal: commit, wait or stop, never FORCE_EXEC | `skills/iterative-build-loop.md` | mdl,gate | 5 | ondemand | -| Adding a second language, a language picker or a translation script — and the moment a language picker errors, shows a blank label or changes nothing: source language, enabled languages, the user's starting language, a switch a non-admin role can use | `skills/learned-multilingual-app.md` | mdl,review | 3,5,6 | ondemand | +| Adding a second language, a language picker or a translation script — and the moment a language picker errors, shows a blank label or changes nothing: source language, enabled languages, the user's starting language, a switch a non-admin role can use | `skills/learned-multilingual-app.md` | architect,mdl,review | 3,5,6 | ondemand | | After marking a module done, or any time "how much is built vs proven" is asked — renders build-plan.html from done- prefixes and verify-module.sh/improvement-register.md, kept as two honestly separate views; --json writes architecture/build-plan.json parsed from build-plan.md's Phase headings (a plan with no Phase headings gets no file) | `project-bin/build-plan-status.sh` | architect,gate,review | 4,5,6 | ondemand | | Turning a client-derived Mendix app into a clean, shareable demo with zero client fingerprint — branding, data, custom widgets | `skills/anonymize-client-app-for-demo.md` | mdl,review | 6 | ondemand | | Handing a headless-built model to a person — opening it in Studio Pro, a free sandbox, or a colleague's machine: the model travels, the demo data and runtime config (keys, an agent's bound model) do not, and each needs its own re-establish step | `skills/handoff-to-studio-pro.md` | test,review | 6,7 | ondemand | diff --git a/agents/architect-agent.md b/agents/architect-agent.md index 2bfd0c0d..17f66961 100644 --- a/agents/architect-agent.md +++ b/agents/architect-agent.md @@ -72,6 +72,7 @@ This table is your whole list. The baseline table in the project's CLAUDE.local. | `skills/brd-to-build-plan.md` | Turning BRDs plus architecture into a numbered, dependency-ordered build plan | | `skills/coverage-ledger.md` | Building the Stage 4 coverage ledger | | `bin/coverage-check.sh` | Checking a coverage ledger against its BRD | +| `skills/learned-multilingual-app.md` | Adding a second language, a language picker or a translation script | | `skills/mendix-agents.md` | Building a Mendix AI agent | | `skills/company-brain.md` | Setting up or wiring a COMPANY BRAIN | | `skills/close-the-loop.md` | Cutover and retrospective | diff --git a/bin/lib/skill-routing.tsv b/bin/lib/skill-routing.tsv index 7d40930b..ca3bbfed 100644 --- a/bin/lib/skill-routing.tsv +++ b/bin/lib/skill-routing.tsv @@ -160,7 +160,7 @@ iterative-build-loop skills/iterative-build-loop.md An exec.sh guard refused a w mdl-cookbook-microflows skills/mdl-cookbook-microflows.md Writing MDL microflow scripts — worked recipes mdl 5 ondemand build/mdl oneshot-mdl-method skills/build/mdl/oneshot-mdl-method.md Writing a single MDL script that takes a project from nothing to a working vertical slice — execution order, why it is deliberately non-idempotent, the instrument hierarchy, and the silent failures that pass every check mdl 5 ondemand build/mdl learned-page-patterns skills/learned-page-patterns.md Building and auditing Mendix pages, or starting a UI fix round across many pages — widget patterns, datasource shapes; for a round: SCSS first, four regressions to check mdl 5 ondemand build/pages -learned-multilingual-app skills/learned-multilingual-app.md Adding a second language, a language picker or a translation script — and the moment a language picker errors, shows a blank label or changes nothing: source language, enabled languages, the user's starting language, a switch a non-admin role can use mdl,review 3,5,6 ondemand build +learned-multilingual-app skills/learned-multilingual-app.md Adding a second language, a language picker or a translation script — and the moment a language picker errors, shows a blank label or changes nothing: source language, enabled languages, the user's starting language, a switch a non-admin role can use architect,mdl,review 3,5,6 ondemand build oneshot-page-structure-patterns skills/oneshot-page-structure-patterns.md Generating a whole page tree in one script — the structure patterns that survive it mdl 5 ondemand build/pages mendix-agents skills/mendix-agents.md Building a Mendix AI agent — the agent is runtime data not a model document, so JSON import, tool microflows, knowledge base chunk loading and the runtime wiring all sit outside MDL, and mxbuild stays green when they are wrong mdl,architect 5 ondemand build/agents mendix-agent-ui skills/mendix-agent-ui.md Embedding a copilot chat panel — the frame is yours, ConversationalUI owns the conversation; wireframe to tokens to snippet to page placement mdl 5 ondemand build/agents From 58ba8f54a9f13353bbc553c249d2009dc66fcec1 Mon Sep 17 00:00:00 2001 From: MendixMau Date: Fri, 16 Oct 2026 11:00:19 +0800 Subject: [PATCH 4/4] changelog: multilingual skill is also routed to architect Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 546d7144..1abeaeca 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(multilingual-app): **a second language is four decisions the translations never see — source language, enabled languages, the user's starting language, and a switch a normal role can use.** New `skills/learned-multilingual-app.md` (routed on demand, stages 3/5/6, mdl + review). Field run 2026-10-09 (Mendix 11.15.0, mxcli v0.25.0, an app authored in Spanish gaining ES + PT): 418 strings per language written correctly, and still the language picker raised an error, showed a blank label and changed nothing, with check, exec.sh and lint green. Causes: captions authored under the default `en_US` (so `en_US` is the authored language); demo users on that hidden source language; the Marketplace LanguageSelector writing `System.User_Language` from the browser, which this project's non-admin role could not, with no grant route found (mxcli `check` passes the grant, `exec` refuses it; Studio Pro does not offer the member, so the first fix — a Studio Pro hand step — was impossible; a probe on the `System.User` module role now comes first); `login.html` outside the translation system. Recipe: a microflow sets `$currentUser`'s language, a nanoflow reloads, CSS highlights from ``; seeded users start in a visible language. `learned-mdl-cannot-express.md` gains the LanguageSelector row. Credit: Carles Zaragoza (field build). +- learn(multilingual-app): **a second language is four decisions the translations never see — source language, enabled languages, the user's starting language, and a switch a normal role can use.** New `skills/learned-multilingual-app.md` (routed on demand, stages 3/5/6, architect + mdl + review). Field run 2026-10-09 (Mendix 11.15.0, mxcli v0.25.0, an app authored in Spanish gaining ES + PT): 418 strings per language written correctly, and still the language picker raised an error, showed a blank label and changed nothing, with check, exec.sh and lint green. Causes: captions authored under the default `en_US` (so `en_US` is the authored language); demo users on that hidden source language; the Marketplace LanguageSelector writing `System.User_Language` from the browser, which this project's non-admin role could not, with no grant route found (mxcli `check` passes the grant, `exec` refuses it; Studio Pro does not offer the member, so the first fix — a Studio Pro hand step — was impossible; a probe on the `System.User` module role now comes first); `login.html` outside the translation system. Recipe: a microflow sets `$currentUser`'s language, a nanoflow reloads, CSS highlights from ``; seeded users start in a visible language. `learned-mdl-cannot-express.md` gains the LanguageSelector row. Credit: Carles Zaragoza (field build). - fix(conformance-check): read ledger rows whose pointer cell is bare (no backticks) and treat status `planned` as not-built; a ledger whose acceptance text is also bare prose is still not measured (acceptance must hold a backticked SHOW/DESCRIBE), but it now exits 2 with a FAULT that says rows were found and the text is prose, instead of "shape changed or glob matched nothing" (TK-006, partial: that ledger shape is diagnosed, not fixed) — field report TK-006 - fix(app-restart): **a secret with spaces in quotes is masked whole, and `Pwd` and `Credential` join the secret words.** Follow-up to #403: `--constant "Mod.ApiKey=two words"` and `--db-password 'two words'` printed everything after the first word (the quoted-flag case leaked before #403 too). `mask()` now masks each argument before quoting it for display, so the whole quoted value goes; the executed command is untouched, and a real-launch test proves the started app receives the raw argv; an env assignment at the start of a `--cmd` string keeps the whole command visible; the per-argument seds only run for arguments holding a secret word. The substring match still over-masks names like `Monkey` (Key) and `Turkey`; that is display only, kept, and pinned by a test. `tests/wave2/test-app-restart.sh` gains 22 cases, 8 of them failing on master. — review of #403 - fix(install-manifest): **a clean toolkit checkout passes its own `project-bin/` manifest self-check, so adopters no longer see the "named by NEITHER list" warning at the top of every sync.** `coverage-check-all.sh` (found by `review-module.sh` through its `_tool` lookup in the toolkit clone) and `ts-sync.sh` (UNPROVEN and SUPERSEDED per its header) join `MXTK_PROJECT_BIN_NOINSTALL` with reasons. New `tests/wave2/test-manifest-clean-checkout.sh` fails, rather than warns, when a `project-bin/` file is in neither list. — joshuamoesa (#381)