Skip to content

WinUI DevTools: inspect, comment on and live-edit a running WinUI app - #943

Merged
Nikola Metulev (nmetulev) merged 239 commits into
mainfrom
nmetulev-devtools
Oct 16, 2026
Merged

Nikola Metulev (nmetulev) merged 239 commits into
mainfrom
nmetulev-devtools

Conversation

@nmetulev

@nmetulev Nikola Metulev (nmetulev) commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

Description

Adds WinUI DevTools to winapp. Before this change, winapp run started a WinUI 3 app and agents could only see it through UI Automation (winapp ui). UI Automation shows controls, but not the XAML that produced them, their bindings or their live properties. There was also no way for a developer to point at an element and tell an agent what to change.Now, for a WinUI 3 project, winapp run starts the app with DevTools by default:- For the developer: an in-app toolbar. - Select element opens an inspector window on the clicked element: visual tree, live properties, its XAML file and line. - Comments mode lets you click an element and leave a comment. The comment is saved to .winapp/ui-comments.json and anchored to the element's XAML declaration.- For agents: the winapp devtools command group reads and changes the same live tree: - inspect and search show the tree with each element's source line. - get-property, set-property and diagnose-binding read properties, change them live and explain bindings. - comments list/get/update read the developer's comments and resolve them.It works for C# (.csproj with UseWinUI), C++ (.vcxproj with the Windows App SDK and XAML pages), dotnet run through the Microsoft.Windows.SDK.BuildTools.WinApp package, and Windows Sandbox (--on sandbox --devtools on).When it's on:- Off automatically: in CI (the CI variable is set), with --no-launch, --without-alias, --profile or --on sandbox, and for build-output folders, .cs files and projects that aren't WinUI.- Choose per run: --devtools on|headless|off, or -p:WinAppRunDevTools=... with dotnet run. headless draws nothing in the app; Ctrl+Shift+F12 brings up the toolbar.- Change the default: winapp config set run.devtools off|headless|on (winapp config list shows your per-user settings). The toolbar's ⋯ menu changes the same setting.- Never breaks a run: when DevTools comes from the default and can't start, the app still runs and winapp prints one line saying why. If the app is already running, run closes it so DevTools can start it, and says so.Non-WinUI runs are unchanged: same output, same JSON ({AUMID, ProcessId}) and the same exit codes.## Usage Examplepowershellwinapp run . # WinUI project: DevTools on by defaultwinapp devtools comments list # what the developer asked forwinapp devtools inspect --depth 4 # live tree with source lineswinapp devtools search Submitwinapp devtools get-property SubmitButton Contentwinapp devtools set-property SubmitButton Content "Send"winapp devtools diagnose-binding <selector> Textwinapp run . --devtools headless # DevTools without the toolbarwinapp config set run.devtools off # turn the default off for your accountdotnet run -p:WinAppRunDevTools=off # same choice through the NuGet packageObserved on samples/winui-app (winapp run . --detach, then winapp devtools inspect -a <pid> --depth 6, trimmed):text📝 DevTools on (default) · turn off: --devtools off or winapp config set run.devtools off...[grid-38abee175a] Grid MainWindow.xaml:14 [AppTitleBar] TitleBar MainWindow.xaml:20 [scrollviewer-ed8e9dae6e] ScrollViewer MainWindow.xaml:26 aid=MainScroller [stackpanel-c5c6c99888] StackPanel MainWindow.xaml:27 [textblock-13e9b6d8d7] TextBlock MainWindow.xaml:29 aid=PageTitle Text="WinUI Sample App" [CounterButton] Button MainWindow.xaml:41 Content="Click Me" AutomationProperties.Name="Counter Button" [SubmitButton] Button MainWindow.xaml:83 Content="Submit" AutomationProperties.Name="Submit Button"## Related IssueNone.## Type of Change- ✨ New feature- 📝 Documentation- 🔧 Config/build- 🧪 Test update## Checklist- [x] New tests added for new functionality- [x] Tested locally on Windows- [x] Main README.md updated- [x] docs/usage.md updated- [x] Language-specific guides updated: new docs/guides/devtools.md and devtools-advanced.md; dotnet.md, dotnet-run-support.md and the NuGet README updated- [x] Shipped skills updated in plugins/winapp/skills/: new winui-devtools; winapp-ui-automation, winapp-sandbox and winapp-troubleshoot point to it## Screenshots / DemoComments mode: click an element, type, press Enter. The panel shows the element's XAML file and line.comment panelSelect element opens the inspector on the clicked element. The tree scrolls to it and the panel shows its source line and properties.inspectorSaved comments show as markers when the app starts, including comments an agent added from the CLI.markers## Additional NotesValidation- CI: unit and integration tests, plus native engine and protocol tests (the engine is loaded into a real WinUI test app). A UI test drives the toolbar, picking, comments, the inspector and keyboard focus in a real window. The sample tests (Electron, C++, Rust, WPF, .NET, MAUI, Flutter, Tauri, WinUI) are unchanged and pass.- Real apps: checked hands-on against AI Dev Gallery, WinUI Gallery, Daylight, samples/winui-app, samples/cpp-winui-app and dotnet run, plus Windows Sandbox.- winapp ui sees only the app: with DevTools on or headless, winapp ui inspect and search show the same elements as --devtools off; winapp ui inspect DevToolsOverlay shows the toolbar. With --devtools headless, the app area is also pixel-identical.Performance. DevTools is on by default, so it costs launch time and memory. Measured on samples/winui-app (Xeon 8370C, 64 GB, app already built, winapp run . --detach --no-build, 3 rounds each):- Launch: 2.8–3.9 s with --devtools off, 8.6–11.5 s with it on.- Memory: working set right after launch went from about 160–177 MB to about 187 MB.- Build: the source-capture build is not slower. On Daylight, from a clean build, it took 21.5–21.9 s against 22.7–22.9 s with DevTools off.The app also uses more memory over a long session, as with Visual Studio's F5 with XAML Hot Reload. --devtools off, winapp config set run.devtools off and -p:WinAppRunDevTools=off turn it off.Limits- Binding diagnosis needs the .NET runtime, so it reports unavailable for C++ and Native AOT apps. get-property still shows the value and where it comes from.- A comment added with comments add --file/--name, without a live element, counts in the badge but has no marker in the app.- dotnet run with --on sandbox doesn't support DevTools. With the default it is off; an explicit WinAppRunDevTools=on with Sandbox is rejected with a message saying so.- When default DevTools has to step aside before launch (for example, the running app can't be closed), a --with-alias run falls back to launching by AUMID, so the app's console output isn't shown in the terminal for that run.Size. Against main, the CLI grows by 5.0 MB (x64 and ARM64), the MSIX by 2.1 MB (x64) / 2.0 MB (ARM64), and the npm and NuGet packages by about 4.2 MB. About 2.6 MB of that is the native engine and 2.4 MB is new CLI code. The engine is built with /Gy /Gw and linked with /OPT:REF /OPT:ICF; before that change the CLI grew by 8.0 MB (x64) / 9.3 MB (ARM64), because the engine was 5.5 MB (x64) / 6.8 MB (ARM64). Inspection speed was unchanged in a side-by-side check.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ilters

