Summary
Fixes #2083.
An in-flight smooth scroll can keep moving a target after its center has been resolved, so a native click can report success while the trusted press lands elsewhere. Requesting an instant scroll alone was insufficient in local reproduction; geometry sampling without cancelling the existing animation also timed out.
- Cancel ancestor and viewport scrolling at their current offsets before actionable click preparation. Measure fresh geometry, center offscreen targets, and retain nearest alignment for targets already intersecting the viewport. Sample the actionable center, target CDP session, and owning-frame viewport offset across rendering opportunities.
- Require two consecutive comparisons within 0.5 CSS pixels per axis, bounded by a two-second preparation deadline. Use the latest center and validated offset without a subsequent offset lookup.
- Fail without pressing when preparation cannot complete. On outside-viewport preparation failures, repeat cancellation and explicitly center the target within the original deadline before restarting sampling; this handles partially visible targets whose centers remain unreachable. Retain existing terminal errors for blockers, invalid/stale targets, and disabled/hidden elements. Never retry after mouse input has been dispatched.
- Use a bounded timer fallback when requestAnimationFrame is unavailable and surface renderer evaluation exceptions.
- Preserve existing press/release and dialog handling, and the check/uncheck non-actionable fallback. Make same-process iframe fallback scrolling explicitly instant.
- Update CLI help, MCP descriptions/parity coverage, README, skill guidance, and command documentation.
Related work
Related to #1985, which stabilizes centers for click/hover/tap. Issue #2083 explicitly reports residual misses with that branch during existing smooth scrolling. This contribution is scoped to native click/dblclick preparation and combines an instant cancellation request with bounded geometry sampling. It does not attempt to replace that PR's broader hover/tap scope or introduce post-press retries. Happy to coordinate or split the changes if maintainers prefer.
Regression coverage
- Document/nested-element scrolling × CSS/XPath/discovered snapshot refs × normal dispatch/direct preparation (12 combinations).
- Direct preparation followed by a 200 ms delayed trusted press; verify center/scroll stability and exactly one target activation.
- Selected same-origin scrolling iframe through CSS, XPath, and refs.
- Fixed headers, footers, and sidebars: above/below/left/right offscreen targets plus a stationary visible target through CSS, XPath, and refs (15 combinations). Visible targets retain their scroll position.
- Deferred scrolling leaves the target intersecting the viewport but its center outside it (three selector variants, without overlays).
- First click starts reverse smooth scrolling away from the target, followed immediately by a second click after completing the form (document/nested scroller × CSS/XPath/refs × five repetitions, 30 sequences).
- Unstable target timeout without input, followed by successful recovery.
- Missing/throwing requestAnimationFrame; delayed mock CDP responses; timeout followed by a late response and a usable connection with no mouse press.
Validation
Local environment: Chrome for Testing 156.0.8078.47, Ubuntu 26.04.1 under WSL2, Rust 1.99.1, Node 24.22.1, pnpm 11.1.3. Browser test threads use RUST_MIN_STACK=16777216.
- Full non-ignored Cargo suite: 1,486 main-binary tests and 23 CLI integration tests pass.
- Selected browser checks: 15 Chrome E2Es pass, followed by 21 additional selected regression executions (one overlaps the earlier selection).
- Review follow-up rerun: all nine click-prefixed E2Es pass, including fixed-edge, deferred viewport recovery, and 30 consecutive-click sequences. The full non-ignored suite passes again (1,486 main tests plus 23 integrations); rustfmt and strict production Clippy pass. All-target Clippy also passes with only the same three pre-existing test lints allowed.
- Six release-binary input contract scenarios pass, including double/right/middle/human clicks and dialog release/recovery.
- Docs: 54 tests, 439 route checks, type-check, and production build pass.
- Strict production Clippy passes. Strict all-target Clippy reports three pre-existing test-only lints under this Rust version: for_unbounded_range, result_large_err, and chunks_exact_to_as_chunks. All-target checking passes when only those three are allowed; no source suppressions were added.
Separate local stress measurements using the issue-derived fixture, four isolated browser sessions, and trusted-event/application-state assertions:
| Build | Trials | Silent wrong-target clicks | Explicit click/setup errors |
|---|---|---|---|
| Saved unpatched debug build | 400 | 4 | 0 |
| Review-follow-up patched debug build | 400 | 0 | 0 |
| Review-follow-up patched release build | 800 | 0 | 0 |
An additional serial single-cross-origin iframe fixture passes 60/60 snapshot-ref clicks with exactly one trusted pointerdown and activation per trial. These external harness measurements are local evidence, not upstream CI results or performance benchmarks; the portable regression tests are included in this PR.
The stress workload and six input contracts were rerun against the repaired release binary (SHA-256 04ba0d8d70f5d12c9f5ccaa94ef416eda3bd2821ab6217383ebd8bee1acb30ac). The final debug workload overlapped release compilation, so no comparative timing claim is made. Alignment-only follow-up candidates passed focused tests but failed this original workload; they were rejected before publication. The accepted follow-up includes explicit cancellation and viewport-center recovery.
Scope and limits
This addresses the reproduced smooth-scroll race, not arbitrary DOM replacement, hover-induced layout changes between move and press, or universal dispatch-time immobility. Sampling tolerance permits sub-threshold drift; element identity is not pinned across DOM replacement.
Nested-cross-origin smooth-scroll click coverage remains blocked by snapshot discovery omitting the leaf-button ref before click dispatch. The isolated fixture fails the same way on both the saved unpatched and final patched builds; it is not counted as passing and no unrelated discovery repair is included. A preparation timeout may also retain a temporary remote object until its execution context is destroyed. Windows-native execution, other engines/providers, and the complete ignored E2E suite are not claimed as validated.
Activity
6The fixed-edge review finding is addressed in 57062a7, with the regression and validation details in the resolved review thread and updated PR description.
The remaining red deployment statuses are Vercel Labs authorization gates (both deployment links point to git/authorize), rather than build/test failures. The latest GitHub Actions CI run 40218727533 also has conclusion action_required pending approval of the contributor workflow.
Could a maintainer please review/approve the contributor CI run and authorize the previews if appropriate? I have left the workflow and deployment security configuration unchanged. Local validation passes: 1,509 non-ignored Rust tests, nine click E2Es, formatting/production lint, 1,200 stress trials without click/setup errors or silent misses, six input contracts, and 60 single-cross-origin frame trials. The automated Vercel re-review of the new commit is still in progress.
@tejas-rathi05 is attempting to deploy a commit to the Vercel Labs Team on Vercel.
A member of the Team first needs to authorize it.