Skip to main content

reactor-devtools

Driving a running Reactor app via `mur devtools` — screenshot, inspect visual tree, click/type/scroll, read hook state. Use when diagnosing visible bugs (layout, contrast) or verifying a UI change landed.

ソース情報

リポジトリ
microsoft/microsoft-ui-reactor
ソースの最終更新活動
2026年10月1日 21:10
検出された SKILL.md の言語
英語
スター
663
フォーク
57

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
reactor-devtools
description
Driving a running Reactor app via `mur devtools` — screenshot, inspect visual tree, click/type/scroll, read hook state. Use when diagnosing visible bugs (layout, contrast) or verifying a UI change landed.
# Reactor Devtools — CLI-driven UI automation The Reactor devtools let you look at a running debug-build app the way a user does (screenshots, rendered text, layout bounds) and drive it the way a user does (click, type, toggle, scroll). **Always prefer the `mur devtools` CLI** — every action composes with shell pipes and `jq`, each invocation is a complete audit record, and no MCP client setup is needed. Under the hood the CLI talks JSON-RPC to a loopback HTTP endpoint the app exposes. The MCP endpoint is still available at `http://127.0.0.1:PORT/mcp` as a parity escape hatch, but reach for it only when the CLI can't express what you need (structured args the CLI flattens, or another MCP client that's already wired up). Loopback-only — **DEBUG builds only**, never ship it. Auth is a per-launch bearer token written into the lockfile; the CLI applies it transparently during lockfile discovery, so you don't see it. **Don't pass `--endpoint` for tool calls** — without the token the server returns 401. `--endpoint` is only useful when you're crafting the `Authorization: Bearer …` header yourself (curl, custom client). ## Getting `mur` on your PATH `mur` resolves from PATH in both modes: - **Skill kit (deployed):** `install-skill-kit.ps1` (shipped with the kit zip) prepends `<install>/bin/<arch>` to your user PATH. - **Cloned repo (selfhost):** `dotnet build` of `src/Reactor.Cli` automatically mirrors the output to `<repo>/bin/<arch>/` (architecture is determined from `RuntimeIdentifier`). Add that directory to your PATH once. If `mur --version` doesn't resolve, neither path was set up; commands below assume it does. ## Attaching to a running app The app author enables devtools capability in the app project, usually only for Debug builds: ```xml <ItemGroup Condition="'$(Configuration)' == 'Debug'"> <RuntimeHostConfigurationOption Include="Reactor.DevtoolsSupport" Value="true" Trim="true" /> </ItemGroup> ``` `ReactorApp.Run` takes no `devtools:` or `preview:` argument. **Prefer attaching to an app that's already running.** Any devtools-enabled app writes a lockfile to `%TEMP%/reactor-devtools/` on first render, and `mur devtools <verb>` discovers it automatically — **no port, no config, no parsing stdout.** ```bash mur devtools session list --pretty # confirm a session is up ``` Exit 0 with a row means you're good to go; every other verb will find it. Exit 4 means no live session — you need to launch one yourself (next section). ## Launching an app yourself Two launch modes, from simplest to most featured: ### Plain `dotnet run` (the default) ```bash dotnet run --project path/to/App.csproj -- --devtools run \ > /tmp/app-stdout.log 2>&1 & ``` This is what you want in almost every AI session: spawn the app, drive it with `mur devtools <verb>`, call `mur devtools shutdown` when you're done. No supervisor machinery, no pinned port — the CLI finds the session via the lockfile regardless. ### `mur devtools <project>` — the supervisor Reach for this **only** when you need a stable MCP endpoint across `reload` cycles: ```bash mur devtools path/to/App.csproj --mcp-port 54931 ``` The supervisor pins the port (reload-proof) and catches the child's exit code 42 to rebuild and relaunch. Useful if you've wired an external MCP client to `http://127.0.0.1:54931/mcp` and want it to survive code edits. Overkill for one-shot automation. If another process already holds the port when the app starts, the app exits with code 43. A port you pinned with `--mcp-port` is never moved: the supervisor reports the conflict and exits 43, so free the port or pick another. Without `--mcp-port`, the supervisor picks a new free port, prints `using MCP port N across reloads instead`, and relaunches. Take the endpoint from that line or the lockfile, not from the first one. ## Discovering the tool surface ```bash mur devtools call tools/list --pretty # names + input schemas mur devtools call tools/list | jq '.tools[].name' ``` `mur devtools --help` lists every named verb with one-line descriptions. ## CLI verb catalog Each verb attaches to the running session via lockfile discovery; no flags needed when only one session is active. Pass `--pretty` to any verb for indented JSON. | Verb | What it does | |---|---| | `version` | Build tag + pid + port — confirm the app is the one you expect. | | `components` | Class names of every `Component` subclass; marks which is mounted. | | `switch <name>` | Swap the root component by class name. **Invalidates all node ids.** | | `reload [--component N]` | Rebuild + relaunch via the supervisor sentinel. Old ids dead. | | `shutdown` | Close the app cleanly (supervisor exits 0). Releases the build output file lock. | | `windows` | Active window ids, titles, bounds, currently-mounted component. | | `windows.list` | Spec 036 §10. Per-window id, key, title, DIP size, DPI, state, isMain. Use this when you care about DPI / DIP size or the `key` column for `UseOpenWindow`-keyed lookups. | | `windows.activate <id>` | Activate (focus) a window. Returns `{ ok, id }`. | | `windows.close <id>` | Close a window. Honors `UseClosingGuard` / `Closing` subscribers — returns `{ ok: false, cancelled: true, id }` when a guard vetoed the close. | | `windows.open <Component> [--title T] [--width W] [--height H] [--key K]` | Open a new top-level window mounting an allowlisted Component. The component name is gated by the same allowlist as `switchComponent`; rejected names return `unknown-component` with the available list. | | `tree [--selector S] [--window W] [--view summary\|full]` | Dump the visual tree as JSON. `full` adds layout/automation/visual fields. | | `screenshot [--selector S] [--out path]` | PNG of the window (or selector-cropped region). `--out path.png` writes to file; `--out -` streams bytes to stdout. | | `click <selector>` | UIA click. Prefers Invoke → Toggle → SelectionItem; returns `via`. | | `invoke` / `toggle` / `select` | Direct UIA pattern access. `select <container> <item>` auto-expands ComboBoxes. | | `type <selector> <text> [--clear]` | Set text on a value-bearing control. | | `focus <selector>` | Programmatic focus on a Control. | | `scroll <selector> [--by H%,V%] [--to <item-selector>]` | Scroll by **percentage deltas** 0–100 (not pixels), or scroll an item into view. | | `expand <selector>` / `collapse <selector>` | ExpandCollapse pattern (ComboBox popup, TreeViewItem, Expander). | | `wait <selector> [--text X \| --text-matches RE \| --visible \| --count N] [--timeout MS]` | Poll a predicate until satisfied or timeout. | | `state [--selector S]` | Dump every hook value (useState/useReducer/etc.) across mounted components. | | `logs [--tail N] [--since SEQ] [--filter RE] [--source stdout\|stderr\|debug\|trace] [--follow]` | Drain captured `Debug.WriteLine` / `Trace.WriteLine` / `Console.Out` / `Console.Error`. Ring buffer installed at `--devtools run` startup — late-attaching agents still see startup output. `--follow` long-polls until Ctrl+C. | | `fire <Component>.<event> [--args JSON]` | Call a NAMED METHOD on a live component by reflection. **Inline lambdas aren't reachable**. | | `properties <selector> [--name PropName]` | Read dependency properties on an element. Omit `--name` to enumerate all DPs with values, types, and local-vs-default status. Supports attached properties via `Grid.Row` syntax. | | `set-property <selector> <name> <value>` | Set a dependency property. Value is parsed from string (Thickness, CornerRadius, Brush hex `#RRGGBB`, enums, bool, double, int). | | `resources [--selector S] [--scope element\|window\|app] [--filter RE]` | Browse ResourceDictionary entries. Walks element → ancestor elements → window → app (including MergedDictionaries and ThemeDictionaries). | | `set-resource <key> <value> [--scope app\|window\|element] [--selector S]` | Set or add a XAML resource. Reports whether the write replaced an existing entry or created a new shadowing entry. | | `styles <selector>` | Inspect the explicitly-assigned Style: TargetType, Setters (property + value), BasedOn chain. Returns `hasStyle: false` when only a default/theme style is active. | | `ancestors <selector>` | Walk the visual tree upward — returns type, name, and automationId for each ancestor up to the root. | | `call <tool\|method> [--args JSON]` | Generic JSON-RPC passthrough — parity escape hatch. | ### Reference-graph overlay The `references` tool (Spec 057) returns the app's reactive reference graph — `descriptor.Reference`/`.ReferenceList`, the `binding.Reference` bridge, and modifier edges like `.LabeledBy`/`.DescribedBy`/`.FlowsTo`/ `.XYFocus*` — as `{from, to, label, slot, kind, resolved}` edges keyed to the same node ids `tree` uses, plus diagnostics for **cycles** and **unresolved** (perpetually-null) references. Reach it through the generic passthrough: ```bash mur devtools call references --pretty mur devtools call references --args '{"selector":"#login-form"}' # scope to a subtree ``` Use it to confirm an accessibility relationship (`LabeledBy`/`DescribedBy`) or an `XYFocus*` ring actually resolved to the control you expect, or to spot a reference that never resolved. Cycles are a supported topology and are reported informationally, not as errors. The same overlay also backs the **References** toggle in the VS Code live-preview panel. ### Session management ```bash mur devtools session list # JSONL, one live session per line mur devtools session list --pretty # human table mur devtools session clean # GC stale lockfiles mur devtools session clean --dry-run # show what would be removed ``` ### Shared flags (before any verb) - `--endpoint <url>` — skip lockfile discovery and talk to this endpoint. **Drops the bearer token** — the CLI has no `--token` flag, so verbs hit the endpoint unauthenticated and get 401. Only useful with `curl` plus a hand-built `Authorization` header, not for `mur devtools <verb>` calls. - `--pretty` — indent JSON output. - `--auto` — loopback port scan (slow; use only when lockfile discovery fails). ### Exit codes | Code | Meaning | |---|---| | 0 | Success. | | 1 | Usage error (unknown flag, missing argument). | | 2 | Transport error (endpoint unreachable, timeout). | | 3 | Another devtools session is already active for this project. | | 4 | No live devtools session found. | | 5 | Tool returned a JSON-RPC error. stdout has the error envelope. | ## Selector grammar (5 forms) 1. **Node id** — `r:main/CounterDemo.SubmitButton` — copy from `tree`. Stable within a window's lifetime; invalidated by `switch` / `reload`. 2. **AutomationId** — `#btn-inc`. Matches `AutomationProperties.AutomationId` exactly. 3. **AutomationName** — `[name='Increment']` or `[name="+ 1"]`. Matches `AutomationProperties.Name` OR the visible caption of Buttons / TextBlocks / TextBoxes / ContentControls. 4. **TypePath** — `Button`, `Button[2]`, `StackPanel > Button`. Type name is `GetType().Name`. Index disambiguates. 5. **Reactor source** — `{component:'CounterDemo',line:42}`. Reserved (Phase 3). `[name=…]` cannot be indexed — if it matches multiple, error is `ambiguous-selector` with all candidate ids listed; pick one by node id or prefix with a TypePath step. ## Typical workflows ### "Does the app look right?" — visual diagnosis ```bash mur devtools screenshot --out /tmp/shot.png mur devtools screenshot --selector "[name='Submit']" --out /tmp/btn.png mur devtools tree --selector "#login-form" --view full --pretty ``` Full-view `tree` nodes carry `bounds`, `actualSize`, `desiredSize`, `layout.margin`, `layout.padding`, `isVisible`, `isEnabled`, `automationControlType`. That's usually enough to spot a margin collapse or a zero-size child without running the app in a debugger. ### Diagnosing a layout issue 1. `mur devtools screenshot --out /tmp/shot.png` — confirm what's actually on screen. 2. `mur devtools tree --selector "<container>" --view full | jq '.nodes[] | select(.actualSize.width == 0)'` — find zero-sized children. A child with `actualSize:{width:0,height:0}` under a parent that's sized means the child isn't getting space (missing `Flex(grow:1)` on a ScrollView inside a FlexColumn is a classic). 3. Edit the Reactor code, then `mur devtools reload` (rebuild + relaunch) and re-screenshot. 4. `mur devtools wait "<selector>" --visible --timeout 2000` if the state is async — don't screenshot before mount. ### Diagnosing a contrast / color issue 1. `mur devtools screenshot --selector "<element>" --out /tmp/el.png` — pull the cropped PNG. 2. Decode the PNG and sample foreground/background pixels. Compute WCAG 2.1 ratio = `(L1 + 0.05) / (L2 + 0.05)` where L is relative luminance. Target ≥ 4.5:1 for body text, ≥ 3:1 for large text / UI chrome. 3. If low, check whether the color came from a Theme token (correct — rebind to the right token for the surface) or a hardcoded hex (wrong — replace with a `Theme.*` token; see `skills/design.md`). 4. Edit, `mur devtools reload`, re-screenshot, re-measure. ### Verifying a state-driven change ```bash mur devtools click "[name='+ 1']" mur devtools wait "[name='Current count: 1']" --timeout 1000 mur devtools state # confirm the underlying UseState value ``` `state` is particularly useful when the UI text doesn't obviously encode the value (e.g. a slider position or a theme toggle). ### Reading debug output while the app runs ```bash mur devtools logs --tail 50 # last 50 lines, most recent first mur devtools logs --source debug --pretty # only Debug.WriteLine / Trace mur devtools logs --filter "Nav.*cache" --tail 20 mur devtools logs --follow # stream until Ctrl+C mur devtools logs --since 42 # everything after seq 42 ``` Pair with `state` and `tree` to diagnose state-driven bugs: paste a `Debug.WriteLine` in the render path, reload, reproduce, then drain logs. The buffer (4 MB default) is installed **before** component reflection so it catches startup output too — attach late and you still see what booted. `dropped` in the response reports ring overflow; bump via `--devtools-logs-capacity <MB>` on the app's launch if you're losing entries. Set `--devtools-logs off` on launch to disable capture entirely. ### Inspecting properties and styles ```bash # Enumerate all dependency properties on an element mur devtools properties "#my-button" --pretty # Read a specific property (supports attached properties) mur devtools properties "#my-button" --name Margin mur devtools properties "#my-button" --name Grid.Row # Mutate a property at runtime mur devtools set-property "#my-button" Background "#FF0000" mur devtools set-property "#my-button" Margin "10,5,10,5" mur devtools set-property "#my-button" Visibility Collapsed # Inspect the applied style (explicit only — theme/default styles return hasStyle:false) mur devtools styles "#my-button" --pretty ```
GitHubで見る
この SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る