Repository navigation
Open
Labels
backlogLower priority / backlogLower priority / backlogbugSomething isn't workingSomething isn't working
Description
Purpose
#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
observableopen.observablemeans the tree was readable, not that it was at rest. - The post-open read never settles. The click gets
postGestureOutcome: unsettledand 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
detailsshape 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_GAPinpackages/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
observableopens 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.
Metadata
Metadata
Assignees
Labels
backlogLower priority / backlogLower priority / backlogbugSomething isn't workingSomething isn't working
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…