Add typed UI geometry context - #844
Conversation
Expose per-window DPI context in JSON output and add typed element metadata to get-property while preserving existing fields. Fixes #820 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟢 Approval recommended
The implementation is additive, handles invalid DPI reads explicitly, preserves compatibility, and includes focused coverage and documentation.
Pull request overview
Adds typed geometry and DPI context to UI automation JSON while preserving compatibility.
Changes:
- Adds per-window DPI metadata to
inspectandstatus. - Adds a scrubbed typed element to
get-property. - Updates tests and JSON documentation.
File summaries
| File | Description |
|---|---|
WinApp.UIAutomation/Models/UiElement.cs |
Clarifies physical-pixel geometry. |
WinApp.Cli/NativeMethods.txt |
Enables DPI APIs. |
Helpers/WindowDpiContextProvider.cs |
Reads and validates window DPI context. |
Helpers/IWindowDpiContextProvider.cs |
Defines the DPI provider contract. |
Helpers/UiJsonContext.cs |
Extends JSON models. |
Helpers/HostBuilderExtensions.cs |
Registers the DPI provider. |
Commands/UiStatusCommand.cs |
Adds target DPI metadata. |
Commands/UiInspectCommand.cs |
Adds per-window DPI metadata. |
Commands/UiGetPropertyCommand.cs |
Adds the typed element projection. |
WindowDpiContextProviderTests.cs |
Tests mapping and failures. |
UiCommandTests.SimpleVerbs.cs |
Tests status DPI errors. |
UiCommandTests.Inspect.cs |
Tests inspect DPI errors. |
UiCommandTests.cs |
Tests new JSON contracts. |
FakeWindowDpiContextProvider.cs |
Supports command testing. |
SKILL.md |
Documents the updated envelopes. |
references/ui-json-envelope.md |
Provides canonical JSON examples. |
docs/ui-automation.md |
Documents user-facing semantics. |
Review details
- Files reviewed: 17/17 changed files
- Comments generated: 0
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Build Metrics ReportBinary Sizes
Test Results✅ 6841 passed, 37 skipped out of 6878 tests in 891.3s (+22 tests, -184.5s vs. baseline) Test Coverage✅ 85.9% line coverage, 79.9% branch coverage · ✅ no change vs. baseline CLI Startup Time71ms median (x64, Try This BuildInstalls the MSIX for your architecture, replacing any previously installed build. Needs the GitHub CLI — the command offers to install it and sign you in if it is missing. & ([scriptblock]::Create((irm https://raw.githubusercontent.com/microsoft/winappCli/main/scripts/winapp-pr.ps1))) 844Switching between builds often?Put the tool on your PATH once: & ([scriptblock]::Create((irm https://raw.githubusercontent.com/microsoft/winappCli/main/scripts/winapp-pr.ps1))) -AddToPathThen this build is just: winapp-pr 844Run Updated 2026-09-17 00:33:48 UTC · commit |
Separate nested top-level HWND trees without duplication, preserve selector safety, and recover PID-only ancestor window context. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
PR Review — typed UI geometry contextDecision: changes required. The feature is well-scoped, on-mission, documented, and green (builds clean with 0 warnings, 68 targeted tests pass). One real regression blocks it: 🔴 Must fix —
|
Zach Teutsch (zateutsch)
left a comment
There was a problem hiding this comment.
One must fix surfaced above, it seems like the trigger circumstances would be pretty rare (an inspect before a window loads) but since it would cause a crash it might be worth addressing.
Up to your judgement.
There was a problem hiding this comment.
🟢 Approval recommended
The solution builds cleanly, targeted suites pass, and live status, inspect, and get-property JSON behave as documented.
Review details
- Files reviewed: 20/20 changed files
- Comments generated: 0 new
- Review effort level: Balanced
Address the Code Quality review without changing promotion behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Scoped inspection of an owned window can query DPI using the process’s main HWND and emit incorrect geometry context.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 20/20 changed files
- Comments generated: 1
- Review effort level: Balanced
Resolve scoped inspect geometry against the selected element's top-level HWND. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Scoped WinUI elements without their own HWND can still receive DPI context from the wrong window.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 20/20 changed files
- Comments generated: 1
- Review effort level: Balanced
Walk UIA ancestors to derive the owned top-level window for scoped inspect and capture targeting. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟢 Approval recommended
The implementation matches the requested contract, builds cleanly, and all targeted tests and plugin validation passed.
Review details
- Files reviewed: 22/22 changed files
- Comments generated: 0 new
- Review effort level: Balanced
Preserve owned-window action HWNDs and surface secondary DPI failures without discarding the inspect tree. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Inspect can retain a stale nonzero session HWND after successfully recovering a different live UIA root, causing incorrect or failed DPI output.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
src/winapp-CLI/WinApp.UIAutomation/Services/UiAutomationService.cs:176
- What is wrong: a nonzero session HWND is kept even when
GetRootElementrejected that handle and recovered a different live window through its PID fallback. Show me: a target whose stored window closes between resolution and inspect causesGetRootElementCoreto fall back successfully to the process's current UIA root, but this branch skips resolving that root because the stale handle is nonzero; JSON then reads DPI from the stale HWND and fails (or reports another window if the handle was reused). Why it matters:ui inspect -a <pid> --jsoncan fail or emit the wrong DPI context even though the existing UIA recovery found a valid window. Smallest fix: derivemainHwndfrom the resolved root first and useuiTarget.WindowHandleonly when the root exposes no HWND; add a stale-nonzero-handle regression alongside the PID-only recovery test.
- Files reviewed: 22/22 changed files
- Comments generated: 0 new
- Review effort level: Balanced
Use the live UIA root handle when a stored session window is stale. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟢 Approval recommended
The previously identified HWND-resolution defects are fixed with focused real-provider and command-level regression coverage.
Review details
- Files reviewed: 22/22 changed files
- Comments generated: 0 new
- Review effort level: Balanced
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> # Conflicts: # src/winapp-CLI/WinApp.Cli/Helpers/HostBuilderExtensions.cs
There was a problem hiding this comment.
🟢 Approval recommended
The prior HWND-resolution issues are addressed with focused real-provider regressions, and no unresolved defects were found.
Review details
- Files reviewed: 22/22 changed files
- Comments generated: 0 new
- Review effort level: Balanced
## Description
Read `FontWeight`, `FontName`, `FontSize`, `ForegroundColor`,
`IsItalic`, and `StrikethroughStyle` across the element's whole
TextPattern document. Uniform values are invariant strings; UIA's mixed
and reserved-not-supported COM tokens become `Mixed` and `NotSupported`,
while elements without TextPattern return `Unavailable`. Provider
failures retain the existing error contract.
Unknown case-sensitive property names now fail with `invalid_arguments`
before querying the app. Omitting `--property` includes all six
attributes. The published `elementId` and string-valued `properties`
JSON envelope and existing geometry formats are unchanged.
## Usage Example
```powershell
winapp ui get-property Document -a myapp --property FontWeight --json
# {"elementId":"Document","properties":{"FontWeight":"700"}}
winapp ui get-property Document -a myapp --json
```
## Related Issue
Fixes #822. Independent of #841, #842, and #844. No query predicates,
explicit actions, selection/caret/range APIs, batch mode, or
persistence.
## Type of Change
- New feature
- Test update
- Documentation
## Checklist
- [x] New unit, CLI, and real-provider tests
- [x] Tested locally on Windows: 26 formatting/property library tests
and 28 CLI/property/public-API tests pass
- [x] `scripts/build-cli.ps1 -SkipTests` completes NativeAOT x64/arm64
publishing, npm/NuGet/MSIX packaging, and generated docs/schema
- [x] Canonical UI automation docs and shipped skill/reference updated
## Additional Notes
Real-app coverage reads native RichEdit uniform and mixed formatting
independently of selection, a Button without TextPattern, and an
HWND-based provider returning UIAutomationCore's actual
reserved-not-supported token through the repository COM projection.
Background fixtures assert they never activate. Unit tests cover
invariant conversion, actual COM sentinel identity, unknown names, and
provider-error propagation; CLI tests pin string serialization and
scrubbed errors.
Pre-human-review gate is pending automated review and CI; this PR is not
being merged by the implementation session.
---------
Co-authored-by: Nikola Metulev <711864+nmetulev@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Zach Teutsch <88554871+zateutsch@users.noreply.github.com>
Summary
ui inspect --jsonwindows andui status --jsonelementtoui get-property --jsonwhile preservingelementIdand string-valuedpropertiestypefieldValidation
ui status --jsonagainst a per-monitor-aware window: 120 DPI / 1.25 scalehwnd: 0and omitted DPI fieldsscripts/build-cli.ps1 -SkipTests: passed, including NativeAOT publish, npm package, NuGet package, and MSIX packagingscripts/validate-plugin-package.ps1: passedscripts/build-cli.ps1: compilation/npm/analyzer phases passed; test phase had three unrelated environment-sensitive failures in existing crash-dump/recording testsMixed-monitor placement could not be exercised on the available desktop; distinct per-window DPI/awareness values are covered by command tests.
Fixes #820