- -a falls back to title matching (preferring ApplicationFrameHost frames) when the
  matched process owns no top-level window, and fails instead of reporting an empty target
- inspect --interactive includes Document and writable-value elements
- --type/--root/--class-name on every single-selector ui command; invoke accepts them
  without --action

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…d examples

- `winapp ui --help` renders plain text: golden path, -a/<selector> definitions, and
  commands grouped by task (2,741 bytes redirected, down from 6,538)
- every ui command gets plain-text help with 1-3 placeholder examples and a one-line
  global options pointer; examples are data (IHelpExamples) and are parse-tested
- `winapp ui <unknown>` fails with exit 1 (even with --help) before sandbox routing,
  suggesting up to two commands; --json emits the error envelope with `suggestions`
- `find` and `tree` are aliases for `search` and `inspect`
- root help points agents at `winapp ui --help`
- a missing option value ends with a pointer to --help instead of the full help
- inspect's footer example uses set-value for editable-only elements

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ghten unknown-command detection

- `-a` falls back only to ApplicationFrameHost frames; a process with no window yet keeps the
  process-scoped target so `wait-for` and `status` behave as before
- bare `winapp ui` still prints the group help after "Required command was not provided"
- only the first unmatched token counts as a command, so `ui -a Notepad` and `ui -- inspect`
  report the normal parse error instead of an unknown command

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment thread src/winapp-CLI/WinApp.Cli/Program.cs Fixed
Comment thread src/winapp-CLI/WinApp.Cli/Commands/DevToolsSetPropertyCommand.cs Fixed
@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Build Metrics Report

Validation passed. All required build and validation jobs succeeded.

Binary Sizes

Artifact Baseline Current Delta
CLI (ARM64) 59.54 MB 64.66 MB 📈 +5.12 MB (+8.60%)
CLI (x64) 59.52 MB 64.60 MB 📈 +5.08 MB (+8.54%)
MSIX (ARM64) 24.72 MB 26.77 MB 📈 +2.05 MB (+8.29%)
MSIX (x64) 26.23 MB 28.37 MB 📈 +2.14 MB (+8.15%)
NPM Package 51.57 MB 55.77 MB 📈 +4.21 MB (+8.16%)
NuGet Package 51.68 MB 55.91 MB 📈 +4.23 MB (+8.19%)

.NET Test Results (TRX reports)

Other suites are reflected in the overall validation status above.

✅ 10419 passed, 43 skipped out of 10462 tests in 1652.5s (+1622 tests, +107.4s vs. baseline)

