fix(selectors): refuse a zero-area press target instead of tapping its origin

AI
7h ago
+78-1

Summary

A selector that matched a 0×0 node (SpringBoard keeps page-1 icons that way on page 2) resolved to the rect's origin, so press tapped (0, 0) and reported success. resolveInteractionTouchPoint now returns invalid for a target with zero width or zero height. That surfaces as the existing target_bounds_invalid refusal with dispatched: 'no'. normalizeRect is unchanged, because other readers use zero-size rects.

This applies to every command that aims through resolveNodeTouchPoint: press/click, and fill or longpress on a selector or ref.

3 files: the resolver, its unit test (zero width, zero height, both, plus a 1×1 target that still resolves), and a command-level press case that refuses and records no tap.

Closes #3841

Validation

  • Tested SHA 5bbc20cd3 (base c48d27a81).
  • pnpm check:affected --run --head 5bbc20cd3: passed, exactCommit=true.
  • Both new tests fail on base (resolved instead of invalid; "Missing expected rejection") and pass at head.
  • Live, at 5bbc20cd3: iPhone 17 Pro Max simulator, iOS 26.5, open com.apple.springboard, home screen on "Page 2 of 2", snapshotQuality.backend: tree.
    • snapshot shows the page-1 icons (Calendar, Photos, Settings, …) at 0,0 0x0, hittable: false. This layout has no Clock, so Settings stands in.
    • press 'label="Settings"' → exit 1, COMMAND_FAILED, reason: target_bounds_invalid, dispatched: no, "Selector label="Settings" resolved to invalid bounds". The request log shows only appState, appState, snapshot sent to the runner, and no tap.
    • press 'label="Watch"' (page 2, 72x94) → exit 0, "Tapped label="Watch" (170, 139)" (its center) and opened Watch.

🤖 Generated with Claude Code