Skip to content

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

Open
airowe wants to merge 1 commit into
callstack:mainfrom
airowe:fix/press-zero-area-target
Open

airowe wants to merge 1 commit into
callstack:mainfrom
airowe:fix/press-zero-area-target

Conversation

@airowe

@airowe airowe commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

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

View guided diff

…s origin

A 0x0 node (SpringBoard keeps page-1 icons that way on page 2) passed
normalizeRect and resolved to its origin, so `press` tapped there and
reported success. resolveInteractionTouchPoint now answers `invalid` for a
target with zero width or height, which surfaces as the existing
target_bounds_invalid refusal with dispatched: 'no'.

Closes callstack#3841

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thymikee

Copy link
Copy Markdown
Member

Thanks for the fix. At 5bbc20c the code change looks right, but it needs live device evidence before it can merge.

This PR changes which points a live press, click, fill or longpress dispatches on iOS: a 0x0 SpringBoard icon now refuses instead of tapping the origin. That is an interaction-dispatch change, so docs/agents/pull-requests.md asks for live proof. The only command-level test uses a fake backend, and the PR says the #1296 SpringBoard route was not re-run. Nothing yet shows that a real capture reports page-2 icons as 0x0 through this route, or that a positive-area page-2 icon still taps. Please run the head build on a simulator or device (pnpm build && pnpm clean:daemon) with the iOS home screen on page 2. snapshot -i --json should show the Clock icon with a 0x0 rect and hittable false. press 'label="Clock"' should exit non-zero with COMMAND_FAILED, reason target_bounds_invalid, dispatched no, and no tap logged. Then press a page-2 icon with a positive rect and show that it still succeeds. Please record the commands, the device, SHA 5bbc20c and the outputs in the PR Validation section.

Not blocking, and fine to take or leave: find <locator> focus and find <locator> type in find.ts still focus a zero-area match at its origin, which is outside #3841 and could fold into #3734 by routing that seam through resolveNodeTouchPoint; the new private hasArea in interaction-touch-point.ts repeats a width > 0 && height > 0 check that exists inline in several places, so a shared helper next to rectArea in kernel/rect.ts is optional.

CI is green: 11 checks, none failing. None of them runs a live device lane on this route. I could not run the new tests or the base-versus-head check here, so I judged the regression test from the base code and the passing CI. There are no conflicts. Merge needs the SpringBoard page-2 evidence recorded in the PR body at the head SHA.

@airowe

airowe commented Oct 24, 2026

Copy link
Copy Markdown
Contributor Author

Live evidence at 5bbc20cd3 is now in the Validation section: iPhone 17 Pro Max simulator, iOS 26.5, SpringBoard on page 2.

  • Page-1 icons report 0x0 with hittable: false. This layout has no Clock, so I used Settings.
  • press 'label="Settings"' exits 1 with target_bounds_invalid and dispatched: no. The runner received no tap, only appState ×2 and snapshot.
  • press 'label="Watch"' (page 2, 72x94) taps its center (170, 139) and opens Watch.

I left the two non-blocking points out of this PR. find … focus/type can go with #3734. The shared positive-area helper only makes sense if hasArea gets a second caller.

🤖 Addressed by Claude Code

@thymikee

Copy link
Copy Markdown
Member

Thanks, that covers it at 5bbc20c. The page-1 icon on page 2 reports 0x0 with hittable false, the press on it is refused with target_bounds_invalid and dispatched: no with no tap reaching the runner, and a positive-area page-2 icon still taps its center and opens the app. Nothing else blocks this from my side, and leaving find … focus/type for #3734 is fine.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 24, 2026

This branch has not been deployed

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

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

press/click: refuse a zero-area target instead of tapping its origin

2 participants