Test Coverage

✅ 87.2% line coverage, 81.3% branch coverage · ⚠️ -0.4% vs. baseline

CLI Startup Time

46ms median (x64, winapp --version) · 📉 -27ms vs. baseline


Updated 2026-10-16 06:02:40 UTC · commit 9874630 · workflow run

@nmetulev

Copy link
Copy Markdown
Member Author

Triage fixes (groups 1 and 2)

Seven commits on top of the original squash. Each fix has a regression test that fails without it. Live checks ran against AI Dev Gallery and samples/winui-app (from a temp copy). "Before" is pre-fix code (the installed build, or this branch before that fix), except where marked "reported".

Fixed

Picking text with no source picks the authored control. Hover and click share the path; Just my XAML off keeps the raw hit.

before  Internal.pick on "Click Me"      -> Microsoft.UI.Xaml.Controls.TextBlock
after   Internal.pick on "Click Me"      -> Microsoft.UI.Xaml.Controls.Button  CounterButton
after   Internal.pick on "Enable feature" -> Microsoft.UI.Xaml.Controls.CheckBox FeatureCheckBox

A comment on an element that still has no source says Saved. Not linked to source. and is weak / requiresConfirmation in JSON.

diagnose-binding on x:Bind. Private members, page-owned and window-owned (static App.MainWindow) x:Bind now evaluate. Reported before: bad-segment / unavailable.

[ModelsExpander] SettingsExpander.ItemsSource   state: evaluated  path: cachedModels             source: SettingsPage
[MRUView] ItemsView.ItemsSource                 state: evaluated  path: mostRecentlyUsedItems    source: HomePage
[titleBar] TitleBar.IsBackButtonVisible         state: evaluated  path: NavFrame.CanGoBack       source: MainWindow

Comment anchors. A unique declaration or x:Name is confirmed, whatever the runtime instance count; via type candidates are dropped when a declaration or name matches; the historical line appears only when there is no current match, and never as file:.

after  SubmitButton: anchorConfirmed=true requiresConfirmation=false (via declaration)

set-property on a bound property warns, and JSON has replacedBinding. The chain's Default "0" for String is now empty.

✅ [textblock-1efceef664] TextBlock.Text: C:\Users\…\cpu-int4-rtn-block-32-acc-level-4 -> C:\edited
⚠ This overrode {x:Bind Path} with a local value until the binding updates again.

Rerun while the app is running

before  … Access was denied. Check permissions on the app's input and staging files. If a previous app instance is still running, close it explicitly before retrying; access denied alone does not identify a lock owner.
after   … The app is still running from this build (PID 16732), so its files cannot be replaced. Close it, then run again.

Identical sourceWarnings collapse into one entry with count, and the entry says to rebuild. I could not recreate the 94-file case without modifying Gallery; unit-tested.

Smaller fixes

search "Phi 3 Medium"   before  No element matches … not an element's rendered text.
                        after   ⚠ Nothing in your XAML matches "Phi 3 Medium"; these are generated or framework elements, such as items created from data.  (+ NavigationViewItem)
list --status resolved  before  2 comments total (1 resolved — hidden, use --all).
                        after   2 comments total (1 open hidden; use --all).
inspect --filter … --depth 1
                        before  No elements matched. Drop --filter to see the whole tree.
                        after   No elements matched within --depth 1; deeper elements were not searched. Rerun with --depth 2, or use `winapp devtools search "submitbutton"`.
comments get --json     before  "declaration": "\u003CButton … x:Name=\u0022SubmitButton\u0022
                        after   "declaration": "<Button … x:Name=\"SubmitButton\"
run --json devTools     before  "NodeCount", "OverlayShown", "Comments"
                        after   "nodeCount", "overlayShown", "comments"

Also fixed: the comments pane says "not linked to source" for unanchored comments and pluralizes the footnote correctly; Layout adorners is a UIA toggle (ToggleState: On/Off) with a state-neutral tooltip; "propert(ies)" is gone.

Group 2: -a on comments add/update/delete; --on hidden on comments list/get; get-property <selector> Text and set-property <selector> Width 200 (-p still works); run --detach prints Next: winapp devtools inspect -a <pid>; human output drops running bytes are not verified and the healthy-binding reason (both stay in JSON); the inspector's stretch note appears only for a non-default alignment.

Not reproduced

Quick-edit panel off-screen. On a 150% single monitor, the panel stays inside the work area for a full-width TextBox and a Gallery expander, with the Popup unconstrained. The existing placement tests already cover the flip, shift and height cap. I added the element and viewport to the overlay.sel shown log line. A repro (resolution, scale, monitors, window state) would unblock it.

Also

  • Rebased on main. Hide MSBuild property JSON from winapp run --aot output #936 fixed the same --aot property-JSON leak, so this branch now uses its result-file approach (plus the build-pass flag DevTools needs) and drops the stdout filter.
  • Pre-existing: the first Internal.pick of a session returns no-hit (old build too).

