Skip to content

Add ui watch command for UI event streaming#602

Open
yeelam-gordon wants to merge 1 commit into
mainfrom
yeelam/ui-watch
Open

Add ui watch command for UI event streaming#602
yeelam-gordon wants to merge 1 commit into
mainfrom
yeelam/ui-watch

Conversation

@yeelam-gordon

Copy link
Copy Markdown

Summary

  • add winapp ui watch to listen for UIA/WinEvent notifications from a running app
  • support event filtering, bounded capture (--duration-sec, --max-events), and JSON/NDJSON output
  • regenerate the CLI schema and UI automation skill/docs

Why

This adds a first-class way to observe focus, selection, window, and related UI events instead of polling for every state transition.

Adds a new `winapp ui watch` command that listens for UIA / WinEvent
notifications from a running app and streams them as they occur, with JSON/NDJSON
output for tooling and bounded capture via event filters, duration, and max-event
limits.

Also updates the generated CLI schema and UI automation skill/docs to surface the
new command.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 6, 2026 03:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new winapp ui watch subcommand that streams live UI events (focus, window open/close, invoke, selection, property/text/structure changes, notifications, live regions) from a running app, instead of forcing callers to poll for state transitions. A dedicated STA thread creates its own CUIAutomation8, registers UIA automation-event/property/structure/focus handlers plus a process-scoped SetWinEventHook for window lifecycle, and pumps messages until a duration/--max-events limit or Ctrl+C. Output is human-readable by default or NDJSON (one compact object per line) plus a trailing summary line with --json, with an optional -o tee to file. The change is wired into DI/command registration and the auto-generated + hand-written docs surfaces are (partly) updated.

