Skip to content

fix(ios): read simulator snapshots past 64 accessibility levels - #4089

Open
KrzysztofMoch wants to merge 2 commits into
callstack:mainfrom
KrzysztofMoch:krzysztof/ios-deep-hierarchy-tap
Open

KrzysztofMoch wants to merge 2 commits into
callstack:mainfrom
KrzysztofMoch:krzysztof/ios-deep-hierarchy-tap

Conversation

@KrzysztofMoch

@KrzysztofMoch KrzysztofMoch commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

On an iOS simulator, snapshot dropped every control nested more than 64 accessibility levels deep, so press by label or ref could not find it. Fixes #4088.

  • The daemon asks the simulator AX bridge for its full protocol bound, 128 levels, instead of 64 (limits.ts).
  • One native request still reads at most 65 levels and the rest arrives through continuations, so trees up to 65 levels deep make exactly the same requests as before. Depth hints compare against that first request, so a hint is still forgotten once a probe-back succeeds.
  • The continuation budget goes from 32 to 64. Without it, screens with several deep chains exhaust the budget and fall back to the slow runner capture. Exhaustion still fails closed (perf(ios): speed up deep snapshots and keep first taps reliable #2414).

8 files: the bridge README, the shared recovery fixture, and the native fixture, which now checks every continuation's levels.

Validation

Device run at 7fcde94 (the bridge change before review fixes to host-side hint bookkeeping, the hint cap, and tests), Xcode 26.6, iOS 26.5 simulator, iPhone 17 Pro, Deep target under N plain UIViews:

screen base 3b9e8d1 head
AX depth 25–64 found, pressed same
AX depth 65–125 missing, Selector did not match found, pressed
70×20 chains 8/20, 5471 ms (runner fallback) 20/20, 516 ms
100×12 0/12, 397 ms 12/12, 535 ms
120×20 0/20, ~6.4 s (budget exhausted) same

At d1a2434 on macOS: snapshot-source vitest 134/134 (native fixtures included), oxlint, typecheck, fallow audit --base upstream/main, check:production-exports. Runner conformance XCTests passed at 7fcde94 (fixture unchanged since).

No check:affected --run receipt: locally layering and eager-closure-budgets.test.ts fail identically on the unchanged base (the clone's origin is a stale fork). CI is the record. Not tested: Xcode 27, iOS 27, physical devices.


Changes made by Claude Opus 5.5 in Claude Code (T3 Code).

The simulator AX bridge cut every regular capture at 65 levels
(maxTraversalDepth 64), so a control nested deeper was missing from the
snapshot and press by label or ref failed, while a coordinate press still
worked. The bridge already reads past the AX server's per-request limit
through continuations; only the daemon's default bound stopped it.

Raise the default to the bridge's own bound (128). Each native request
still reads at most 65 levels, so trees up to 65 levels issue exactly the
same requests, rejections, and depth hints as before. Double the
continuation budget to 64 requests, since each root-to-leaf path needs one
continuation per accepted-depth window and the bound doubled.
@vercel

vercel Bot commented Oct 29, 2026

Copy link
Copy Markdown

@KrzysztofMoch is attempting to deploy a commit to the Callstack Team on Vercel.

A member of the Team first needs to authorize it.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 8 files

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

Comment thread apple/snapshot-bridge/SnapshotBridgeCapture.m
Comment thread packages/platform-apple/src/snapshot-source/adapter.ts Outdated
Comment thread .changeset/ios-deep-tree-snapshot-depth.md Outdated
Depth hints now cap their comparison at the first request themselves, so the adapter passes the
requested levels unchanged and ios_snapshot_source_recovery reports 129 for a 128-level capture.
Correct the per-request limit notes and the changeset's budget claim.
@thymikee

Copy link
Copy Markdown
Member

The change looks sound at d1a2434, but the live simulator evidence is for an older commit, so it needs a fresh run before merge.

The deep-control press by label was taken at 7fcde94. The PR body says that run predates the host-side hint bookkeeping, the hint cap and the test changes. At this head, depth-hints.ts compares against min(requested, 65), and the native first-request window in SnapshotBridgeCapture.m is min(hint, 65). That is the same device route, and nothing shows it works at head. A bookkeeping regression, such as a hint that is never sent or never forgotten on deep trees, would only appear as extra rejected requests and added latency, which the fixtures do not catch. Please re-run on an iOS simulator at d1a2434 and quote the log lines and the head SHA in the PR body. (1) A UIKit screen with Deep target under about 100 plain UIViews (AX depth > 65): snapshot -i, then press label="Deep target" must succeed, and the first capture's ios_snapshot_source_recovery must show requestedLevels 129, hint no-hint, rejected 1, acceptedLevels 32, learning learned. (2) Immediately repeat snapshot and press: the log must show the hint applied with hintedLevels 32 and rejected 0. (3) The 100x12 wide screen must be bridge-served with no runner fallback. (4) The 120x20 screen must still fall back and show the continuation-budget-exhausted warning.

Not blocking, and fine to take or leave: the TS constant MAXIMUM_REQUEST_LEVELS in limits.ts mirrors the native maximumRequestLevels with only a comment, like MAXIMUM_DEPTH does for kMaximumDepth, so one small test comparing them with the bridge sources (or reporting the first-request levels in the recovery counters and dropping the TS constant) could follow in a separate PR; MIN(nativeLevelsHint, maximumRequestLevels) cannot change the result from this host because consume() never sends a hint above the first window, so please keep it or drop it and say which in one line; and the native fixtures in foreground-owner.m hard-code 65 and 64, so exposing those two constants through SnapshotBridgeCapture.h would keep them in step if the request cap changes.

The three cubic-dev-ai threads on the 65-level window comment (#4089 (comment)), the requestedLevels diagnostic (#4089 (comment)) and the changeset wording (#4089 (comment)) are all fixed at this head and already resolved.

Only the Vercel check fails, with "A member of the Team first needs to authorize it", which is the fork preview authorization and not a code failure; the diff touches nothing the docs site builds. The other 11 checks pass. I could not run the Objective-C fixtures, vitest or a simulator, so the 134/134 result and the device table are the author's. The next step before merge is the live simulator run above at d1a2434.

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

iOS simulator: snapshot drops controls nested deeper than 64 accessibility levels

2 participants