@nmetulev

Copy link
Copy Markdown
Member Author

Quick-edit panel off-screen: not a bug. I reproduced the reported setup: a window as wide as the monitor, then picks on a full-width element and on one near the bottom, at 150% (monitor 3840×2160 px, work area 3840×2088 px). The panel stays inside the work area:

full-width TextBox  Quick edits (3229,1260 600x815)   Open in DevTools (3248,2022 190x35)
Submit near bottom  Quick edits (194,1260 600x815)    Open in DevTools (214,2022 190x35)

The original report compared physical coordinates with a logical screen size. winapp ui reports physical pixels (panel x=1751, "Open in DevTools" y=1206), while "1843×1229" was a DPI-unaware read of a 2764×1843 px screen. In physical pixels both points are on-screen; they are only outside the app window, which the guide allows. No change made, apart from logging the element and viewport on overlay.sel shown.

@nmetulev

Copy link
Copy Markdown
Member Author

Group 3 decisions implemented

Four commits. Each change has a test that fails without it. "Before" is the pre-fix build; "after" is from this branch, run on samples/winui-app.

Pin (kept) is a toolbar toggle. It uses the same accent on-state as Pick and Layout adorners, and the corner dot is gone.

before  (old toolbar.xaml) DevToolsProtoPin: Button (no toggle state) + DevToolsProtoPinDot, a 7px accent dot at the bar corner
after   DevToolsProtoPin: ClassName ToggleButton, ToggleState On -> invoke -> Off; accent background when pinned

Theme toggle (kept on the toolbar). Tooltip "Toggle the inspected app between Light and Dark themes" is now "Switch app theme".

Inspector Properties pane. Order is now: type and name, ancestors, source line + Open, then the filter and the grid. The box model and the offered/took explanation move into a Layout section after the grid. It is collapsed by default, and its state is remembered across runs (devtools-InspectorLayoutOpen.setting flipped 1 -> 0 as I toggled it). Source-verification notes are one line, with the full reason on hover and in the UIA HelpText. The new guide screenshot shows the reordered pane: devtools-inspector.png.

set-property on a bound property now ends with a restart hint:

⚠ This replaced the binding {Binding Title}; its source no longer updates Text. Restart the app to restore the binding.

No devtools reset and no automatic capture.

pid is now processId in all winapp devtools JSON (attach, list, live commands, error payloads). run --json keeps ProcessId, and --pid/-a are unchanged. The native protocol's DevTools.negotiate still reports pid; that's the wire protocol, not a CLI payload.

before  devtools list --json -> "pid": 23652        get-property … --json -> "pid": 0
after   devtools list --json -> "processId": 23652  get-property CounterButton Content --json -> "processId": 29736

Guide split. devtools.md starts with five numbered steps and two screenshots (16 KB and 28 KB, in docs/images/). Then it covers reviewing comments with an agent, finding and inspecting, diagnosing bindings, live property changes, refresh, continuing from a terminal, and failures. devtools-advanced.md covers Sandbox, attaching (with the visual UI prerequisites), window and subtree targeting, comment storage and authoring, and the protocol (call, Binding.walk, capture/restore). The two pages are cross-linked. usage.md, sandbox-execution.md, the docs index and the skills now point at the right page, and the ProcessId-vs-pid note is gone.

Unchanged: --all, and Layout adorners (already off by default and remembered).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…hored UI comments

`winapp run --devtools` injects an inspection agent into a WinUI 3 app: an
in-app overlay (pick, quick edit, comments), an inspector window, and a
`winapp devtools` command group for agents (inspect, search, get/set
property, get-source, diagnose-binding, comments).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Keep walking past a nearer generated scope that does not reference the target
  (a UserControl whose content the page supplies) instead of reporting unavailable.
- Enrol windows held in static App fields (App.MainWindow { get; private set; }).
- Resolve non-public members for {x:Bind}, which compiles against its owner.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…omments

A hit with no source (a Button's string content, a TextBox placeholder, a nav
item label) now promotes to its nearest ancestor with authored source, the same
as a template part. Hover and click share this path; Just my XAML off keeps the
raw hit. A comment saved on an element that still has no source reports
'Saved. Not linked to source.' in the panel.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- A declaration that matches exactly one source declaration, or a unique
  x:Name, is a confirmed anchor; runtime instance counts no longer weaken it.
- Drop 'via type' candidates when a declaration or x:Name match exists.
- Show the historical creation location only when there is no current match,
  and never print an empty line number.
- A comment without a source file is weak, requires confirmation, and says it
  is not linked to source.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Comments pane: an unanchored comment says 'not linked to source' instead of
  'not on this screen' and is not counted as off screen; the footnote agrees in number.
