Skip to content

Commit d77519d

Browse files
authored
docs(adr): correct ADR 0031 pointer event delivery evidence (#3245)
A bare CGEventPostToPid named no window. Re-measured against a background AppKit, SwiftUI and WKWebView fixture: with the window number (field 51) and field 58 or a window-local location, the public call delivers click, double-click and secondary click without activation. SkyLight posting adds nothing; drags need focus-without-raise. Pointer actions keep no event fallback until an event path verifies its outcome (#3213).
1 parent c53e88b commit d77519d

1 file changed

Lines changed: 19 additions & 8 deletions

File tree

‎docs/adr/0031-macos-native-app-backend.md‎

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
## Status
44

5-
Accepted (2026-10-03). Opt-in; XCTest stays the default app-session backend.
5+
Accepted (2026-10-03). Opt-in; XCTest stays the default app-session backend. Evidence gap: pointer
6+
event delivery was re-measured on 2026-10-05 (#3213); Electron, Catalyst, and wheel delivery with
7+
window fields are unmeasured.
68

79
## Rules at a glance
810

@@ -47,10 +49,17 @@ windows.
4749

4850
**Pointer delivery.** Events posted to a process with `CGEventPostToPid` were measured against
4951
Calculator (SwiftUI): mouse events were dropped whether the app was frontmost or in the background,
50-
and wheel events were dropped in the background, while every post reported success. Keyboard events
51-
were accepted by Calculator and by Electron apps in the background. A fallback that cannot be
52-
observed to work would turn "nothing happened" into success, so pointer actions have no event
53-
fallback.
52+
and wheel events were dropped in the background, while every post reported success. Those events
53+
named no window. A re-measurement against a background AppKit, SwiftUI, and WKWebView fixture
54+
(#3213) found the window fields decide delivery, not the API: with the target window number in
55+
event field 51 and either field 58 or a window-local location (`CGEventSetWindowLocation`), the
56+
public `CGEventPostToPid` delivered click, double-click (click state in field 1), and secondary
57+
click to all three, 54 of 54, without activating the app or changing the frontmost app. Fields 51
58+
and 58 are undocumented. Drags and WKWebView button clicks were delivered only after a private
59+
focus-without-raise activation. Keyboard events were accepted by Calculator and by Electron apps in
60+
the background. A dropped post still reports success, and a fallback that cannot be observed to
61+
work would turn "nothing happened" into success, so pointer actions have no event fallback until
62+
an event path verifies its outcome.
5463

5564
**Target resolution.** The helper hit-tests inside the session app (other apps' windows above it do
5665
not answer) and walks at most four ancestors for a text input or a pressable control role.
@@ -76,9 +85,11 @@ pointer would need a long-lived helper. `AGENT_DEVICE_MACOS_GHOST_CURSOR=0` disa
7685

7786
- **Suppressing Automation Mode.** `automationmodetool` removes the authentication prompt, not the
7887
overlay, and the runner still owns the pointer.
79-
- **Private SkyLight event delivery** (`SLEventPostToPid`, focus-without-raise). It would cover
80-
pointer-only controls, but it is private API that can break with any macOS release. Revisit only
81-
with evidence of apps the accessibility path cannot drive.
88+
- **Private SkyLight event delivery** (`SLEventPostToPid`, focus-without-raise). Posting through
89+
`SLEventPostToPid` delivered nothing the same fields did not deliver through the public
90+
`CGEventPostToPid` (#3213). Focus-without-raise (`SLPSPostEventRecordTo`) made the target
91+
AppKit-active while the frontmost app kept the user's keystrokes, and it was the only way drags
92+
reached the fixture, but it is private API that can break with any macOS release.
8293
- **Native by default.** The accessibility path cannot express drags, holds, or double-clicks, and
8394
apps with sparse accessibility trees still need the runner. Defaults change only with coverage
8495
evidence across app frameworks.

0 commit comments

Comments
 (0)