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
1 change: 1 addition & 0 deletions .claude/commands/mxcli-dev/review.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ proactively. Add a row after every review that surfaces something new.
| 21 | A registry that reads only one source (an `embed.FS`) has no extension point, so the only way to customise its output is to hand-edit the generated block — which a digest fence then refuses to touch on the next run. The feature ends up hostile to the exact case it was built for | API design | When a guard refuses a user's edit, ask whether the thing being edited should have been authorable. Introduce a source abstraction (`fs.FS` + root) and let a project-local directory shadow the embedded set — the walk functions usually already take a root, so the change is contained |
| 22 | A test helper that expands a template cross-products every list against every reference (e.g. every `@each $weight` list against every `url()`), inventing artefacts no asset ever shipped and failing on correct input | Test coverage | Scan positionally: a reference must be expanded against the loop it actually sits under. Prove the helper both fires on a real break (delete one shipped file) and stays quiet on correct input — a helper only checked against green code has not been shown to detect anything |
| 23 | A copy-to-scaffold path renames files but not the identifiers built from the name (`@mixin mxcli-<name>-<alt>`, `@import "mxcli-<name>"`), so two artefacts collide the moment both exist — and the symptom is a rule that silently compiles to nothing | Code correctness | Assert the structural contract on the *generated* artefact, not just the shipped ones: factor the built-in's contract test into a helper and run the scaffold through it. Verify once end to end against the real toolchain and record it |
| 24 | A new splice refusal routed through the shared `cannotSplice` path changes **mdl 0** too: there a refusal falls back to the whole-flow rebuild, so a headerless script that used to splice now loses its merges, curves and `$ID`s — a silent change of meaning ADR-0011 rules out (#1508 case 6, caught by `TestFlowModify_InsertIsSpliced`) | Language versioning | Gate the new refusal on `flowRebuildRefused.Applies(ctx.LanguageVersion)` so mdl 0 keeps its old splice; add an mdl 0 case asserting "spliced, no MDL-V1-REBUILD" beside the mdl 1 refusal test |
| 24 | A stacked PR's base merged at an older head: commits pushed to the base branch after its merge never reach `main`, while both PRs read as fine (ako/mxcli#1108 lost its last two commits this way) | Merge hygiene | Before merging any stacked PR, and after pushing to a branch whose PR may already be merged, check each commit with `git merge-base --is-ancestor <sha> origin/main`; retarget the next PR to `main` so it carries the stranded commits, and say so in its description |
| 24 | A writer leaves out a property Studio Pro always writes (an empty `Documentation`, the empty half of an attribute/association pair) or writes one Mendix does not declare. `mx check`, the build and the runtime stay green, but Mendix's merge/diff engine compares property NAMES and throws, so the document becomes unmergeable once Studio Pro saves it (mendixlabs/mxcli#1373) | BSON writing | The doctype gate's `checkPropertySets` catches it per script: `mx convert -p` re-serializes what is not canonical. Fix the writer, or record Mendix's value with `MXCLI_PROPERTY_SETS_RECORD` + `internal/propertysets/cmd/propdefaults`; never guess a default |
| 24 | A skill or command still instructs work into a package the repo has deleted (`sdk/mpr` test locations, a symptom table moved to `findings/*.jsonl` years prior) — the doc reads as authoritative and every instruction in it is a compile error or a no-op | Docs quality | When a package is deleted or a doc is restructured, grep `.claude/` for its name in the same PR. A deletion that leaves the guidance behind is worse than no guidance, because the reader trusts it |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/backend", "date": "2026-10-21", "symptom": "create or modify of a nanoflow drawn with @position on every statement, adding an if/else after the declare without @position and editing the two calls in the next if's branch: `spliced: 1 inserted, 1 replaced, 1 dropped`, with `if $Flag` -299 -> 40, its merge 186 -> 525 and the return 530 -> 869, while the replaced calls stayed at (-124, 100)/(60, 100), left of their own decision. Re-running the same script was refused (`the connectors of the If at (40, 200) are redrawn`). With a plain if (no else) the new if took over the stored decision and the restated `if $Flag` was drawn on top of it at (-299, 200), both merges at (186, 200).", "cause": "Three rules that each held alone. makeRoom shifts every top-level node past the cut by the deficit (339); a Replace whose first statement states @position is Placed and written at its stated coordinates, never shifted; movedNode records a move only when the stated position differs from the stored one, so the shifted nodes were never moved back, and the merge is not a move candidate at all. On re-run @merge is geometry but not a position field, so the pair fell to `redrawn`. No-else: lcsStatements weighed ifConditionChanged the same as sameIfShell and the tie went to the earlier pair, so the new if became a condition edit of the stored decision and the restated if a Placed insert, which never runs checkRoom.", "file": "mdl/backend/mfmutator/splice.go (Pin, pinnedPast, makeRoom, Replace), mdl/backend/microflow_mutation.go (MicroflowMutator.Pin), mdl/executor/cmd_flow_modify_patch.go (statedPoints, patch), mdl/executor/cmd_flow_modify.go (lcsStatements weights, mdl 1 gate in planFlowModify)", "insight": "Shift-consistently cannot satisfy idempotence: once the script states -299 and the store says 40, the re-run must move it back or refuse — measured even for a plain one-activity insert into a described flow (+104, then the re-run refused on @merge). So a node at a stated point is pinned and a shift that would move one is refused, naming the room and the node; the mutator pins by point rather than $ID because the node at a stated point may be one a later Placed fragment puts there. Collect stated points from @position, @merge, @start, note and parameter positions, skipping loop bodies (relative coordinates). The control is the same script with the positions after the insert left out: spliced, everything moved by one delta, re-run Unchanged. Gate the pins on mdl 1 (flowRebuildRefused.Applies): under mdl 0 the refusal falls back to the whole-flow rebuild, which deletes the Studio Pro merges and $IDs the shift kept — TestFlowModify_InsertIsSpliced (ADR-0012's describe+insert acceptance, headerless) caught exactly that, and ADR-0011 forbids the change of meaning. Not touched: the shift still moves parameters (y 53) along with the row.", "refs": ["ako/mxcli#1508", "mendixlabs/mxcli#1598"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"area": "mdl/backend", "date": "2026-10-21", "symptom": "A splice that makes room for an inserted fragment (create or modify / alter … insert) moved the flow's parameters along with the row: one if/else inserted into NF_Do moved $Flag from its derived (200, 53) to (539, 53), a longer replace then to (699, 53), and from then on describe printed a parameter @position no script stated. Inserting before the first statement of a two-parameter microflow moved $Count from (300, 53) to (410, 53).", "cause": "mfmutator shift (splice.go) moves every top-level node past the cut, and a Microflows$MicroflowParameter is a top-level object of the unit's object collection (the decoded model keeps parameters apart, so nothing in the executor sees them as nodes). mxcli's derived parameter row is absolute (200 + idx*pitch, 53), right of the start event, so almost every insert's cut falls left of it.", "file": "mdl/backend/mfmutator/splice.go (shifted)", "insight": "Measure the authored convention before deciding what 'the row' is: 63 of 71 Studio Pro-authored parameters (PedApp 19/20, JTSBootLogboek 44/51) sit exactly at the start event's x, all above every other node, none within 60px of the flow line — a parameter belongs to the flow, not to the stretch of line past an insert, and Studio Pro moves nothing on insert. The decoded model hides this: microflows.Microflow carries Parameters apart from ObjectCollection, so a probe that type-switches the collection for *MicroflowParameter does not compile, while the BSON the mutator walks has them inline. Excluding them in shifted also keeps them out of pinnedPast, so a stated parameter position can no longer block a splice it is not in the way of.", "refs": ["ako/mxcli#1547", "ako/mxcli#1508"]}
2 changes: 1 addition & 1 deletion .claude/skills/mendix/write-microflows/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Choose the mode by who owns the microflow ([choose-edit-mode](../choose-edit-mod
(targets from `describe microflow X with handles`). `create or modify` of `describe`
output patches too: unchanged writes nothing; a statement (even one whose handler returns), guard
clause, `return` value, `if` condition, header clause, parameter (added/retyped; removed only if unused) or stated `@position`/`@start` change is
patched in place (a move keeps the node's flows), as is a loop-body activity with new values. A redrawn
patched in place (a move keeps the node's flows), as is a loop-body activity with new values ([an insert needing room past a stated `@position`](reference/pitfalls.md#13-inserting-where-the-script-states-every-position)). A redrawn
`@anchor`/`@curve`, other loop-body change, error handler or other `return` added/taken away rebuilds under
mdl 0 (`MDL-V1-REBUILD`: IDs renumbered, merges and curves lost) and is refused under `mdl 1;`. **To add,
remove or reorder in a loop body, `alter … replace` the whole loop** ([pitfalls](reference/pitfalls.md#11-changing-something-inside-a-loop-body)). **To rebuild deliberately, `drop` and `create` in ONE script** — the grants carry only within it ([pitfalls](reference/pitfalls.md#drop--create-is-still-a-new-document)).
Expand Down
16 changes: 16 additions & 0 deletions .claude/skills/mendix/write-microflows/reference/pitfalls.md
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,22 @@ alter microflow MyModule.GetCode {
};
```

### 13. Inserting Where the Script States Every Position

`describe` output states `@position` on every statement. Under `mdl 1;`, `create or
modify` never moves a node the script draws at a stated position: a statement inserted
**without** `@position` into a gap too small for it is refused, naming the room it needs
and the node in the way (mendixlabs/mxcli#1598). Moving the rest of the row along would
leave the stored flow saying something other than the script, so the next run would move
it back. Either:

- give the new statements an `@position` where there is room for them, or
- leave the positions of what comes after them unstated (`@position`, `@merge`, a note's
`position:`), so the splice can move it along — the second run of that script is then
Unchanged.

A headerless (mdl 0) script still moves the row along, as it always did.

## Implicit Variable Creation (CE0111 Duplicate Variable)

These statements **implicitly create a new variable** with the name on the left side:
Expand Down
35 changes: 19 additions & 16 deletions .claude/skills/mendix/write-nanoflows/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,21 @@ description: "Nanoflow syntax in MDL — shared with microflows, but client-side
| lines | section |
|---|---|
| 35-44 | When to Use This Skill |
| 45-78 | Changing an Existing Nanoflow |
| 79-97 | When to Use a Nanoflow vs a Microflow |
| 98-112 | Key Differences from Microflows |
| 113-136 | Nanoflow Structure |
| 137-145 | Naming Convention |
| 146-214 | Supported Activities |
| 215-481 | Real-World Patterns |
| 482-552 | NanoflowCommons JavaScript Actions Reference |
| 553-591 | Disallowed Activities |
| 592-595 | Return Type Restrictions |
| 596-640 | Error Handling in Nanoflows |
| 641-648 | Security (GRANT/REVOKE) |
| 649-661 | Management Commands |
| 662-672 | Common Mistakes |
| 673-687 | Validation Checklist |
| 45-81 | Changing an Existing Nanoflow |
| 82-100 | When to Use a Nanoflow vs a Microflow |
| 101-115 | Key Differences from Microflows |
| 116-139 | Nanoflow Structure |
| 140-148 | Naming Convention |
| 149-217 | Supported Activities |
| 218-484 | Real-World Patterns |
| 485-555 | NanoflowCommons JavaScript Actions Reference |
| 556-594 | Disallowed Activities |
| 595-598 | Return Type Restrictions |
| 599-643 | Error Handling in Nanoflows |
| 644-651 | Security (GRANT/REVOKE) |
| 652-664 | Management Commands |
| 665-675 | Common Mistakes |
| 676-690 | Validation Checklist |

<!-- /skill-index -->
# Mendix Nanoflow Skill
Expand Down Expand Up @@ -58,7 +58,10 @@ Choose the mode by who owns the nanoflow ([choose-edit-mode](../choose-edit-mode
the stored end event or decision, and a body without a trailing `return` means the stored end. The
header (documentation, return type, parameters added, retyped or — when unused —
removed) is set on the stored document, and a stated `@position` or `@start` moves the
stored node, keeping its flows. A change it cannot patch — anything inside a loop body
stored node, keeping its flows. Under `mdl 1;` a node at a stated position is never moved
to make room: a statement inserted without `@position` where the gap is too small is refused, naming
the node in the way — give the new statements an `@position`, or leave `@position` and
`@merge` off the statements after them. A change it cannot patch — anything inside a loop body
or error handler, a redrawn `@anchor`/`@curve`, any other `return` added or taken away —
rebuilds the whole nanoflow under mdl 0
(warning `MDL-V1-REBUILD`: element IDs renumbered, merges removed, curves reset) and
Expand Down
2 changes: 1 addition & 1 deletion docs-site/src/language/versions.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ A construct with no mechanical rewrite is reported with the reason, and `fmt` re

`-p app.mpr` also settles a bare `commit $X;` in a `create or modify microflow|nanoflow`, with or without the header. Since mendixlabs/mxcli#895 a bare commit means *with* events, Studio Pro's default; an older mxcli stored the same statement *without* events. Where the stored flow commits the variable without events, `fmt --upgrade -p app.mpr` writes `commit $X without events;`, so re-running the script keeps what is stored instead of turning the event handlers on (or, inside a loop under `mdl 1`, being refused). A new flow, a new commit, or a stored commit with events is left as written; where the stored flow commits the variable both ways, the statement is left and reported. Without `-p`, `fmt` prints a note (`MDL067`) for each flow with a bare commit.

`-p app.mpr` also tells the upgrade which statements `exec` would refuse once the header is there. Under `mdl 1` a `create or modify microflow|nanoflow` of a stored flow is applied as a patch, and a change the patch cannot make (inside a loop body anything but an activity replaced by one of the same kind, an error handler, a redrawn connector, a `return` added) is refused with nothing written; without the header the same statement rebuilds the whole flow (`MDL-V1-REBUILD`). No rewrite keeps that meaning, so `fmt` names each such statement and leaves the header off that file, applying the rest of the upgrade. Change those flows with `alter`, or drop and create them, and upgrade again; `--force-header` adds the header regardless. `mxcli check script.mdl -p app.mpr` reports the same statements (an `MDL-V1-REBUILD` error under `mdl 1`, the warning without the header), computed by the same code as `exec` and `diff`.
`-p app.mpr` also tells the upgrade which statements `exec` would refuse once the header is there. Under `mdl 1` a `create or modify microflow|nanoflow` of a stored flow is applied as a patch, and a change the patch cannot make (inside a loop body anything but an activity replaced by one of the same kind, an error handler, a redrawn connector, a `return` added, a statement inserted without `@position` that needs room past a node the script places) is refused with nothing written; without the header the same statement rebuilds the whole flow (`MDL-V1-REBUILD`). No rewrite keeps that meaning, so `fmt` names each such statement and leaves the header off that file, applying the rest of the upgrade. Change those flows with `alter`, or drop and create them, and upgrade again; `--force-header` adds the header regardless. `mxcli check script.mdl -p app.mpr` reports the same statements (an `MDL-V1-REBUILD` error under `mdl 1`, the warning without the header), computed by the same code as `exec` and `diff`.

## Behaviour changes to know about

Expand Down
9 changes: 9 additions & 0 deletions docs-wiki/bug-patterns/flow-graph-geometry.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ translated into the gap; anything that is not on the line (a note) must not
size the gap, and anything the script placed absolutely must not be
translated.

**Making room is a move the script did not ask for.** Shifting the row after
a gap is consistent only while nothing past the gap has a stated position:
once the script says -299 and the store says 40, the next run must move it back
or refuse, so "shift everything consistently" can never re-run Unchanged. A
node at a stated point is pinned instead, and a shift that would move one is
refused with the room it needs — under mdl 1 only, because under mdl 0 a
refusal becomes a whole-flow rebuild, which loses more than the shift did.
Check a splice fix by re-running the same script, not by describing once.

## See also

- [fix-issue findings](../../.claude/skills/fix-issue/findings/) — the formulas,
Expand Down
Loading
Loading