- Layout adorners is a ToggleButton, so UI Automation exposes its state; the tooltip
  no longer says 'Show' while it is on.
- The precedence chain shows a null default as empty instead of the raw handle '0'.
- The inspector's extra-width note appears only when HorizontalAlignment is not Stretch.
- Log the quick-edit panel's element and viewport for placement triage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…arnings

- When staging fails because the app is still running, name its PID(s) by matching
  the process image to the staged path, instead of a generic access-denied error.
- Collapse identical source-location warnings into one entry with a count, and say
  to rebuild when the build output does not match the XAML sources.
- run --devtools --detach ends with 'Next: winapp devtools inspect -a <pid>' and no
  longer prints 'running bytes are not verified'.
- devTools fields in run --json are camelCase like the rest of the new fields.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- set-property warns when it replaces a {Binding} or overrides an x:Bind (replacedBinding in JSON).
- get-property/set-property take the property positionally; -p still works.
- search falls back to the whole tree when nothing in your XAML matches (items created
  from data), and no longer claims displayed text is not searched.
- inspect --filter says when matches may be below --depth.
- comments: -a alias; list --status footer counts what was hidden; --on is hidden
  where it is always rejected.
- Relaxed JSON escaping so captured XAML is readable; 'properties' instead of 'propert(ies)'.
- Hide the healthy-binding scope disclaimer from human diagnose-binding output.
- Binding-owner e2e: static-held windows diagnose; unique x:Name comments confirm.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Pinned uses the same accent on-state as Pick and Layout adorners and is a
  ToggleButton, so UI Automation reports its state. The corner dot, which read as a
  notification badge, is removed.
- The theme button's tooltip is 'Switch app theme' so it is not clipped at the edge.
- Move the settings helpers to DevToolsSettings.h so the inspector can use them.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The Properties pane shows the header (type and name, ancestors, source line and
Open), then the filter and the grid. The box model and the offered/took explanation
move into a Layout section after the grid, collapsed by default and remembered
across runs. Source-verification notes are one line; the full reason is on hover
and exposed to screen readers.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…inding

- winapp devtools JSON (attach, list, live commands, errors) uses processId like
  winapp ui and winapp target. run --json keeps ProcessId; --pid and -a are unchanged.
- The set-property binding warning ends with 'Restart the app to restore the binding.'

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
devtools.md starts with numbered steps and two screenshots, then covers reviewing
comments with an agent, finding elements, bindings, live changes, refreshing, the
terminal workflow and failures. devtools-advanced.md covers Sandbox, attaching to a
running app, window and subtree targeting, comment storage and the protocol.
Links in usage, Sandbox docs and skills point at the right page.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
DevTools.negotiate (and the legacy hello reply) report processId instead of pid,
matching the CLI payloads. The protocol schema and the CLI's negotiate parser
change with it; there is no alias because the protocol has not shipped.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Generalize #942's ui help renderer and unknown-command handling to any compact
  help group; winapp ui output is unchanged apart from one pointer line.
- winapp devtools --help: purpose, the main workflow, what -a and <selector> mean,
  commands grouped by task, and when to use winapp ui instead. Every devtools
  command has examples, stored as data and parsed in tests. Unknown devtools
  commands suggest the closest one.
- Root help gets a short 'Start here' block for winapp ui and winapp devtools.
- A winapp ui command whose target has the DevTools agent prints one tip line on
  stderr (not with --json or --quiet).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
#942 added the options to ui record; the hand-written argument builder did not
forward them, which failed the generated-options sync test.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The winui-devtools skill now matches requests to change, translate or restyle a
running UI without editing source; winapp-ui-automation says it reads, clicks and
types in any app and defers live WinUI edits to winui-devtools. The UI automation
and DevTools docs mention the new help and the ui tip.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment thread README.md
@chiaramooney

Copy link
Copy Markdown
Contributor

Based on time before you go out we don't have to address all these in this PR but filing here for us to track going forward.

Agent found a few functions which seem to duplicate logic:

Issue Sites
Three helpers defined as file-scope statics and re-declared as byte-identical lambdas in the same file DevToolsWindow.TreeRows.inc:79/144, :97/181, :111/161 — both versions live
Layout reads re-implemented (already drifted) DevToolsTap.EditBridges.inc:711-769 vs ElementCommands.inc:232-315
SplitAroundSegment copy-pasted DevToolsBindingRow.cpp:13-31 vs DevToolsPathWalk.cpp:15-33
Property-provenance labels in C++ and C# (code comment admits it) DevToolsTap.PropertyRead.inc:25-45 vs DevToolsPropertyRow.cs:37-60
XAML property-name knowledge in two tables EditBridges.inc:87-99 vs PropertyRead.inc:77-82
Truncation limit 120 hardcoded three times Query.inc:75, DevToolsBatch.h:95, DevToolsPreviews.cs:129
Slug width duplicated across the language boundary DevToolsSlug.cs:10-19 hardcodes 10, documents the C++ constant as source of truth
Check/CheckEq assertion helpers re-declared ~16 test files
packages.config re-parse ProjectContextDetector.cs:74-81 vs PackagesConfigReader.cs:22-44

Code Cleanup For Styles/Controls:

  • Five identical six-brush ToggleButton override blocks in one file: xaml/toolbar.xaml lines 101, 126, 163, 267, 345 — hoist into the existing <Canvas.Resources> at line 9.

  • Style literals scattered through C++-concatenated markup: 24 FontSize= in PaneRows.inc, 16 in PropsLayout.inc, 13 repeats of Foreground="{ThemeResource TextFillColorSecondaryBrush}" in PaneRows.inc alone, 64 CornerRadius= literals overall. Three keyed Styles in window-shell.xaml would absorb nearly all of it.

  • DevToolsTheme.h isn't a theme file — it holds three ints. The two actual palettes are buried in DevToolsOverlay.State.inc:442-493. Palette→token substitution maps are then re-typed three times (State.inc:468, SelectionAnchor.inc:166, :213).

  • Control re-creation is inconsistent: property rows use ItemsRepeater + DataTemplate (good), comment rows too (good), but tree rows are hand-built per row from raw Grid/Button/TextBlock activations at TreeRows.inc:202/255/324/481. The tree is the only one of three list surfaces not templated.

Screenshots can include the toolbar

Now that devtools are on by default, an agent using winapp ui screenshot will by default take a screenshot of the app which includes the devtools overlay. Should we add a note to the documentation or winapp cli skills that tells agents they should run with the devtools off or no overlay before taking an app screenshot?

# Conflicts:
#	src/winapp-CLI/WinApp.Cli.Tests/FakeDotNetService.cs
#	src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.Query.cs
#	src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.cs
In a packaged app that references a XAML library, the library's XAML is copied loose next to the exe while the app's own pages are compiled into resources.pri. The layout scan then trusted the loose file and hid the app's pages: devtools inspect showed only the library's TextBlock. With a source root (any winapp run launch), the default tree now uses the same rule as picking: a file in the project on disk.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@nmetulev

Nikola Metulev (nmetulev) commented Oct 15, 2026 •

Copy link
Copy Markdown
Member Author

Merged main (acacbd4) into this PR: 13e5431, plus one fix found while checking (e51afe3)

File Main This PR Resolution
WinApp.UIAutomation/Services/UiAutomationService.Query.cs #1102 added preferExactName to SearchConstrained / SearchConstrainedCore added excludeDevToolsChrome to the same signatures Kept both parameters; each caller passes the one it needs
WinApp.UIAutomation/Services/UiAutomationService.cs #1101 made WalkTree return bool, tracking windows it has walked so WebView2 cycles end added inspectedIdentities (popup de-duplication) and skips the DevTools chrome subtree Main's WalkTree plus our parameter and chrome skip; the skip now returns true so the walk continues with the next sibling
WinApp.Cli.Tests/FakeDotNetService.cs #1032 records each build's environment overrides writes the build result file the DevTools build reads Both, in the streaming and inherited fakes

Checked with main's changes

Found while checking #1032 (e51afe3): before this fix, devtools inspect on that app showed only the library's TextBlock.

  • Cause: the app's own pages are compiled into resources.pri, while the library's XAML is copied loose next to the exe. The "your XAML" filter trusted that loose file and hid everything else.
  • Fix: with a source root (any winapp run launch), the filter now uses the same rule as picking: a file in the project on disk.
  • Before: Found 1 element(s), only TextBlock LibW/Control1.xaml.
  • After: StackPanel MainWindow.xaml:2, [Title] TextBlock MainWindow.xaml:3, [FromLibrary] Control1 MainWindow.xaml:4.
  • samples/winui-app shows the same 14 elements as before.
  • A new native test failed before the fix.

Validation: Release build of the solution with TreatWarningsAsErrors (0 warnings); CLI DevTools, Comment, Run, UI and ProjectRunService tests (3,575 pass); UI Automation tests (996); native units, overlay (550), dispatcher (461/461), window (95/95).

Main moved again (ad0a62c), merged in 6bbbb83:

# Conflicts:
#	src/winapp-CLI/WinApp.Cli/Models/ProjectRunModels.cs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
With DevTools on by default, a screenshot showed its toolbar, markers, highlight and comment flyout. The screenshot now asks the engine to make that chrome transparent for the capture and puts it back afterwards, so the toolbar, selection, pick mode and focus are unchanged. DevTools' own windows (the inspector and the flyout's popup) are left out of the composite.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…f them

BuildOneTreeRow re-declared AppendUiTo, NewTextBlock and SetAutomationTextOn as identical lambdas.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Route1Layout and CmdElementLayout each carried a copy of the same reads. Both now call ReadElementLayout_ui; Layout.get still adds the parent's wire handle.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
… window

