fix(daemon): consume the post-gesture record when its capture aborts
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 aTimeoutErrormid-loop leavessession.postGestureStabilizationundefined. Removing thefinallyfails it. - Live, iPhone 17 Pro / iOS 26.5 simulator, Settings,
--json --debug:scroll down 0.75, thenwait stable 100 400twice (deadline shorter than the stabilization loop), thenwait stabletwice. First short wait: timeout,captureStalled: true,captures: 0. Second short wait:captures: 1, so the record was consumed. Both normal waits settled withcaptures: 3and nopost_gesture_*diagnostic. Same recipe on main 3660c7fa4: second short wait stillcaptures: 0and the first normal wait logspost_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 stableafter 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.