perf(ios): read each resolved text input through one snapshot

Merged
TH
1h ago
+489-236

Summary

Each attribute read from a live XCUIElement re-runs its query, and each query snapshots the whole app. A fill made 35 queries. Now:

  • Every resolver returns ResolvedTextEntryInput (element + snapshot). It owns the exists-first and snapshot-first reads. Value, frame, identity, and the baseline come from that snapshot. The point lookup's snapshots also answer identity, so its consumers stop probing again.
  • TextEntryReader owns reuse: input() hands out the last read until dispatch { } runs, which wraps every post, tap, and pause. The focus step's post-focus read seeds it. A bound target refuses a replacement input after any dispatch.

Read-back, unconfirmed evidence, identity refusal, the tap witness, textEntryRoute, and the penalized route are unchanged. 12 runner files. The fixture gains a value-read counter and an on-demand input replacement.

Validation

  • pnpm check:affected --run --head 737fa35cbc: passed, exactCommit: true. GitHub owns the Swift and smoke lanes.
  • 121 runner tests (text entry, input probe, typing, command execution, interaction) pass at 737fa35cbc, iPhone 17 Pro iOS 27.0 simulator.
  • The query-count test reads 35 on main, 19 at 2d345058e9, and 17 now (pin โ‰ค 24).
  • Mutation: a warmup pause that bypasses the reader fails testFillFailsWhenAppReplacesMaskedInputWhileWarmupPauses (types 23456 into the successor).
  • Runner type median ms, interleaved, 2d345058e9 โ†’ 737fa35cbc: test-app field-email 3483 โ†’ 3280; Bluesky Sign in 2910 โ†’ 2767.

Risk: a fill can reuse a read only between dispatches; readiness's slow path pays one extra snapshot.

๐Ÿค– Generated with Claude Code