Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
2e79a28
docs(proposal): mxcli microflow debugger — breakpoints by name
claude Jul 26, 2026
0ef2446
Merge pull request #44 from ako/docs/microflow-debugger-proposal
ako Jul 26, 2026
741eacd
debug: slice 1 — DebuggerClient + mxcli debug status/enable/disable
claude Jul 26, 2026
ed24b03
debug: slice 2 — breakpoints by name (activities/break/unbreak/breaks)
claude Jul 26, 2026
3ed996e
debug: slice 3 — paused / inspect / step / continue
claude Jul 26, 2026
2694653
debug: slice 4 — run --local --debug (enable at boot) + shutdown safety
claude Jul 26, 2026
a6bbf3b
debug: slice 5 — docs + skill for the microflow debugger
claude Jul 26, 2026
cc7a05a
debug: slice 6 — nanoflow support + nanoflow logging docs
claude Jul 26, 2026
aad59e6
debug/docs: fold in updated nanoflow findings (single-use debug_id, L…
claude Jul 26, 2026
0f6a48b
debug: add get_list — inspect a list variable (mxcli debug inspect --…
claude Jul 26, 2026
2854532
Merge pull request #45 from ako/feature/debugger-slice1
ako Jul 27, 2026
b797828
run --local: --metrics + --runtime-setting (OpenTelemetry/Prometheus …
claude Jul 27, 2026
3351fb4
run --local: --trace — attach the OpenTelemetry agent with default sp…
claude Jul 27, 2026
fc70449
Merge pull request #46 from ako/feature/run-local-metrics
ako Jul 27, 2026
32090a1
Fix nightly on Mendix 10.24: drop 11.x-only design property from 15b
claude Jul 27, 2026
3e9a102
Merge pull request #47 from ako/fix/nightly-10.24-15b-design-property
ako Jul 27, 2026
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/skills/fix-issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ to the symptom table below, so the next similar issue costs fewer reads.
| Design properties are written free-form: a `ColorPicker`/`ToggleButtonGroup` value serializes as a plain option (wrong `$Type` for Studio Pro's Appearance tab), and a typo'd key/value (they're case-sensitive) passes `mxcli check`. Also `show design properties <widget>` reports "No design properties found for widget type container" for a valid widget | Root bug: `resolveDesignPropsKey` upper-cased the MDL keyword but the lookup map is **lowercase-keyed**, so `container`→`DivContainer` never resolved — leaving `resolveDesignPropertyValueType` dead code and the theme registry unused on the write/validate paths | `mdl/executor/theme_reader.go` (`resolveDesignPropsKey` case fix) + `mdl/executor/cmd_pages_builder_v3.go` (`astDesignPropToValue` takes theme props) + `mdl/executor/validate_design_properties.go` (new, MDL-WIDGET11/12) wired from `cmd/mxcli/cmd_check.go` + `cmd/mxcli/lsp_diagnostics.go` (cached `themeRegistry`) | Fix `resolveDesignPropsKey` to lower-case the lookup. On write, resolve each flat value's type from the registry **by matching the value against the property's declared options** (see the CE6084 correction below — the control type alone does NOT decide it). On check (`-p` only, when themesource defines properties), walk page/snippet/alter-page widget trees and warn: **MDL-WIDGET11** unknown key (case-sensitivity hint / valid-key list), **MDL-WIDGET12** invalid value (lists allowed values). Warnings, not errors — a newer theme may add keys/values (forward-compat, per `page-styling-support.md:402`). Skip compound (registry doesn't model sub-props) and widgets with no type-specific metadata (pluggable). Bug-test `mdl-examples/bug-tests/typed-design-properties.mdl` |
| Follow-up regression from the row above: after typed design properties merged, `mx check` fails **CE6084** "Expected design property _Flex container_ / _Column gap_ / _Align items Y_ … to be of type **Toggle button group**, but found **Custom**" on any page using a flat `ToggleButtonGroup` value (Atlas flex/spacing/typography, e.g. `'Column gap': 'Medium'`). Broke `TestMxCheck_DoctypeScripts` on `12-styling`, `15c-fragment-bindings`, `31-pluggable-datagrid-gallery-v010` (both engines) — green on unit tests, red only in `make test-integration` | `resolveDesignPropertyValueType` mapped `ToggleButtonGroup`→`custom` by control type. But a ToggleButtonGroup selection picks one of a **fixed option set**, so Studio Pro stores it as an **Option** — a `Custom` value type mismatches the declaration. Only a ColorPicker's **off-list** value (a free-form hex) is genuinely Custom. The value type is decided by the **value**, not the control | `mdl/executor/cmd_pages_builder_v3.go` (`resolveDesignPropertyValueType`, now takes the value and reuses `themeOptionAllowed`) | Make it value-aware: value ∈ declared options → `option` (Dropdown, ToggleButtonGroup, predefined ColorPicker swatch alike); off-list **and** `ColorPicker` → `custom`; else `option`; no metadata → `option`. Verified: the three doctype examples pass `mx check` = 0 errors on both engines. Test `TestAstDesignPropToValue_Typed` extended with the `Column gap: Medium` + ColorPicker swatch/hex cases. **Diagnosis pattern**: a value-type/BSON-`$Type` mapping keyed on a *declared control type* is a trap — verify it against `mx check`, never assert it from the type name alone (this is exactly how the original bug slipped in). **Process lesson**: this shipped red because `make test-integration` (mx-check doctype roundtrips) was not run before merge — run it, not just unit tests, for any page/widget-serialization change |
| The **nightly** matrix (Mendix 10.24 / 11.6 / 11.12) fails only on **10.24**: `TestMxCheck_DoctypeScripts/15c-fragment-bindings-examples` → `Execution error: failed to build page: building block not found: Atlas_Web_Content.List_Cards` (both engines). 11.6/11.12 pass; unit tests + push-test (single-version) pass. Looks like a "design property on 10.24" issue but isn't | `15c` demonstrates `use building block Atlas_Web_Content.List_Cards`, an Atlas UI building block that ships in **11.x but is absent from the 10.x Atlas** (the example comment wrongly said "present in every standard Mendix app"). The example had **no `-- @version:` gate**, so on a 10.24 project the whole file ran and mxbuild couldn't resolve the block. (12-styling gates its design-property section at line 186; 31 gates the whole file at line 1 — both already skip on 10.24) | `mdl-examples/doctype-tests/15c-fragment-bindings-examples.mdl` | Add `-- @version: 11.0+` immediately before the `create page … P002_Rebound_Block` block (its last statement) so `filterByVersion` skips only the building-block demo on 10.x; the fragment-binding statements above stay ungated and keep 10.24 coverage. Verified: 15c passes on **10.24** (10 lines skipped, 0 errors) and still runs+passes the section on **11.6.3** (0 errors). **Diagnosis pattern**: a nightly-only, version-specific doctype failure = an example using a construct (building block, widget, syntax) that doesn't exist in the oldest matrix version and lacks a `-- @version:` gate; reproduce locally with `MX_BINARY=~/.mxcli/mxbuild/<ver>/modeler/mx go test -tags integration -run TestMxCheck_DoctypeScripts/<file>` |
| Same nightly pattern, **CE6083** this time: `TestMxCheck_DoctypeScripts/15b-fragment-slots-examples` fails only on **10.24** (both engines) — `[CE6083] "Design property Card style is not supported by your theme"` at every `cardWrap` container. `Card style` is an **Atlas v3 design property (11.x); the 10.x Atlas theme doesn't define it** | The shared `define fragment Card` used `designproperties: ['Card style': on]` and is instantiated by every page in the file, so a `-- @version:` gate would have to gate the whole file (killing 10.24 coverage of the slot feature the example is actually about). Unlike 15c's building block, the design property was **incidental** to the example | `mdl-examples/doctype-tests/15b-fragment-slots-examples.mdl` | **Drop the incidental v3 design property**, keep `class: 'card'` (Atlas card styling works on every version) — the example demonstrates content *slots*, not design properties (those live in 12-styling, gated 11.0+). Verified: 15b passes on 10.24 **and** 11.6.3 (0 errors, both engines). **Gate vs remove rule**: if the version-specific construct IS the point of a self-contained section → `-- @version:` gate it (15c); if it's incidental and in a shared/expanded definition → remove it and use a cross-version equivalent (15b). **Proactive sweep** after any such fix: `grep -lE 'designproperties|use building block' mdl-examples/doctype-tests/*.mdl` and confirm each usage is either gated or version-safe (note the doctype test skips `*.test.mdl`/`*.tests.mdl`) |
| `mxcli run --local`: when a page action throws, the browser shows the generic Mendix error dialog and there is nothing to correlate it against — the runtime's own stdout/stderr (server stack trace, microflow `LOG` output) is swallowed, so a server-side bug can't be told apart from a client one | The runtime JVM was spawned with `cmd.Stdout=log; cmd.Stderr=log` where `log` is an in-memory `syncBuffer` surfaced only on a *startup* failure; during normal operation it goes nowhere on disk | `cmd/mxcli/docker/localboot.go` (`spawnAndConfigure`, `openRuntimeLog`, `LocalRuntime.logFile`, `LocalRuntimeOptions.RuntimeLogPath`) + `cmd/mxcli/docker/runlocal.go` (default `<projectDir>/.mxcli/runtime.log`) + `cmd/mxcli/cmd_run.go` (`--runtime-log`) | Tee the JVM's stdout+stderr to `<projectDir>/.mxcli/runtime.log` via `io.MultiWriter(log, file)` (the in-memory buffer still backs startup-error reporting). Append across restarts with a `=== runtime start … ===` marker; close the handle on Stop/reopen. Default on; `--runtime-log <path>` relocates, `-` disables. Print the path at boot. Test `TestOpenRuntimeLog`. Findings #25 |
| Follow-up to the above (#25 re-test): `run --local` writes `runtime.log` but it stays **nearly empty** — the JVM tee captures startup/JVM output only; **application** logs (microflow `LOG`, server-side exception stack traces) never reach stdout, so a page-action error still can't be diagnosed | A standalone runtime (launched via `runtimelauncher.jar`) attaches **no log subscriber** by default — unlike a Studio Pro / m2ee run, which calls `create_log_subscriber` **after** start. Mendix application logs flow to log *subscribers*, not stdout, so with none attached they go nowhere | `cmd/mxcli/docker/runtime_controller.go` (`RuntimeController.LogSubscriberFile`/`Stdout`, `attachFileLogSubscriber`, called at the end of `Start`) + `cmd/mxcli/docker/localboot.go` (`StartLocalRuntime` sets `ctrl.LogSubscriberFile` to the abs runtime-log path) | After a successful `start` (and on every restart's `Start`, since each fresh JVM has no subscriber), call the `create_log_subscriber` admin action with `{type:"file", name:"mxcli-run-local", autosubscribe:"INFO", filename:<abs runtime.log>, max_size:1GiB, max_rotate:0}`. **`max_rotate:0` is load-bearing**: the JVM stdout tee holds an fd on the same file, and a rotate-rename would detach it. Best-effort (a logging failure must not fail an up runtime — warn to Stdout instead). Pass an **absolute** path (the runtime's cwd is `<install>/runtime`, not mxcli's). Tests `TestStart_AttachesLogSubscriber`, `TestStart_NoLogSubscriberWhenUnset`, `TestStart_LogSubscriberFailureNonFatal`. Findings #25 (round 2) |
| Follow-up to round 2 (#25 re-test): the subscriber is registered but `runtime.log` **still** holds only the 4 JVM-banner lines — a probe microflow's `log info/warning/error` and a forced runtime exception produce **zero** lines. Isolation test: boot+subscriber → 0 probe lines; then call `start_logging` → 6 lines | A standalone runtime boots with logging **not started**, so a registered subscriber sits **inert** — nothing is delivered until the logging subsystem is activated. `create_log_subscriber` alone is necessary but not sufficient | `cmd/mxcli/docker/runtime_controller.go` (`configureRuntimeLogging` — renamed from `attachFileLogSubscriber`; now also calls `start_logging`) | After `create_log_subscriber`, call `CallM2EE(c.opts, "start_logging", nil)` in the same step (order: create subscriber → start_logging). Treat an "already started" response as success (`start` re-runs on the DB-update retry / restart paths on a still-running JVM). Still best-effort (warn to Stdout, never fail an up runtime). Test `TestStart_StartLoggingAlreadyStartedIsSuccess`; `TestStart_AttachesLogSubscriber` now asserts the `[start, create_log_subscriber, start_logging]` sequence. **Diagnosis pattern**: when a registered sink receives nothing, check whether the subsystem that feeds it is even *running* — registration ≠ activation. Findings #25 (round 3) |
Expand Down
133 changes: 133 additions & 0 deletions .claude/skills/mendix/debug-microflows.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Debug Microflows — `mxcli debug`

## Overview

`mxcli debug` drives the Mendix runtime's **microflow debugger** from the command
line: set breakpoints **by name**, inspect a paused microflow's variables, and
step/continue — against an app started by `mxcli run --local`. It is the headless
counterpart to Studio Pro's debugger, so you can debug a server-side microflow
without leaving the warm loop.

mxcli is uniquely able to offer breakpoints **by name** because it owns both
halves: the admin password + app URL (from `run --local`) and the activity model
GUIDs (from the `.mpr`). You never deal with raw GUIDs.

## When to use this skill

- A page action throws or misbehaves and you need to see *where* in a microflow it
goes wrong, with the in-scope variables.
- You want to confirm a microflow takes the branch/value you expect.

For a server **stack trace / `LOG` output** (not stepping), you usually just want
the runtime log — see `run-local.md` (`--runtime-log`). Use the debugger when you
need to **pause and inspect** live execution.

## Prerequisites

- The app running under `mxcli run --local` (Mendix 11.x).
- Start it with **`--debug`** so the debugger is enabled and a session is ready:

```bash
mxcli run --local -p app.mpr --debug
```

`--debug` alone does **not** change runtime behaviour — nothing pauses until you
set a breakpoint. It caches a debug session token under `<projectDir>/.mxcli/`
so the `mxcli debug` commands below (run from another terminal, **same `-p`**)
work immediately, with no separate `mxcli debug enable`.

## The loop

```bash
# terminal 1: app + debugger
mxcli run --local -p app.mpr --debug

# terminal 2: find the activity, break on it by name
mxcli debug activities Sudoku.ACT_Hint -p app.mpr
mxcli debug break Sudoku.ACT_Hint --activity 'Retrieve' -p app.mpr

# now trigger the microflow in the browser — the request pauses. Then:
mxcli debug paused -p app.mpr # which flow is paused + its variables
mxcli debug inspect Game -p app.mpr # one variable in detail
mxcli debug step over -p app.mpr # over | into | out
mxcli debug continue -p app.mpr # resume (the browser request completes)

# when done — ALWAYS:
mxcli debug disable -p app.mpr
```

## Commands

| Command | What it does |
|---------|--------------|
| `mxcli debug status` | Is the debugger on? How many microflows are paused? |
| `mxcli debug enable` / `disable` | Turn the debugger on/off (use `--debug` on `run --local` instead of `enable` for the warm loop) |
| `mxcli debug activities <Module.Flow>` | List a microflow's activities with the object IDs you can break on |
| `mxcli debug break <Module.Flow> --activity <#n\|caption> [--if <expr>]` | Set a breakpoint, resolved by name (`--if` = conditional) |
| `mxcli debug unbreak <Module.Flow> --activity <#n\|caption>` | Clear a breakpoint |
| `mxcli debug breaks` | List the breakpoints mxcli has set this session (name → object ID) |
| `mxcli debug paused` | Show paused microflows + full state (variables) |
| `mxcli debug inspect <var> [--list] [--flow <debug_id>]` | Inspect one variable of a paused flow (`--list` for a list variable → `get_list`) |
| `mxcli debug step [over\|into\|out] [--flow <debug_id>]` | Advance one step (default `over`) |
| `mxcli debug continue [--all]` | Resume the paused flow (or all with `--all`) |

Selecting an activity: `--activity '#2'` (the index from `activities`) or a
caption substring like `--activity 'Retrieve'` (must match exactly one, case-
insensitive). Selecting a paused flow: `--flow <debug_id>` (from `paused`); with a
single paused flow it is auto-selected.

## Nanoflows (client-side)

`mxcli debug` works for **nanoflows** too — `break`/`activities`/`unbreak` auto-detect
whether `Module.Flow` is a microflow or a nanoflow and set the breakpoint the right
way (a nanoflow needs the `nanoflow_name` param; the wrong key NPEs the runtime —
mxcli handles this for you). Break by name exactly as for a microflow:

```bash
mxcli debug break Sudoku.NF_ToggleNotes --activity 'Change' -p app.mpr
```

A paused **nanoflow** does not appear in `get_paused_microflows` — it surfaces only
in the runtime's `poll_events`. `mxcli debug paused` (and `step`/`inspect`/`continue`)
merge both sources, so a paused nanoflow shows up with its `debug_id` like any other;
its variables are in the "Client events (poll_events)" section of `paused`.

Symptom of a paused nanoflow **without** mxcli: a frozen browser, the console logging
"Starting execution" but never "Finished", and `mxcli debug status` showing
`client_connected: true`.

**Nanoflow `debug_id` is single-use.** Unlike a microflow (stable id), a nanoflow
gets a **new** `debug_id` after every step — the old one is invalidated. Because each
`mxcli debug` command re-reads the current state, just let `step`/`inspect`/`continue`
**auto-resolve** the flow (don't pass `--flow`): a bare `mxcli debug step over` picks up
the fresh id each time. Reusing a `--flow <debug_id>` copied from an earlier `paused`
will fail on the second nanoflow step with "could not find … in debug with id".

For **nanoflow log output**, see `write-nanoflows.md` — the runtime rewrites the log
node to `Client_Nanoflow`, so grep `runtime.log` for `Client_Nanoflow`, not your node
name.

## Gotchas

1. **A breakpoint pauses whoever hits it — the browser included.** The triggering
request hangs until `continue` (or `disable`). This is normal; just don't walk
away from a paused session.
2. **Always finish with `mxcli debug disable`.** `run --local --debug` disables it
for you on shutdown, but if you enabled it by hand, turn it off by hand.
3. **Use the same `-p` everywhere.** The session token and breakpoint record live
under `<projectDir>/.mxcli/`; a different `-p` (or none) looks in a different
place and won't see the session `run --local --debug` started.
4. **Conditions are Mendix expressions** (`--if '$Game/Solved = false'`), same
syntax as a Studio Pro conditional breakpoint.
5. **Overriding the target runtime:** `--app-url`, `--admin-port`, `--admin-pass`,
`--debug-pass` (or `MXCLI_APP_URL` / `MXCLI_ADMIN_PASS` / `MXCLI_DEBUG_PASS`)
default to a `run --local` runtime; set them to debug a differently-configured
or remote runtime.

## Validation checklist

- [ ] App started with `mxcli run --local --debug`.
- [ ] `mxcli debug status` shows `enabled`.
- [ ] `mxcli debug activities <Module.Flow>` lists the activity you want.
- [ ] After triggering the flow, `mxcli debug paused` shows it with variables.
- [ ] Finished with `mxcli debug disable`.
Loading
Loading