fix(runner): read a blind fill-point lookup as unreadable, not empty

TH
2d ago
+72-19

Summary

#3718 made the iOS degraded-channel fill refuse before its focus tap when the point identifies no text input, and split a lookup that cannot answer into TEXT_INPUT_PROBE_UNAVAILABLE. One conflation survived that split: absent was answered from an empty enumeration of the four text-input queries. A degraded XCTest channel is known to enumerate nothing on some simulator UI trees while screenshots and direct gestures still work (the host says so where it degrades: src/commands/capture/runtime/snapshot.ts), and that state records no issue and throws nothing — so the probe reported NO_TEXT_INPUT_AT_POINT, publishing "there is no input at that point" from a reading that saw nothing. For a ref fill whose ref is the input, the refusal is a false claim, the hint ("pick the input node itself") is unactionable, and nothing recovers until the 120 s penalty expires.

The pre-tap lookup now answers absent only when the four queries enumerated some element to filter: an enumeration that read nothing is unavailable, same as a recorded issue or an exception. queryTextInputs splits into enumerateTextInputQueries + filterTextInputCandidates so the identity probe sees the raw enumeration; the three other callers keep their exact behavior through the composed function. The commands.md bullets for the two refusals state the difference ("read the screen" vs "including reading no elements at all"), and the alert-section reason enumeration gains the two #3718 reasons. Three files of code + test + docs, one command family; no wire, host, or contract change.

agent-device fill 201 496 "text"   # blind enumeration (queries read nothing) → TEXT_INPUT_PROBE_UNAVAILABLE, dispatched: no
agent-device fill 201 700 "text"   # screen read, point outside any input    → NO_TEXT_INPUT_AT_POINT, dispatched: no (unchanged)

Validation

Base 5b61e8a9a (main), head da50c614f. Same XCTest on an iPhone 17 Pro simulator:

  • Sanity (first base run): with main's probe ignoring the flag, the same test's fill lands — ok, field reads "fresh" — so the test exercises the real route and only the blind state changes the outcome.

  • base (main's probe source plus only the test-flag read, so the blind state injects but nothing guards it): testSynthesizedReplacementRefusesABlindEnumerationAsUnreadableNotEmpty red — NO_TEXT_INPUT_AT_POINT where TEXT_INPUT_PROBE_UNAVAILABLE is required, i.e. main today publishes "no input at that point" from an enumeration that read nothing. The test arms a textInputProbeBlindForTesting flag (test-target builds only, same shape as the existing issue injection) that makes the four queries return nothing without an issue.

  • head: that test green, and the sibling refusal family green — the pre-tap refusal + tap witness, the failed-lookup probe-unavailable case, the bound-route siblings (moved-on-focus, dropped-select-alls, paced production caller), and the non-text-control authorization test.

  • Mutation: deleting the !enumerated.isEmpty guard at head turns the new test red with NO_TEXT_INPUT_AT_POINT, so the guard is what carries the fix.

pnpm check:affected --run --head da50c614f: passed (exactCommit=true; command-docs, docs-links, docs-prose, xctest-selection, packaged-runner-swift; the device-bound swift-runner and replay lanes are skipped locally and run in CI). CI obligations: ios.yml targeted runner lane selects the new test. Resolved risks: probeTextInputs (the tap-witness caller) keeps its own absent/unavailable reading — an empty enumeration there only lost an authorization, which is not an assertion published to a caller, so it stays out of scope; the non-penalized route is unchanged as in #3718.

Refs #1721

🤖 Generated with Claude Code