You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
iOS selector click can still tap a stale point after layout shifts the post-open settle does not cover #3367
#3356 settles the next screen read after an iOS open that returns timing.postOpenObservation: "unobservable". Three cases from #3354 are still open. In each one a selector click can tap a stale point and report success:
The layout moves between the click's own pre-tap capture and its tap, at any time and not only after an open.
The layout moves after an observable open. observable means the tree was readable, not that it was at rest.
The post-open read never settles. The click gets postGestureOutcome: unsettled and a warning, then taps anyway.
Evidence
The evidence is the same as #3354: main run 37815426076, where General was at y=319 and the settled list had it at y=406. Nobody has reproduced the shift locally. In 5 live runs of 01-settings.ad on an iOS 27.0 simulator, every read settled before the tap. Collect evidence that these cases happen before choosing a design.
Required behavior
When the target's position can't be confirmed at dispatch, the click fails with a typed reason instead of tapping the old point. Declare the reason and its details shape in the error taxonomy.
Judging that "the target moved" requires the target's identity across reads. The whole-surface signature comparison (src/daemon/interaction-surface-signature.ts) can't establish it, and it collapses duplicate identities.
Do not add a capture or runner query to warm clicks whose layout is already stable.
Give any bounded wait a wall-clock deadline, not only a polling budget. Capture cancellation must propagate: abort the capture and wait for it to stop, rather than racing a timer. Today the post-open loop's 1.5 s is a polling budget, and its two reads took 2.55–2.57 s live.
A screen that animates constantly must not make a stable target unclickable.
Update TAP_OUTCOME_NOT_OBSERVED_GAP in packages/contracts/src/interaction-guarantees.ts.
Completion
A fixture where the target moves between the click's capture and its tap. The click either taps the new location or fails with the typed reason.
Warm-click p50 on 01-settings.ad, measured before and after the change, does not regress. It was about 1.3 s on the iOS 27.0 simulator.
Rejected so far
A runner-side frame check on every click. It would take an estimated 550–800 lines and add a query to every warm click.
Settling after every iOS open. The first click would cost about 1.8 s more, with no evidence that observable opens shift, and it still wouldn't cover shifts at other times.
Failing whenever the settle times out. A stable target next to an unrelated animation would be refused.
Sources. I took the ios.yml "Smoke Tests" job logs from 473 runs on main and PRs. 437 of those ran 01-settings.ad on an iOS 26.2 simulator with --retries 2. I also read 30 downloaded ios-artifacts bundles.
How often attempt 1 fails. 37 of 437 runs (8.5%) failed attempt 1 of 01-settings.ad. Every failure was at step 4, the wait after click General.
Stale-looking taps. 17 failed attempts in 14 runs tapped General at y=319, while a later attempt in the same run tapped y=406 and passed. In those 17:
the click's own snapshot took a median of 6.4 s (range 2.8–17.2 s)
the time from that snapshot finishing to the tap was a median of 2.8 s (range 0.76–6.2 s)
In first-try passes, the snapshot takes 0.6–1.8 s and the gap is a median of 0.7 s. So stale taps go with a slow capture and a long wait before the tap on a loaded runner.
Not stale-point failures. 11 failed attempts tapped the same point as the attempt that passed. Those are capture or wait failures (#2491/#3328), not stale points.
Open verdicts. 61 opens: 54 probe-failed, 7 observable, and 0 unobservable. All 7 observable opens were on retries, and all of them passed.
After #3356. It has run on 4 CI runs so far. post_gesture_snapshot_stabilized action=open never appears in them, which is expected: no CI open reports unobservable.
What this shows for each case
CI's real case: a probe-failed open. It isn't one of the three cases this issue lists, and it accounts for all 17 stale-looking taps. open already uses a fixed delay for every verdict other than observable, so the next PR makes the post-open settle use the same rule.
(a) A shift between the click's capture and its tap. Plausible but not isolated. The artifacts don't keep snapshot contents or the click's request log. They can't tell "the layout moved after the read" apart from "the read caught a layout still moving".
(b) A shift after an observable open. No evidence: 7 of 7 passed. The sample is small and comes only from retries.
(c) The post-open settle timed out, then the click tapped a stale point. No evidence: there were no unobservable opens, and the artifacts don't record whether a click's settle timed out.
Next steps
Land the change that settles after any open that didn't read the app.
Watch the 01-settings.ad attempt-1 failure rate on CI after it lands, against the 8.5% baseline.
Hold the typed failure and the wall-clock deadline until (a) or (c) has evidence of its own. Getting that evidence needs the click's request log or its pre-tap frame kept in CI artifacts.
Activity
thymikee commentedon Oct 10, 2026
CI evidence (2026-10-03 to 10-10)
Sources. I took the
ios.yml"Smoke Tests" job logs from 473 runs on main and PRs. 437 of those ran01-settings.adon an iOS 26.2 simulator with--retries 2. I also read 30 downloadedios-artifactsbundles.How often attempt 1 fails. 37 of 437 runs (8.5%) failed attempt 1 of
01-settings.ad. Every failure was at step 4, thewaitafterclick General.Stale-looking taps. 17 failed attempts in 14 runs tapped General at y=319, while a later attempt in the same run tapped y=406 and passed. In those 17:
In first-try passes, the snapshot takes 0.6–1.8 s and the gap is a median of 0.7 s. So stale taps go with a slow capture and a long wait before the tap on a loaded runner.
Not stale-point failures. 11 failed attempts tapped the same point as the attempt that passed. Those are capture or wait failures (#2491/#3328), not stale points.
Open verdicts. 61 opens: 54
probe-failed, 7observable, and 0unobservable. All 7observableopens were on retries, and all of them passed.After #3356. It has run on 4 CI runs so far.
post_gesture_snapshot_stabilized action=opennever appears in them, which is expected: no CI open reportsunobservable.What this shows for each case
probe-failedopen. It isn't one of the three cases this issue lists, and it accounts for all 17 stale-looking taps.openalready uses a fixed delay for every verdict other thanobservable, so the next PR makes the post-open settle use the same rule.observableopen. No evidence: 7 of 7 passed. The sample is small and comes only from retries.unobservableopens, and the artifacts don't record whether a click's settle timed out.Next steps
01-settings.adattempt-1 failure rate on CI after it lands, against the 8.5% baseline.fix(daemon): settle the first capture after any iOS open that did not…
fix(daemon): settle the first capture after any iOS open that did not…
fix(daemon): settle the first capture after any iOS open that did not…