fix(daemon): consume the post-gesture record when its capture aborts

Merged
OK
2mo ago
+64-26

Summary

Refs #2885.

wait stable bounds each capture with runWithinWaitDeadline, which aborts the capture's signal with a TimeoutError at the wait's deadline. After a scroll, that capture routes through capturePostGestureStabilizedResult, which runs the post-gesture stability loop (up to the 3.5 s distrust cap, each poll a full simulator capture) inside what the stable loop counts as one capture. When the wait deadline aborted the loop mid-poll, the throw skipped clearPostGestureStabilization, so the pending record stayed armed and every later capture on the session paid the same loop again: the next wait stable also stalled with captures: 0, and the one after it, which is the 15-of-16 pattern in the issue.

The owner now clears the record in finally: entering the loop consumes it, on settle, timeout and abort alike. No other behavior changes; the stalled timeout still reports captureStalled: true and captures.

Touched: 1 source file, 1 test file.

Validation

Tested commit 206d61c3a (654bb7f8a plus a comment rewording).

  • pnpm check:affected --run: passed.
  • New test in post-gesture-stabilization.test.ts: a second capture that throws a TimeoutError mid-loop leaves session.postGestureStabilization undefined. Removing the finally fails it.
  • Live, iPhone 17 Pro / iOS 26.5 simulator, Settings, --json --debug: scroll down 0.75, then wait stable 100 400 twice (deadline shorter than the stabilization loop), then wait stable twice. First short wait: timeout, captureStalled: true, captures: 0. Second short wait: captures: 1, so the record was consumed. Both normal waits settled with captures: 3 and no post_gesture_* diagnostic. Same recipe on main 3660c7fa4: second short wait still captures: 0 and the first normal wait logs post_gesture_snapshot_stabilized, meaning it re-entered the loop. Full tables in the PR comments.
  • Not addressed here: the stable loop still charges one nested stabilization loop to its first capture, so a slow enough host can still stall the first wait stable after a gesture. That is a second seam (the stable loop is itself a settle observer and could skip the nested loop); this PR stops the stall from repeating.