Skip to content

cua-driver: detect & suppress background click side-effects - #1477

Merged
ddupont808 merged 1 commit into
mainfrom
cua-driver/background-side-effects-detection
May 11, 2026
Merged

cua-driver: detect & suppress background click side-effects#1477
ddupont808 merged 1 commit into
mainfrom
cua-driver/background-side-effects-detection

Conversation

@ddupont808

@ddupont808 ddupont808 commented May 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • WindowChangeDetector (new) — snapshot/detect/suppress pipeline wired into both AX-indexed and pixel-click paths in ClickTool. When a background click causes a cross-app side-effect (e.g. clicking "Browse UTM Gallery" in UTM opens a Safari window), the detector announces it to the agent via 🪟 Action opened new window(s): Safari ("Gallery | UTM"). in the tool result.
  • SystemFocusStealPreventer (extended) — adds wildcard suppression (targetPid = 0) that fires for any app activation except restoreTo, covering side-effect apps whose pid is unknown at arm time.
  • ClickTool (wired) — both code paths snapshot before and detect after the action.

The foreground-restore machinery is transparent — the agent only sees the new-window notice, not the re-raise.

How it works

  1. snapshot() captures the window set + frontmost pid, then arms the wildcard suppressor so any app that self-activates during the action is squashed before the first compositor frame.
  2. The AX action fires (inside the existing FocusGuard.withFocusSuppressed envelope).
  3. detectChanges() polls 1s for new windows or frontmost changes, then ends the suppressor.
  4. If new windows appeared, reRaiseForeground() is called as a belt-and-suspenders belt and the suffix is appended to the result.

Test plan

  • test_click_opens_new_window.py::TestBrowseUTMGalleryUXGuard
    • FocusMonitorApp is the simulated user foreground (ux_guard sentinel)
    • UTM launched in background; agent clicks "Browse UTM Gallery"
    • ✅ Safari window appears
    • 🪟 notice present in click result, naming Safari
    • ✅ FocusMonitorApp remains frontmost after the click
    • ✅ Focus-loss count ≤ 1 (one unavoidable reactive tick — suppressor fires after didActivateApplicationNotification, not before)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added window change detection to identify and manage cross-app side effects triggered by user interactions.
    • Improved focus management to preserve foreground app status when clicks trigger actions in background applications.
  • Tests

    • Added integration test validating proper focus preservation during cross-app interactions.

Review Change Stack

…ow / focus steal)

Adds WindowChangeDetector — a snapshot/detect/suppress pipeline wired
into ClickTool's AX and pixel paths that handles the case where a
background click causes a cross-app side-effect (e.g. clicking
"Browse UTM Gallery" opens a Safari window and briefly activates it).

## What changes

### WindowChangeDetector (new)
- `snapshot()` captures window set + frontmost pid, then immediately arms
  a wildcard `SystemFocusStealPreventer` suppression (targetPid = 0) so
  any app that self-activates during the action is squashed before the
  first compositor frame.
- `detectChanges()` polls for new layer-0 windows or frontmost changes
  during the action window, then ends the suppressor.
- `resultSuffix` appends `🪟 Action opened new window(s): <App> ("<title>").`
  to the tool result so the background agent is aware of side-effect
  windows — without surfacing the foreground-restore machinery.
- `needsRestore` triggers on new windows even when foregroundChanged is
  false (suppressor may have prevented the OS-level steal before it was
  observable in the poll loop).

### SystemFocusStealPreventer (extended)
- `handleActivation` now also matches wildcard entries (targetPid = 0),
  firing for any pid other than `restoreTo`. Covers side-effect apps
  whose pid is unknown at arm time (e.g. Safari launched by UTM).

### ClickTool (wired)
- Both AX-indexed and pixel-click paths call `snapshot()` before the
  action and `detectChanges()` / `reRaiseForeground()` after.

## Test
`test_click_opens_new_window.py::TestBrowseUTMGalleryUXGuard`
- FocusMonitorApp is the simulated user foreground (ux_guard sentinel).
- UTM is launched in the background; the agent clicks "Browse UTM Gallery".
- Asserts: Safari window appears, 🪟 notice in result, FocusMonitorApp
  remains frontmost, focus-loss count ≤ 1 (one unavoidable reactive tick).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Ignored Ignored May 11, 2026 11:35pm

