fix(ios): open the sole-screen gesture route on a full-screen iPad app
Summary
Taps, long presses, scroll/swipe/gesture-viewport, and the back fallback fail on an iPad simulator when the app's accessibility tree is too deep for XCTest: the sole-screen synthesized-gesture route accepted only an iPhone, though iPad hosts run the same hostile trees. RunnerFullScreenEvidenceFromForeground now decides the proof in-process by comparing the AXRuntime primary app's frame against the system app's frame — the same rule the snapshot bridge refuses a windowed capture on (app-window-not-full-screen, ADR 0004); neither read walks the app's tree. The comparison, tolerance, foreground-element protocol, primary-pid check, and the whole frame-usability test live in one canonical header, apple/snapshot-bridge/ForegroundApplicationGeometry.h, which the bridge includes directly and the runner's UITests target compiles through its HEADER_SEARCH_PATHS (resolved in the checkout and in the installed package; check:packaged-runner-swift guards the packaged search path, and the header is fingerprinted by both the bridge cache identity and the runner source fingerprint). RunnerResolveSoleScreen keeps one owner of the rule, early-returns on the phone idiom before probing, and opens on iPad only on fills-screen; windowed/Stage Manager or unknown keeps requiring a window. The Swift side reads the same single owner (RunnerResolveSoleScreen in soleScreenReferenceFrame), reusing #3920's soleDisplayFrame for the frame itself, so screen-count and screen-space rotation stay #3920's and the device rule stays the bridge's. The back fallback point derives from the screen's top safe-area inset via one formula for every route. The nightly workflow gains an iPad matrix leg inside the existing job (no parallel job).
Validation
Head 36028a1f (includes origin/main at 45125d7, incl. #3920).
- Shared-header round: the two duplicated rule blocks and the byte-parity guard are deleted; both consumers compile the canonical header. The installed-package build is proven:
xcodebuild build-for-testingof the packaged tree in a scratch install layout succeeds, and fails with'ForegroundApplicationGeometry.h' file not foundwhen the shipped header is removed. The header enters both build identities (bridge source list + runner fingerprint, with a packaged-layout test). - Runner unit tests (unit-test variant, own derived path): the nightly iPad selection passes on a dedicated simulator, log shows
AGENT_DEVICE_RUNNER_FULL_SCREEN_EVIDENCE result=fills-screenand the SpringBoard negativeresult=not-primary-app. ThetestSoleScreenDisplayIDIsNamedOnlyForAnAppThatFillsItsScreendevice test asserts the windowed/foreign-owner refusal (not-primary-app→ no sole screen) executably. - Post-merge live re-check at
36028a1f(Release fixture artifact + JS repack, own sims/state dirs): iPad heavy feed —press id="heavy-post-0-like"→Liked posts: 1,press 417 640,longpress 200 560 800→Last action: held post 1,scroll down --pixels 500,swipe 417 900 417 400→Flung,back; the session runner.log carries 10×SYNTHESIZED_SOLE_SCREEN_ROUTE resolved={{0, 0}, {834, 1210}}. Landscape-left: the same synthesis completes and the route resolves the rotated frame{{0, 0}, {1210, 834}}; rotating back to portrait recovers{834, 1210}without relaunch. iPhone heavy feed —press id="heavy-post-0-like"→Liked posts: 1andbackwork;AGENT_DEVICE_RUNNER_BACK_FALLBACK_TOP_INSET read=62.0 windows=1; zero probe lines on the phone. pnpm check:xctest-selection(501 declared; host 358, PR 5, nightly 493, nightly-iPad 3, 0 dark) and the darwin-Werrorbridge compile pass.pnpm check:affected --run --head 36028a1fpassed all runnable checks withexactCommit: true. PR CI: 19/19 pass.- XCTest Nightly dispatched on the final head — run https://github.com/callstack/agent-device/actions/runs/40242364443: both legs success; the iPad leg runs the 3-test selection on
iPad Pro 11-inch (M5)(log quotes in the linked comment above), and the reporter assertsExecuted 3 test(s); the source reaches 3 on this lane.
Base/head comparison for the live table is the pre-PR behavior measured at f4944b5 (see the 2026-10-25 review replies): every heavy-feed gesture timed out or refused; at head each completes in ~1s.