Changes:

  • New UiWatchCommand + IUiEventWatcher/UiEventWatcher (COM CCW sinks + WinEvent hook) and a WindowLifecycleCoalescer to collapse paired CREATE+SHOW / DESTROY+HIDE events.
  • Event/property validation, element-scope-requires-window fail-fast, selector re-resolution, NDJSON JSON context, and unit tests for the command and coalescer.
  • Regenerated cli-schema.json/SKILL.md and updated docs/usage.md + skill fragments (but not docs/ui-automation.md/winapp.agent.md).

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/winapp-CLI/WinApp.Cli/Commands/UiWatchCommand.cs New command: options, validation, streaming output, summary.
src/winapp-CLI/WinApp.Cli/Services/UiEventWatcher.cs Real watcher: STA pump, UIA handlers, WinEvent hook, teardown.
src/winapp-CLI/WinApp.Cli/Services/IUiEventWatcher.cs Contract, event-name constants, request/outcome models.
src/winapp-CLI/WinApp.Cli/Services/WindowLifecycleCoalescer.cs De-dupes paired window lifecycle WinEvents.
src/winapp-CLI/WinApp.Cli/Helpers/UiWatchJsonContext.cs Compact source-gen JSON context + event/summary DTOs.
src/winapp-CLI/WinApp.Cli/NativeMethods.txt CsWin32 declarations for event handlers/WinEvent/message pump.
src/winapp-CLI/WinApp.Cli/Helpers/HostBuilderExtensions.cs Registers watcher service + command handler.
src/winapp-CLI/WinApp.Cli/Commands/UiCommand.cs Adds watch as a ui subcommand.
src/winapp-CLI/WinApp.Cli.Tests/* New command + coalescer tests and FakeUiEventWatcher.
scripts/generate-llm-docs.ps1 Adds ui watch to the skill command map.
docs/usage.md, docs/fragments/.../ui-automation.md, .github/plugin/.../SKILL.md, .claude/.../SKILL.md, docs/cli-schema.json Documentation and generated-schema updates for the new command.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/usage.md
Comment on lines +1100 to +1102
#### `winapp ui watch`

Stream live UIA / WinEvent notifications from a running app as they occur. With `--json`, emits NDJSON (one compact JSON object per event line) followed by a summary line.
Comment on lines +112 to +115
PostQuitMessage
GetCurrentThreadId
MSG
GetAncestor

if (!string.IsNullOrWhiteSpace(outputPath))
{
logFile = new StreamWriter(outputPath, append: false, Encoding.UTF8);
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Build Metrics Report

Binary Sizes

Artifact Baseline Current Delta
CLI (ARM64) 31.71 MB 31.80 MB 📈 +90.5 KB (+0.28%)
CLI (x64) 32.04 MB 32.12 MB 📈 +88.0 KB (+0.27%)
MSIX (ARM64) 13.30 MB 13.35 MB 📈 +45.3 KB (+0.33%)
MSIX (x64) 14.15 MB 14.18 MB 📈 +28.2 KB (+0.19%)
NPM Package 27.77 MB 27.84 MB 📈 +75.6 KB (+0.27%)
NuGet Package 27.79 MB 27.87 MB 📈 +78.6 KB (+0.28%)
VS Code Extension 20.60 MB 20.67 MB 📈 +73.2 KB (+0.35%)

Test Results

1562 passed, 1 skipped out of 1563 tests in 427.6s (+17 tests, -50.4s vs. baseline)

Test Coverage

18.2% line coverage, 37.1% branch coverage · ✅ no change vs. baseline

CLI Startup Time

40ms median (x64, winapp --version) · ✅ no change vs. baseline


Updated 2026-07-06 03:16:58 UTC · commit 10d55d3 · workflow run

@nmetulev nmetulev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI-generated review — produced by GitHub Copilot CLI at a maintainer's request. Every finding comes from an automated, multi-dimensional review plus a hands-on build-and-test of this branch in an isolated worktree. Treat file:line references as pointers to verify, not gospel. Nothing was pushed. Posting as a comment (no approve / request-changes).

Verdict: 🟡 SHIP-WITH-CHANGES — Good architecture (dedicated STA pump, correct COM handler teardown), but a buffering bug currently defeats the streaming use case this command exists for.

What I actually ran

Built (~33s, 0 warnings); 17 unit tests pass. Watched Notepad while driving it from a second shell:

  • --duration-sec and --max-events bounding stop cleanly; NDJSON is one event per line + a summary object. ✅
  • Reading the -o file mid-run: empty until the process exits (C01) — real-time streaming doesn't work.
  • Opening one WinUI3 Notepad emitted 25+ window-open events for internal panes (DesktopWindowXamlSource, Default IME, CicMarshalWnd, …); only 1 was the real window (H01).
  • Focus-event latency <100ms; timestamps ISO-8601 UTC.

Must-fix (Critical / High)

  • C01 — no flush after each event (UiWatchCommand.cs:173). StreamWriter defaults to AutoFlush = false, so neither stdout nor the -o file streams until the process exits. This is the headline "agents read events live" scenario, and it's currently broken. One-line fix (Flush() after each write).
  • H01 — window-open/close filter too broad (UiEventWatcher.cs:225-230): 20–30 internal-pane events per window open. Add a top-level-window filter (parent == desktop, or WS_CAPTION/WS_OVERLAPPEDWINDOW).
  • H02 — event selector isn't a winapp slug. It's the raw AutomationId (UiEventWatcher.cs:314-319) and is frequently null, so event.element.selector can't be piped into ui invoke/set-value. The unit-test fakes use slug-format strings, which hides this. Route through SelectorService.

Medium / Low

  • M01 RequestStop() is a non-atomic check-then-set. M02 the s_active static is unguarded against a concurrent WatchAsync. M03 --output description is copy-pasted from screenshot. M04 npm winapp-commands.ts is missing uiWatch().
  • L: no integration test for the real watcher (all 17 use FakeUiEventWatcher); the summary line lacks a type discriminant; ui-json-envelope.md isn't updated; the detail field format varies across event types.

Fit & recommendation

Push notifications are genuinely more efficient than polling. But this is a long-running streaming command inside an otherwise request/response CLI, and it overlaps the existing wait-for verb. The agent workflow (fork a subprocess, drive the app from another, parse after exit) is awkward, and the flush bug amplifies it. I'd gate this on fixing streaming (C01), then decide whether the streaming model earns its complexity versus investing in wait-for.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants