Skip to content

fix(ios-runner): stop wide sliders from wedging tree snapshots - #3776

Draft
gwdp wants to merge 5 commits into
callstack:mainfrom
gwdp:gwdp/collapsed-tab-one-query
Draft

gwdp wants to merge 5 commits into
callstack:mainfrom
gwdp:gwdp/collapsed-tab-one-query

Conversation

@gwdp

@gwdp gwdp commented Oct 17, 2026 •

Copy link
Copy Markdown

Summary

The collapsed-tab fallback from #287 (#277) could read hundreds of live XCTest elements one attribute at a time and wedge the runner on crowded screens.

Candidate attributes are now collected during one XCTest query per capture. Query admission uses the remaining processing slice and the root-read duration; an unstarted query reports deadlineExhausted to the capture plan.

Ordinary sliders are excluded from recovery: overlapping sibling buttons must not become duplicated slider children. Recovery remains available for childless tab bars and segmented controls. The fixtures cover slider sibling preservation and tab-bar query deadlines. Seven files changed; no test-app dependency added.

Validation

Head 95238dc3f6; maintainer reproduction and results:

  • pnpm check:affected --run --head 95238dc3f65a81599ddcd8108269869ae7858d28 passed, exactCommit: true, receipt run-FHHaHO.
  • Five focused live iOS XCTest tests passed on iPhone 17 / iOS 26.2.
  • iOS and macOS runner builds and repository formatting passed.
  • Independent review found no actionable issues.

The earlier crowded-screen regression failed on base a0ad15bc7 (author-reported 398.2 s). A maintainer reproduction at 58872fc7f measured 0.14 s for 513 nodes; the slider sibling assertions failed before eligibility was narrowed and passed afterward.

CI-owned native/replay checks were skipped locally. #277's exact React Native case remains unreproduced. Query admission estimates cost; it cannot cancel an admitted XCTest query.

Authorship and LLM use

Original implementation primarily LLM-generated by Claude Code, directed and reviewed by Gabe. Codex-assisted maintainer follow-up narrows slider eligibility and updates tests.

Rollback

Revert the PR.

A wide native slider qualifies for the collapsed tab fallback, which
read every live descendant of the app one attribute at a time on the
XCTest main thread. The tree-processing slice cannot cancel those
reads, so a crowded screen or a sheet with sliders kept the runner busy
until it reported wedged. Match candidates inside one XCTest query with
a predicate that applies the fallback's own content, type and geometry
rules and copies each match's attributes as XCTest evaluates it. Start
that query only while the time left in the processing slice covers what
the capture's root tree read took; when it cannot start, the tree tier
reports a deadline-exhausted, truncated capture so the plan can use the
next backend.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thymikee

Copy link
Copy Markdown
Member

The code looks sound at 4c9e5ed, but the PR is missing one live run. The runner unit suite calls recursiveTreeSnapshotAcquisition directly with a hand-picked processingDeadline, so it skips the plan wiring this PR adds: the processingSlice/processingDeadline calculation, runMainThreadWork, and the mapping of the tier outcome in RunnerTests+SnapshotCapturePlan.swift. The Duo run used the production route, but it was built from v0.21.28 plus the patch, not from this head. No run shows a collapsed tab bar, which is the case in the PR title and #277. So nothing yet shows that the daemon snapshot at this head adopts collapsed items, or that it recovers from the not-started branch without RUNNER_BUSY. Please build from this head (pnpm build && pnpm build:xcuitest && pnpm clean:daemon) and run agent-device snapshot --json on an iOS simulator. Use the runner test app launched with --agent-device-crowded-screen --agent-device-wide-slider, or a screen with a real collapsed tab bar or segmented control. Record the head sha. The output must show the Volume slider's Button/StaticText Left/Right children. The daemon log must have no RUNNER_BUSY or RUNNER_WEDGED, and runner.log must not contain AGENT_DEVICE_RUNNER_SNAPSHOT_TAB_FALLBACK_QUERY_NOT_STARTED.

Not blocking, take or leave: (a) querySweepCanStartQuery now also gates the collapsed-tab query through expectedCost, so a neutral name such as xctestQueryCanStart would fit better (RunnerTests+Snapshot.swift); (b) attributes as! NSObject in collapsedTabCandidate copies the existing snapshotHasFocus cast; (c) the title and the #277 link describe tab bars, but the fix and fixture cover wide sliders and the RN case was not reproduced, so retitle or link it with "Part of"; (d) no test drives the plan tier with a deadlineExhausted result from the tree tier; (e) snapshotDescendants re-walks the root snapshot to collect container frames, which is cheap but repeats the traversal.

The Swift runner suites are not shown as CI results, and the checks list is empty for this head, so there is no failing job to attribute. I did not build or run the Swift suites, so the claims about in-process predicate evaluation on iOS 27 and the new tests failing on the base rest on your report. Before merge, please post the live iOS daemon snapshot at 4c9e5ed on a collapsed tab, segmented control or slider screen, showing the adopted children and no RUNNER_BUSY.

gwdp and others added 3 commits October 17, 2026 01:08
The collapsed tab query now shares the check with the query sweep, so
the name no longer belongs to the sweep alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The slider sat at y=800, below the iPhone Duo inner display, so a live
snapshot there left it out. Above the crowded rows it is on screen on
an iPhone and on the inner display, and it still covers no row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ot start

A stub slows the plan's root tree read so the processing slice left
for the collapsed tab query is shorter than the read. The plan must
reject the truncated tree with the budget reason instead of accepting
it as healthy. Reverting the tree tier's outcome in the plan fails it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gwdp gwdp changed the title fix(ios-runner): find collapsed tab items in one query fix(ios-runner): stop wide sliders from wedging tree snapshots Oct 17, 2026
@gwdp

