Skip to content

fix(ios): stop a fill's post-focus reads from recording an XCTest failure (#3238) - #3329

Merged
thymikee merged 2 commits into
mainfrom
t3/implement-3238-pr
Oct 9, 2026
Merged

thymikee merged 2 commits into
mainfrom
t3/implement-3238-pr

Conversation

@thymikee

@thymikee thymikee commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

Summary

fill/type still read the element they resolved before their own focus tap, after the tap: textEntryRefreshPoint(for:) read element.frame to build the target's refresh point, and moveCaretToEnd(element:) read it before the caret edge-tap reached from clearTextInput. A field that stops answering its resolving query once focused (#3060's Flutter shape) turns either read into a recorded No matches found for …, which the dispatch path converts to XCTEST_RECORDED_FAILURE plus an invalidated target — the report's fill @e6 row at 4.5 s.

Both frames now come from textEntrySnapshotFrame, which reads through snapshot() — the throwing channel that records nothing, already how probeTextEntryInput and withElement reach an element. Where no frame can be answered the call sites degrade instead of dispatching a stale point: the refresh point drops to no point (the target is not yet identity-bound, so the pre-focus point could name a field that moved into it — the fill fails closed with a typed reason), and the caret edge-tap skips to the point-free delete burst.

Runner-only; 3 files. Absence-vs-failure reads stay on probeTextEntryInput. Closes #3238.

Validation

Tested at 2c9588332. New regression test on --agent-device-text-entry-unqueryable-on-focus drives fill through execute(command:): red before the change at the refresh-point read (RunnerTests+TextEntryFocus.swift:169), green after with didRecordXCTestFailure == false and the app-side focus witness proving the gesture landed. Moves-on-focus, both #3237 tap tests, and the fill/synthesized-replacement suites pass on an iPhone 18 Pro simulator; iOS and macOS build-for-testing succeed. pnpm check:xctest-selection and check:packaged-runner-swift pass; remaining check:affected items (swift-runner builds, replay lanes) are GitHub-authoritative. The moveCaretToEnd hunk is defensive-only — every clearTextInput caller passes an element that already passed the snapshot-probing resolve — and is pinned by no new test.

View guided diff Turn on auto-fix

…lure (#3238)

#3237 settled this on the tap route; the type/fill focus path kept two reads of
the element it resolved BEFORE its own focus tap. `textEntryRefreshPoint(for:)`
read `element.frame` after that tap to build the target's refresh point, and
`moveCaretToEnd(element:)` read it before the caret edge-tap reached from
`clearTextInput` on a replacement fill. A field that stops answering the query
that resolved it once it takes focus — the #3060 Flutter shape — turns either
read into a recorded "No matches found for …", which the dispatch path converts
into XCTEST_RECORDED_FAILURE plus an invalidated target for a focus the gesture
had already delivered. The report's `fill @e6` row measured 4.5 s at this seam.

Both frames now come from `textEntrySnapshotFrame`, which reads through
`snapshot()` — the throwing channel that records nothing, already how
`probeTextEntryInput` and `withElement` reach an element. Where the frame
cannot be answered the call sites degrade per the route's own rules: the
refresh point drops to NO point rather than the pre-focus one, because this
route's first resolve is not yet bound to an identity and a field that went
unqueryable on focus may have moved the layout so the old point names another
input, so the fill fails closed with a typed reason instead of typing into
that input; the caret edge-tap skips to the caller's point-free delete burst
rather than dispatching a point from a frame the field may have moved.

Runner-only, no CLI/daemon/API change. Reads that distinguish absence from
failure stay on `probeTextEntryInput`. A fill whose field genuinely did not
take focus still fails typed, and a genuinely recorded failure still
invalidates and surfaces. The regression test drives `fill` through
`execute(command:)` on the unqueryable-on-focus fixture: red before the change
at the refresh-point read, green after with the app-side focus witness proving
the gesture landed.
@github-actions

github-actions Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.13 MB 5.13 MB +342 B
Package (unpacked) 5.13 MB 5.13 MB +342 B
Package (download) 1.54 MB 1.54 MB +69 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 28.7 ms 28.3 ms -0.4 ms
CLI --help 85.8 ms 85.1 ms -0.7 ms

@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 3 files

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

View guided diff | Turn on auto-fix | Re-trigger cubic

… the handle (#3329 review)

A successful `snapshot()` proves the handle answered its query when the frame was
read, not that it still answers when the tap dispatches — and an element-anchored
`coordinate(withNormalizedOffset:).tap()` re-runs the resolving query to place its
anchor when the action executes, the same #3060 recorder one call later. Both
branches now leave the handle: the edge-tap point is computed from the snapshot
frame and dispatched through `tapAt`, the app-relative coordinate route that
resolves no element at all — the shape `waitForTextEntryReadinessAfterTap`
settled on in #3237 — and the empty-frame `element.tap()` (how a departed handle
reads) is folded into the same fail-closed guard, degrading to the point-free
delete burst rather than tapping a handle that may not resolve. `clearTextInput`
threads `app` to reach `tapAt`; its three call sites already hold it.

No CLI/daemon/API change.
@thymikee

thymikee commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

Gate record at 65a9dbf (caret edge-tap dispatched app-relative after the cubic-bot finding — see thread reply): pnpm check:affected --run — all runnable checks passed (xctest-selection: 0 methods reachable by no lane; packaged-runner-swift: 58 files ok). swift-runner-ios/macos builds and the replay lanes are GitHub-authoritative; both build-for-testing succeeded locally. iPhone 18 Pro simulator: 14-test text-entry batch green (regression test, both #3237 tap tests, moves-on-focus pair, fill/clear/repair suites).

@thymikee

thymikee commented Oct 8, 2026

Copy link
Copy Markdown
Member Author

This PR is ready for human review. At 65a9dbf the change looks correct, and CI is green: 19 checks, 0 failing, including the iOS runner and replay lanes that exercise this Swift code. There are no conflicts.

Not blocking, and you can take or leave these: waitForTextEntryReadinessAfterTap and withElement still read frames through their own snapshot() calls, so one record-free helper could serve every post-dispatch frame read; no test covers the choice to return nil instead of requestedPoint when snapshot cannot answer, so a variant where the field moves and becomes unqueryable would pin it; and the long comments in RunnerTests+TextEntry.swift (on moveCaretToEnd, textEntrySnapshotFrame and textEntryRefreshPoint) could shrink to the invariant, such as "post-focus reads go through snapshot(); with no frame, use no point", while noting that a zero-size frame in the caret path now skips silently where it used to call element.tap().

The cubic-dev-ai thread on the edge tap no longer applies, since the tap now goes through tapAt(app:x:y:) from the snapshot frame, so you can resolve it: #3329 (comment)

I did not run the regression test or the simulator batch. The red-before claim rests on reading the old code and the #3237 measurement. The live evidence is your 14-test batch on an iPhone 18 Pro simulator, and no CLI fill ran against a real Flutter field like the one in #3060. I also did not trace each clearTextInput caller to confirm that moveCaretToEnd is defensive-only. The macOS coordinate translation for the new caret tap is confirmed by reading only. Could you confirm the simulator batch result at 65a9dbf?

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 8, 2026
@thymikee
thymikee merged commit 9d6012c into main Oct 9, 2026
19 checks passed
@thymikee
thymikee deleted the t3/implement-3238-pr branch October 9, 2026 06:52
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-09 06:52 UTC

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.

iOS: type/fill still reads its pre-focus element after the focus tap, ending the runner session

1 participant