Request Review

@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR adds a new side-effect detection and foreground-app restoration mechanism. When a UI click causes cross-app foreground changes or new windows to appear, the driver automatically detects these changes and restores the original foreground app. A wildcard focus-suppression mode prevents affected side-effect apps from stealing focus during the restoration window.

Changes

Foreground Guard for Click Side-Effects

Layer / File(s) Summary
WindowChangeDetector Data Model
libs/cua-driver/Sources/CuaDriverServer/Tools/WindowChangeDetector.swift
New types: WindowEvent (window ID, PID, app name, title), Snapshot (pre-action window IDs, front PID, suppression handle), and Changes (new windows, foreground change flag, computed needsRestore and resultSuffix).
Wildcard Focus Suppression
libs/cua-driver/Sources/CuaDriverCore/Focus/SystemFocusStealPreventer.swift
Dispatcher now matches targetPid == 0 as a wildcard suppression rule; filters out cases where the activated PID equals the candidate's restoreTo PID to prevent self-restoration.
Side-Effect Detection and Restoration
libs/cua-driver/Sources/CuaDriverServer/Tools/WindowChangeDetector.swift
snapshot() captures pre-action window state and arms wildcard suppression. detectChanges() polls for newly appeared windows and foreground changes, returning early on detection. reRaiseForeground() re-activates the original foreground app. currentWindowIds() helper collects visible layer-0 window IDs.
ClickTool Click-Action Integration
libs/cua-driver/Sources/CuaDriverServer/Tools/ClickTool.swift
Both element-indexed and pixel click paths now capture pre-action snapshot, detect post-action window/foreground changes, conditionally restore the original foreground app when needed, and append changes.resultSuffix to the success response text.
Integration Test: UX Guard Validation
libs/cua-driver/Tests/integration/test_click_opens_new_window.py
New end-to-end test validates the background click UX guard: launches FocusMonitorApp in foreground, starts UTM in background, clicks Browse Gallery control (opens Safari), verifies result text includes new window notice, confirms FocusMonitorApp remains frontmost, and asserts foreground focus loss is minimal (≤1 event). Includes helpers for app lifecycle, process management, window polling, and focus-loss tracking.

Sequence Diagram

sequenceDiagram
  participant Action as User Action
  participant ClickTool
  participant WCD as WindowChangeDetector
  participant Suppression as Focus Suppression
  participant App as Original Foreground App
  
  Action->>ClickTool: click UI element
  ClickTool->>WCD: snapshot()
  WCD->>WCD: capture window IDs & front PID
  WCD->>Suppression: beginSuppression(targetPid: 0)
  WCD-->>ClickTool: Snapshot armed
  
  ClickTool->>ClickTool: execute AX click action
  Note over ClickTool: side-effect: Safari opens
  
  ClickTool->>WCD: detectChanges(snapshot)
  loop poll until deadline
    WCD->>WCD: detect new windows
    WCD->>WCD: check foreground change
  end
  WCD-->>ClickTool: Changes { newWindows: [Safari], needsRestore: true }
  
  alt needsRestore
    ClickTool->>WCD: reRaiseForeground(pid)
    WCD->>App: activate()
  end
  
  WCD->>Suppression: endSuppression()
  ClickTool-->>Action: success + "🪟 new Safari window"
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • trycua/cua#1375: Modifies ClickTool.swift and adds focus-monitoring tests with background-focus protections.
  • trycua/cua#1386: Modifies ClickTool.swift click dispatch behavior (adds AXEnabled pre-check for ghost-click fix).

Poem

🐰 Hops with delight at this fine new guard,
Clicks may now roam, yet the foreground stays hard,
Safari may bloom when a gallery beckons,
But focus returns—the detector reckons!
A snapshot, a change, a restoration so clean,
The finest UX guarding I've ever seen. ✨🪟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: detecting and suppressing background click side-effects. This directly reflects the core additions of WindowChangeDetector and SystemFocusStealPreventer wildcard matching.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cua-driver/background-side-effects-detection

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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.

1 participant