The engine now names where each Property.get row's value came from (origin), using the same rule as the window's pill, and get-property reports it as source instead of deriving its own copy. Output is unchanged.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
DeriveEditKind asks CardMapType instead of listing Foreground and Background again.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Previews and query results each cut values at a literal 120.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Each native test file declared its own Check/CheckEq helpers and failure counter. They now include DevToolsTestCheck.h, which gives every file its own counter.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
ProjectContextDetector re-parsed packages.config itself. It now uses PackagesConfigReader, so a malformed packages.config no longer hides UseWinUI and turns the project Unknown.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The five toolbar ToggleButtons each repeated the same six transparent checked-state brushes. They now come from the toolbar Canvas resources.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The C++-built pane markup repeated the secondary text color, the note text and note box, and the section caption. They are now DT_* constants defined once in DevToolsWindow.cpp.

They are not keyed Styles in window-shell.xaml: XamlReader.Load parses each pane fragment on its own, so a StaticResource or ThemeResource that points at the shell fails to load (tried both; the pane showed 'Unable to render properties').

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The two overlay palettes move from DevToolsOverlay.State.inc to DevToolsTheme.h. The toolbar, comment flyout and selection builders each mapped palette fields to $TOKEN$s; they now share one DevToolsXamlSubst overload that fills every palette token before the builder's own values.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Conflict in docs/usage.md (C++ run notes): kept the DevTools line and main's -c / -p:Configuration line from #1085.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
winapp ui screenshot now leaves DevTools chrome out, so the comment-marker check saw no markers. The check now captures the window with PrintWindow and measures the label offset from the window's physical rect.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
In a blank app the collapsed toolbar said "Comments": the label treated visible comment markers, which are on by default, as a comment state, and hid the count when it was 0. The pill now reads DevTools at rest, and Picking or Commenting only while clicks go to DevTools. The pill no longer shows a comment count; the Comments button's badge still does. Its UI Automation name stays "DevTools (open toolbar)" and adds the active mode.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The pick-mode check ran after a menu hover had left the toolbar expanded, so the pill was not on screen. It now runs right after the at-rest check and also checks leaving pick mode. The later check with saved comments first moves the pointer off the toolbar.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Writing a string to TitleBar.Content made the setter fail with E_NOINTERFACE, but WinUI kept the string. The TitleBar then failed on it at the next window activation and WinUI fail-fasted the app (0xC000027B), taking DevTools with it. Every DevTools write (DevTools window, set-property, the protocol) now remembers the property's local value, puts it back when the setter fails, and clears the error WinUI left on the thread. The DevTools window shows the reason on the row, for example: Couldn't set Content: TitleBar rejected 'x' (String). The previous value was kept.

Also fixes the binding-confirm prompt text, which was written through the wrong interface.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
After pick mode the DevTools window is open and the toolbar stays expanded, so the pill isn't on screen. The label rule takes no comment count, which the native test covers.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@nmetulev

Copy link
Copy Markdown
Member Author

Chiara Mooney (@chiaramooney) thanks for the review. Each point below is its own commit.

README command lists: winapp devtools is now in both lists (b0d7ea1). Both threads are resolved.

Screenshots include the toolbar: winapp ui screenshot now hides DevTools chrome (toolbar, comment markers, highlight, comment flyout) during the capture and restores it right after (32fb72e). Toolbar state, such as pick mode or an open toolbar, is the same afterwards. A screenshot with DevTools on is pixel-identical to the same app with --devtools off. The cost is about 50 ms per screenshot (table below). docs/guides/devtools.md and the winapp-ui-automation skill each say this in one line.

Duplicated logic

Item Commit Notes
TreeRows helpers re-declared as lambdas d540cb0
Layout reads 69a1a05 One read. Layout.get output is byte-identical before and after on 5 elements.
SplitAroundSegment cdcdd31
Property-provenance labels b7a199e The engine sends the label and C# uses it. get-property output is identical (about 1,100 lines compared).
XAML property-name tables 7bd0d69
120-character limit 87da00f One C++ constant. C# no longer hardcodes 120; it reads the engine's truncated flag, so there's no second copy to keep in sync.
Slug width 6dd0313 A C# test reads the C++ constant and fails if they differ.
Check/CheckEq ee722a7 One shared header for all native test files.
packages.config re-parse 2645077 Uses PackagesConfigReader. One side effect: a malformed packages.config no longer turns a <UseWinUI>true</UseWinUI> project into "unknown".

Styles and controls

  • Toolbar ToggleButton overrides: hoisted into <Canvas.Resources> (76e8b11).
  • Repeated literals in C++-built markup: these are now named constants defined once in DevToolsWindow.cpp (73681ef), not keyed Styles in window-shell.xaml. The pane markup is parsed by XamlReader.Load one fragment at a time, so a Style key from window-shell.xaml isn't in scope there. I tried both {StaticResource} and {ThemeResource}, and each time the properties pane failed with "Unable to render properties for this node".
  • Palettes: both palettes moved to DevToolsTheme.h, and one map fills the palette tokens for all three builders (aa85f33).
  • Tree rows: they stay hand-built for now.

No visible change: before/after screenshots of the toolbar, both toolbar menus, the comment flyout and the inspector (tree, Set view, Comments), in light and dark, match pixel for pixel. The only differences are the PID in the title, caret blink and Acrylic noise.

Also fixed: the collapsed toolbar said "Comments" in a blank app (8cc3836). Visible comment markers (on by default) made the label say "Comments", and it hid the count when there were 0 comments. Now the collapsed toolbar reads DevTools at rest, whatever the comments or markers state. It reads Picking or Commenting only while that mode is on, because then clicks go to DevTools instead of the app. The comment count stays on the Comments button's badge. The UI Automation name stays "DevTools (open toolbar)" and adds the mode while one is on.

Collapsed toolbar label before and after
Also fixed: editing TitleBar.Content crashed the app (d9d72f8).

  • Before: writing text to a TitleBar's Content (in the DevTools window or with set-property) failed, but WinUI kept the text anyway. get-property then showed Content: x [Local]. At the next window activation, the TitleBar failed on that value and WinUI ended the app (Application Error, exception code 0xc000027b in Microsoft.UI.Xaml.dll). The toolbar and DevTools window went with it.
  • Cause: the failure is synchronous, but the value is left half-set. The crash comes later, from the TitleBar's own activation handler, and it reports the error our write left on the UI thread.
  • After: every DevTools write (the DevTools window, set-property, the protocol) remembers the property's previous local value. If the control rejects the new one, DevTools puts the old value back and clears that thread error. The row shows why:
> winapp devtools set-property AppTitleBar Content x -a <app>
Error: Couldn't set Content: TitleBar rejected 'x' (String). The previous value was kept (0x80004005) (property-write-failed)

DevTools window showing the refused TitleBar.Content write

Button.Content = "Hello" still works. There's no list of "text-capable" properties: the control's own setter decides, so writes like TextBox.Header = "Name" keep working. A new E2E phase writes TitleBar.Content, checks the value is unchanged, switches window activation 10 times, and checks that Button.Content still accepts text. It fails on the previous build (value left as x) and passes now.

Performance: baseline 6bbbb83 (right after the main merge) vs aa85f33 (all of the above). Later commits merge main, fix test scripts, change the collapsed-toolbar label (runs only when a mode changes), and add a read of the previous value before each DevTools property write (not on any hot path).

Method: the same machine, alternating A/B three times per build. No other builds or benchmarks ran during the window. Engine numbers come from the engine's own UI-thread counters; wall times include the CLI round trip.

Metric Baseline median (spread) Head median (spread) Change
Inspector open, AI Dev Gallery (487 nodes), engine 36.4 ms (1.2) 36.7 ms (1.7) +0.6%
Inspector open, 2,519-node tree, engine 173.4 ms (7.5) 173.0 ms (3.1) -0.2%
Full tree (Just my XAML off), AI Dev Gallery, engine 27.9 ms (3.8) 28.3 ms (2.7) +1.4%
Full tree, 2,519-node tree, engine 129.2 ms (8.0) 126.7 ms (2.3) -2.0%
Scroll tree top to bottom to top, 2,519 nodes, wall 308.5 ms (195) 301.7 ms (9.7) -2.2%
Select deep element to revealed, 2,519 nodes, engine 16.9 ms (1.2) 15.7 ms (0.6) -7.0%
Memory after opening inspector, 2,519 nodes 266.0 MB (11.3) 261.2 MB (11.8) -1.8%
Comments pane open, 320 comments, engine 14.1 ms (0.7) 13.6 ms (1.9) -3.7%
Toolbar ⋯ menu open, wall 306.2 ms (7.6) 309.8 ms (8.5) +1.2%
Toolbar Comments menu open, wall 302.3 ms (3.6) 302.1 ms (9.9) -0.1%
Element creation added by DevTools (160k elements) +75.9% (19.2) +72.0% (7.3) -3.9 pts
winapp ui screenshot, DevTools on minus off 2.5 ms (12.2) 52.9 ms (23.0) +50 ms (the new hide/restore)
CLI startup (--version, 30 interleaved runs) 93.4 ms 93.6 ms +0.2%
winapp.exe / engine DLL size 49,399,296 / 2,726,912 B 49,405,952 / 2,726,912 B +6.6 KB / 0

Nothing is worse than its run-to-run spread except the screenshot, which is the new hide/restore cost and stays under the 100 ms target.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-review Agent work and technical checks complete; awaiting review or re-review, not approval or merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: winapp run --devtools: in-app inspector and shared design-time protocol

5 participants