Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are.
- learn(skills/microflow-preflight.md, agents/mdl-agent.md): **a microflow now gets a tier before any MDL — Simple, Guided or Split-first — and Split-first means a posted split plan (thin orchestrator + one `SUB_` per responsibility, with signatures) that the user confirms first.** Prompted by a colleague's session refusing a long microflow as "too difficult" while the same task went through on a stronger model: the piece was too big, not the task. mdl-agent gains two rules — never hand back "too difficult", hand back the split plan; escalate one failing `SUB_` by name after one retry, never the whole script. Also records the mxcli team's answer on positioning: a standalone `mxcli layout` command — which on v0.24.0 and upstream main (2026-09-25) arranges domain models only (`--dry-run` on a scratch copy of a PoC model: 10 entity moves, no microflow), so the no-`@position` rule and the if-branch workaround stand until a microflow mode ships; noted in the bug ledger and `learned-microflow-patterns.md` — Maurits Visser
- learn(bug-logs): **`BUG-DRAFT-layout-merge-in-if-branch`** — the v0.24.0 layout engine (upstream #1154) puts the merge node on top of the activity that follows a loop inside an `if` branch, so `mxcli lint` reports MPR008 on a correct script that carries no `@position`; plain if/else and a branch holding only the loop lay out clean. Paste-ready draft in `bug-logs/pending-github-issues/`, NOT YET FILED. Matters to the lint ratchet (#134): an MPR008 whose two elements are a merge and the activity after a loop in an `if` branch is this bug, fixed by the workaround, never by `--update-baseline`. Measured on mxcli v0.24.0, Mendix 11.12.1, scratch copy of a PoC model — Maurits Visser
- new(skills/microflow-preflight.md): **a microflow with a loop, a list built from a list, or more than 20 activities now gets a posted checklist before its first MDL line.** Lint runs only after exec. CONV009/QUAL003 count top-level activities only, so 20 activities inside one loop count as 3. Retrieve, REST and Java calls inside a loop have no lint rule at all. The skill maps each Mendix microflow best practice to the rule that catches it afterwards, or marks it "preflight is the only check". It sets the layout rule to "omit `@position`", which upstream now states too, and adds STOP row 25, a baseline routing row and an mdl-agent bullet. It corrects the stale `reset layout` advice (BUG-28), the commit-in-loop cookbook recipe and the 30–50-activity guideline. Field evidence: measured on mxcli v0.24.0 on a scratch copy of a real model — CONV011 on a commit in a loop, MPR008 on partial and 100 px hand placement, MPR011 on negative loop-body coordinates, and a v0.24.0 auto-layout defect where a loop followed by an activity in an `if` branch puts the merge on that activity (MPR008 on a correct script) — Maurits Visser
- learn(learned-microflow-patterns, mdl-cookbook-microflows): **validation microflows collect every field error, then stop once — never `validation feedback` + `return` per field.** An empty form flagged one field per submit: the first input turned red, the user fixed it and resubmitted, and only then saw the second, because each check returned early and the later ones never ran (44 feedback-then-`return` sites across 11 scripts in the reporting project). The "Validation Feedback — Correct Pattern" section now leads with the rule and a before/after (early-return vs `$IsValid` collect-all, a cross-field check guarded on its own inputs, state guards still allowed to return early), and the cookbook's guard-chain example says it is for state guards only. `mxcli check` passes both shapes. — a requirements-driven RFQ project (product owner report, 2026-09-25)
- fix(bug-logs): **BUG-155 — `alter page … set RenderMode` on a dynamic text, with an upstream-ready fix package** (issue draft, 3-commit patch, PR body, submission steps in `bug-logs/pending-github-issues/bug155-*`; fix revert-proven and `mx check`-clean on 11.14.0) — a requirements-driven RFQ project
- fix(project-bin/exec.sh): **the lint ratchet now runs after every clean mxbuild, and its verdict lands in the same BUILD-LOG row.** mxbuild proves the model compiles, not how it is built: on a field project a migration microflow shipped three commits inside loops (`mxcli lint` CONV011) under "✅ applied · mxbuild clean", because lint lived only in the gate-agent definition as an optional step and that agent had been spawned 0 times against 21 `exec.sh` runs. `exec.sh` now calls `bin/lint-gate.sh` after a clean build; a rise vs the committed baseline writes `⚠️ applied, LINT ROSE: <rules>` and exits 1 while keeping the write (shape, not corruption), `SKIP_LINT=<reason>` is the only override and is recorded in the row, and a project without `lint-gate.sh` gets `lint not installed` rather than a blank cell. `agents/gate-agent.md` and `agents/agent-roles.md` now read the row instead of treating lint as optional; `learned-detection-gaps.md` gains the register row. Field run: a marketplace guest-group migration, 2026-09-24, ~13 s per run on a 10k-finding project — Maurits Visser
- learn(ui-loop, learned-detection-gaps): **the screenshot harness is an instrument, and nobody
scores it.** Two harness defects on the PRD benchmark's Arm A produced screenshots that were
Expand Down
23 changes: 23 additions & 0 deletions bug-logs/mxcli-bugs.md
Original file line number Diff line number Diff line change
Expand Up @@ -5499,6 +5499,29 @@ and render it in `DESCRIBE MICROFLOW` so the round trip does not silently flip i
callee's flag is readable in the model.
---

## BUG-155: `alter page … set RenderMode` is refused on a dynamic text, though `create page` writes it — fix ready on a fork branch

**Severity:** Low — loud refusal, clean workaround; costs a full widget restatement per heading-level change
**mxcli version:** main `31eee45` / v0.21.0
**Mendix version:** 11.14.0
**Discovered:** 2026-09-22, promoting a comparison page's title to H2 on a requirements-driven RFQ project
**Reproducible:** yes — any `dynamictext`, page or snippet

`set RenderMode = H2 on txtTitle` fails with `property "RenderMode" not found (widget has no
pluggable Object)`. Root cause: the MPR backend's fixed property list in
`setRawWidgetPropertyMut` lacks `RenderMode`, so it falls through to the pluggable setter; the MCP
backend's mutator already handles it (the two lists drifted).

**Workaround:** `replace txtTitle with { dynamictext txtTitle (Content: '…', RenderMode: H2, Class: '…') }`
— restate content, params and class.

**Fix:** written, tested (fails-then-passes, revert-proven), validated on a real 11.14.0 model
(`mx check` 0 errors). Upstream package — issue draft, patch, PR body, submission steps — in
`pending-github-issues/bug155-*`. Not yet filed upstream.

**Not covered by the fix:** `set Content`/`ContentParams` on a dynamic text (parser-level),
container `RenderMode`, button `RenderType` — all still need `replace`.

## BUG-DRAFT-loop-var-expression-typecheck: the expression type checker is skipped inside a `LOOP` body — the identical expression is caught on a parameter and missed on a loop variable (2026-09-15)

> **FILED UPSTREAM 2026-09-15 — https://github.com/mendixlabs/mxcli/issues/1100**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
**Repo:** `mendixlabs/mxcli`
**Source:** `bug-logs/mxcli-bugs.md`, `## BUG-155` — found 2026-09-22 building a supplier-portal
comparison page on a requirements-driven RFQ project (Mendix 11.14.0)
**Status:** NOT YET FILED — fix ready on fork branch `MendixMau/mxcli:fix/alter-page-set-rendermode`
(fork PR MendixMau/mxcli#1); companion `bug155-fix-README.md` has the PR package
**Suggested labels:** bug, alter-page

---

**Title:** `ALTER PAGE … SET RenderMode` is refused on a dynamic text, though `CREATE PAGE` writes it

**Body:**

## Summary

`RenderMode` is how a dynamic text becomes a heading (`H1`–`H6`) or a paragraph. `CREATE PAGE`
writes it (`dynamictext x (Content: 'Title', RenderMode: H1)`), `DESCRIBE PAGE` round-trips it, and
the MCP backend's page mutator already sets it — but the MPR backend's `ALTER PAGE … SET` refuses
it, so fixing one heading level means replacing the whole widget.

## Environment

- mxcli main at `31eee45` (also seen on v0.21.0)
- Mendix **11.14.0**
- Linux container

## Repro

```sql
alter page MyModule.SomePage {
set RenderMode = H2 on txtTitle -- txtTitle is a DYNAMICTEXT
};
```

**Actual:**

```
failed to set RenderMode on txtTitle: property "RenderMode" not found (widget has no pluggable Object)
```

(on current main the refusal is worded as "not a property of this built-in widget … use alter
styling" — which is also wrong: RenderMode is not a design property).

**Expected:** `Altered page`, and `describe page` shows `RenderMode: H2`.

**Workaround:** `replace txtTitle with { dynamictext txtTitle (Content: '…', RenderMode: H2) }` —
which means restating the content, its parameters and its class just to change one enum.

## Root cause

`setRawWidgetPropertyMut` in `mdl/backend/pagemutator/mutator.go` handles a fixed list of built-in
properties and sends everything else to the pluggable-widget setter. `RenderMode` is not on the
list, so it falls through and fails. `mdl/backend/mcp/page_mutator.go` already handles it — the
two property lists have drifted.

## Proposed fix

Add a `rendermode` case that applies only to a dynamic text (`Forms$DynamicText`), accepts
`Text | Paragraph | H1..H6` case-insensitively, stores the canonical spelling, and names the
allowed set on any other value or widget. A fix with tests and docs is ready; happy to open the PR
once this is approved.

**Out of scope (separate issues if wanted):** `set Content` / `ContentParams` on a dynamic text
(parser-level), `RenderMode` on a container, `RenderType` on a button.
111 changes: 111 additions & 0 deletions bug-logs/pending-github-issues/bug155-fix-README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
**Companion to:** `bug155-alter-page-set-rendermode-dynamictext.md`
**Patch:** `bug155-fix.patch` (git format-patch, 3 commits — fix, docs, test comment; author is a placeholder —
`git commit --amend --reset-author` after `git am`)
**Branch, ready to use:** `MendixMau/mxcli:fix/alter-page-set-rendermode` (fork PR MendixMau/mxcli#1)
**Status:** verified 2026-09-22 against `mendixlabs/mxcli` main `31eee45`; NOT YET submitted —
mxcli's CONTRIBUTING requires the issue to be filed and approved before a PR

---

# BUG-155 fix — how to turn it into the upstream PR

## What is in it (8 files, one concern)

| File | Change |
|---|---|
| `mdl/backend/pagemutator/mutator.go` | `rendermode` case in `setRawWidgetPropertyMut`: dynamic text only; `Text`, `Paragraph`, `H1`–`H6`, any casing, stored canonical; any other value or widget is an error naming the allowed set |
| `mdl/backend/pagemutator/dynamictext_rendermode_test.go` | `TestSetWidgetProperty_DynamicTextRenderMode` (every value, mixed casing) and `…Invalid` (`H7`, and a non-dynamic-text widget) |
| `mdl-examples/bug-tests/alter-page-set-rendermode-dynamictext.mdl` | page and snippet repro with the expected output |
| `.claude/skills/fix-issue/findings/mdl-backend.jsonl` | the finding (symptom, root cause, the MCP/MPR drift) |
| `.claude/skills/mendix/alter-page/SKILL.md` | RenderMode in the SET table |
| `docs-site/src/language/alter-page.md` | same |
| `docs/01-project/MDL_QUICK_REFERENCE.md` | table row and "Supported SET properties" list |
| `cmd/mxcli/syntax/features_page.go` | `mxcli syntax page.alter` shows the `SET RenderMode` line |

## Evidence already gathered

- **Test first:** both new tests fail on `31eee45` and pass with the fix; reverting the
`mutator.go` hunk makes them fail again.
- `make build && make test && make lint` pass (ANTLR 4.13.2, Go toolchain from `go.mod`).
- **Real model:** on a scratch copy of an 11.14.0 project, the unfixed binary refused
`set RenderMode = H2 on compTitle`; the fixed binary printed `Altered page`, `describe page`
showed `RenderMode: H2`, native `mx check` reported 0 errors, and exactly one `.mxunit` changed.
- **Agentic:** the statement was written by Claude Code from the skill text, with no hints.

## Steps (mxcli CONTRIBUTING, Steps 1–6)

1. File the issue: paste the companion draft (`bash render-paste-ready.sh` strips the local
header). Wait for the maintainer's go-ahead and assign yourself. Note the number `NNN`.
2. Get the code, either way:
```bash
# a) straight from the fork branch
git clone https://github.com/MendixMau/mxcli && cd mxcli
git checkout fix/alter-page-set-rendermode
git checkout -b fix/NNN-alter-page-set-rendermode
# b) or onto a fresh upstream checkout
git checkout -b fix/NNN-alter-page-set-rendermode origin/main
git am <toolkit>/bug-logs/pending-github-issues/bug155-fix.patch
```
Then `git rebase -i origin/main` → squash to one commit if the maintainer prefers, reword it to
`fix: ALTER PAGE/SNIPPET SET RenderMode on a dynamic text (closes #NNN)`, `--reset-author`,
and drop trailers you do not want upstream. Optionally rename the bug-test to
`NNN-alter-page-set-rendermode-dynamictext.mdl` (upstream names most bug-tests by issue).
3. Re-verify on the current main:
```bash
make -C mdl/grammar bootstrap && export ANTLR4_TOOLS_ANTLR_VERSION=4.13.2
make build && make test && make lint
```
4. Push to your fork and open the PR against `mendixlabs/mxcli:main`. Compare URL for the
ready-made branch:
https://github.com/mendixlabs/mxcli/compare/main...MendixMau:mxcli:fix/alter-page-set-rendermode
5. PR body — paste this, filling in `NNN`:

```markdown
Closes #NNN

## What does it do?

`ALTER PAGE` / `ALTER SNIPPET` can now set `RenderMode` on a dynamic text:

alter page Mod.Page { set RenderMode = H2 on txtTitle };

`CREATE PAGE` already wrote this property and the MCP backend's mutator already set it; the MPR
backend's fixed property list in `setRawWidgetPropertyMut` did not include it, so it fell through
to the pluggable-widget setter and failed. The new case:

- applies only to a dynamic text (`Forms$DynamicText`); any other widget gets an error naming it
- accepts `Text`, `Paragraph`, `H1`–`H6` in any casing and stores the canonical spelling
- rejects anything else with `invalid RenderMode "H7" for dynamic text "txtTitle": expected one of Text, Paragraph, H1, H2, H3, H4, H5, H6`

## Testing

- New unit tests in `mdl/backend/pagemutator/dynamictext_rendermode_test.go`, written first:
they fail on main and pass with the fix (verified by reverting the fix).
- Bug-test `mdl-examples/bug-tests/…-alter-page-set-rendermode-dynamictext.mdl` (page + snippet).
- `make build`, `make test`, `make lint` pass.

## Mendix validation

Mendix 11.14.0: on a copy of a real project, `set RenderMode = H2` on an existing dynamic text →
`Altered page`; `describe page` shows `RenderMode: H2`; `mx check` 0 errors; one `.mxunit` changed.

## Docs

Skill `alter-page`, docs-site `alter-page.md`, `MDL_QUICK_REFERENCE.md`, `mxcli syntax page.alter`,
and a finding in `.claude/skills/fix-issue/findings/mdl-backend.jsonl`.

## Agentic Code Testing

- [x] Tested with Claude Code in dev container
- [x] Claude can generate correct MDL for this feature
- [x] Skills updated (if applicable)
- [x] error messages are helpful for debugging

## Out of scope

`set Content` / `ContentParams` on a dynamic text (grammar-level), `RenderMode` on a container,
`RenderType` on a button.
```

6. After merge and release: mark BUG-155 RESOLVED in `mxcli-bugs.md`, and in the consuming
project drop the `replace` workaround in favour of `set RenderMode`.
Loading
Loading