gwdp commented Oct 17, 2026

Copy link
Copy Markdown
Author

Thanks for the review. Head is now 58872fc7f.

Live run at the head. I built with pnpm build && pnpm build:xcuitest && pnpm clean:daemon and opened the runner test app with --agent-device-crowded-screen --agent-device-wide-slider on an iPhone Duo simulator (iOS 27.1, inner display). On that display the daemon cannot use the simulator AX bridge (window-coordinate-space-unresolved), so every capture goes to the runner. At y=800 the slider was below the 669 pt inner display, so d85383ada moves it to y=20, above the crowded rows.

5 of 5 agent-device snapshot --json captures: backend: tree, state: healthy, acquisition 0.53–0.62 s, 449 nodes, each with the warning "Simulator AX snapshot unavailable (window-coordinate-space-unresolved); used XCTest for this capture". Excerpt from the first one:

{"index":3,"parentIndex":2,"type":"Slider","label":"Volume","rect":{"x":20,"y":20,"width":360,"height":31}}
{"index":4,"parentIndex":3,"type":"Button","label":"Left"}
{"index":5,"parentIndex":3,"type":"StaticText","label":"Left"}
{"index":6,"parentIndex":3,"type":"Button","label":"Right"}
{"index":7,"parentIndex":3,"type":"StaticText","label":"Right"}

daemon.log and runner.log have no RUNNER_BUSY or RUNNER_WEDGED, and runner.log has no AGENT_DEVICE_RUNNER_SNAPSHOT_TAB_FALLBACK_QUERY_NOT_STARTED. The not-started branch did not occur in this run; (d) below covers it through the plan.

Non-blocking points

  • (a) Renamed to xctestQueryCanStart in 0b08fd35c.
  • (b) No change, as you noted.
  • (c) Retitled. The body now says the fix is for the fallback from fix: expose collapsed iOS tab items in snapshots #287 (Investigate missing child refs for iOS horizontal tab bars #277) and that Investigate missing child refs for iOS horizontal tab bars #277's React Native case was not reproduced.
  • (d) testPlanMovesPastATreeTierWhoseCollapsedTabQueryCannotStart (58872fc7f) runs runSnapshotCapturePlan on the fixture with a stub that slows the root read, so the query cannot start. It asserts reason budget and a state other than healthy. It fails when the plan returns .completed for the tree tier instead of its outcome.
  • (e) Left as is. The one query needs every container frame before it starts, and the walk reaches containers in tree order. The pre-pass reads the in-memory root snapshot, with no XCTest round trip, once per capture and only on screens that have a container.

Also: the crowded-screen test, adapted to main's signatures, fails on a0ad15bc7 (walk 398.2 s against the 8 s slice). Full iOS runner suite at 9abcb4c4b: 449 tests, 0 failures. pnpm check:affected --run passed on 58872fc7f. The CI runs from the fork still need maintainer approval.

@thymikee

Copy link
Copy Markdown
Member

I read the code at 58872fc and found no remaining problems. The findings from the earlier review at 4c9e5ed are fixed. Nothing from that review remains open.

No checks are reported for this head, because the fork's CI runs need maintainer approval. There is no failing job to attribute, and the Swift runner suites do not run as PR CI results. There are no conflicts.

I did not build or run the Swift runner suites. That the new plan test passes, that it goes red under the .completed mutation, and that the crowded-screen test fails on a0ad15b rests on reading the code and on your report. The new plan test also assumes the first XCUIApplication snapshotWithError call is the tree tier's root read. Its delayed assertion shows the stub fired, but not on which call. The live Duo run at 58872fc is your report (JSON excerpt and log greps), and I did not reproduce it. It covers the wide-slider fixture, not a real React Native collapsed tab bar, and the PR body now says so.

Before merge, a maintainer needs to approve the fork CI run, and the PR needs to leave draft.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 17, 2026
@thymikee

Copy link
Copy Markdown
Member

@gwdp pushed 95238dc3f6 after reproducing this on an iPhone 17 simulator (iOS 26.2). I kept your single-query acquisition and deadline handling.

The raw XCTest tree already exposes Left/Right as siblings of Volume. The regular capture was adding copies beneath the slider while keeping the originals. The follow-up excludes .slider from collapsed-tab eligibility and changes the crowded-screen test to assert one copy of each button under its original parent. Deadline coverage now uses a genuinely childless UITabBar fixture, including the plan-level recovery test.

Validation at 95238dc3f6:

  • Five focused live iOS XCTest tests passed: slider sibling preservation, collapsed-tab query admission, plan recovery, and both candidate-predicate tests.
  • iOS and macOS runner builds passed; repository formatting passed.
  • pnpm check:affected --run --head 95238dc3f65a81599ddcd8108269869ae7858d28 passed with exactCommit: true (receipt run-FHHaHO). CI-owned native/replay checks were skipped locally; this does not claim those lanes passed.
  • Independent source review found no actionable issues.

The exploratory measurements support your performance fix: 513 nodes acquired in 0.14 s, versus 4.56 s for only the first 20 elements using the old exists/type/frame read pattern. That was a bounded sample, not a fresh full RUNNER_WEDGED reproduction.

I also tried test-app with RN 0.88.0-rc.0 and @react-native-community/slider 5.2.1. Grouped children were absent from both the root snapshot and broad query; the RN slider appeared as Other, so #277's exact case remains unreproduced. No test-app dependency or exploratory instrumentation is included in the push.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants