fix(ios): wait for hidden-keyboard synthesized text to commit before responding - #1673
fix(ios): wait for hidden-keyboard synthesized text to commit before responding#1673thymikee wants to merge 1 commit into
Conversation
…responding The synthesized-first-responder bare-type route returned ok as soon as the private XCTest event record was posted, while the target app was still committing characters. On slow CI simulators the trailing characters landed after the response, so agents (and the smoke test) observed a truncated field value through the public type path. After dispatch, poll the tapped element until its value reaches textBefore + typedText, exit immediately when the app transforms the input (formatter, mid-text caret, autocomplete), and if progress stalls as a strict prefix, re-synthesize the missing tail once. Submit-suffixed text keeps the old immediate return so a repair can never double-submit. Validated on a booted iPhone 17 Pro simulator: 5/5 passes of testBareTypeUsesTappedInputWhenSoftwareKeyboardIsHidden under full-core CPU load, with the commit wait absorbing up to ~390ms of post-dispatch lag that the previous code ignored (uniform ~494ms dispatch-only before); the tail repair never had to fire, consistent with commit lag rather than true drops.
Size Report
Startup median (7 runs, lower is better):
Top changed chunks:
|
|
[P1] Do not re-synthesize a suffix merely because the observed value stayed a strict prefix for 600 ms. The motivating failure is delayed app-side commit after the private synthesize call returns. A 600 ms quiet prefix cannot distinguish that delay from a genuinely dropped suffix: the original tail may still be queued. Keep this change as an observation-only commit wait, or require evidence that the original event stream is finished before repairing. Add a production-shaped regression where the value stalls past the quiet window while the original suffix remains queued, then commits; prove the command never posts a second suffix and the final value is exactly the requested text. |
|
Closing — this work is continuing elsewhere. |
|
Addresses the P1 on #1673: a 600 ms quiet prefix cannot distinguish delayed app-side commit from a genuinely dropped suffix, so re-synthesizing the tail could post it while the original was still queued and commit the text twice after the command had already reported ok. Drop the repair path entirely — the tail builder, the quiet-window constant, the stall tracking, and the synthesizer dependency the wait only needed in order to repair. What remains is a bounded observation: poll the tapped element until the value commits, the app transforms it, the value becomes unreadable, or the 3s ceiling expires. A dropped suffix still reports ok, exactly as before this change; only the false truncation from commit lag is removed. The submit-key skip stays, for its own reason: the app may clear or rewrite the field on submit, so textBefore + typedText is not the value to wait for. Also wire testSynthesizedTextCommitProgressWalksExpectedPrefixOnly into the iOS smoke lane — that workflow enumerates its tests by hand with -only-testing, so a new test that is not listed never runs.
|
Picking this up in #1676 — the commit wait is folded in there, minus the repair path. Your P1 stands, so the tail re-synthesis is gone rather than defended: Also wired |
…responding (#1676) * fix(test): wait for typed text to settle in the hidden-keyboard runner test testBareTypeUsesTappedInputWhenSoftwareKeyboardIsHidden read textField.value in one shot right after executeTypeCommand returned. The simulator commits synthesized keystrokes after the command responds, so on a loaded CI machine the read landed mid-word — observed failures reported ("h") and ("hardware-ke"). It failed on 3 of 5 runs of a branch carrying zero Swift changes and passed on re-run. Poll the value until it holds the expected text (10s ceiling) and assert on the last value read, so a real regression still fails with what the field actually held. Both reads in the test use the same helper; the assertions are unchanged. * Revert "fix(test): wait for typed text to settle in the hidden-keyboard runner test" This reverts commit caecdc6. * fix(ios): wait for hidden-keyboard synthesized text to commit before responding The synthesized-first-responder bare-type route returned ok as soon as the private XCTest event record was posted, while the target app was still committing characters. On slow CI simulators the trailing characters landed after the response, so agents (and the smoke test) observed a truncated field value through the public type path. After dispatch, poll the tapped element until its value reaches textBefore + typedText, exit immediately when the app transforms the input (formatter, mid-text caret, autocomplete), and if progress stalls as a strict prefix, re-synthesize the missing tail once. Submit-suffixed text keeps the old immediate return so a repair can never double-submit. Validated on a booted iPhone 17 Pro simulator: 5/5 passes of testBareTypeUsesTappedInputWhenSoftwareKeyboardIsHidden under full-core CPU load, with the commit wait absorbing up to ~390ms of post-dispatch lag that the previous code ignored (uniform ~494ms dispatch-only before); the tail repair never had to fire, consistent with commit lag rather than true drops. * fix(ios): make the synthesized commit wait observation-only Addresses the P1 on #1673: a 600 ms quiet prefix cannot distinguish delayed app-side commit from a genuinely dropped suffix, so re-synthesizing the tail could post it while the original was still queued and commit the text twice after the command had already reported ok. Drop the repair path entirely — the tail builder, the quiet-window constant, the stall tracking, and the synthesizer dependency the wait only needed in order to repair. What remains is a bounded observation: poll the tapped element until the value commits, the app transforms it, the value becomes unreadable, or the 3s ceiling expires. A dropped suffix still reports ok, exactly as before this change; only the false truncation from commit lag is removed. The submit-key skip stays, for its own reason: the app may clear or rewrite the field on submit, so textBefore + typedText is not the value to wait for. Also wire testSynthesizedTextCommitProgressWalksExpectedPrefixOnly into the iOS smoke lane — that workflow enumerates its tests by hand with -only-testing, so a new test that is not listed never runs.
What
The
synthesized-first-responderbare-typeroute (added in #1657) returnedokas soon as the private XCTest event record was posted, while the target app was still committing characters. On slow CI simulators the trailing characters land after the response, so anything reading the field right after — the smoke-lane assert, or a real agent doingtype→ submit — observes a truncated value through the publictypepath. This is the root cause of thetestBareTypeUsesTappedInputWhenSoftwareKeyboardIsHiddenflake ("hardware-keyboa" != "hardware-keyboard") that hit #1670, #1671, andclaude/agent-device-issue-1658on TypeScript-only diffs.Fix is in the production route, not the test: after a successful dispatch,
awaitSynthesizedFirstResponderCommitpolls the tapped element until its value reachestextBefore + typedText(3s ceiling), and:Prefix/tail decisions are pure policy functions with unit tests. Bare
typekeeps itsverified: nilcontract — no new failure modes, the response just can't outrun the field anymore.Validation
On a booted iPhone 17 Pro simulator (iOS 26.2):
yesburner per core): all pass.type-allphase ran 563–884ms vs a uniform ~494ms dispatch-only on unpatched code — the commit wait absorbed up to ~390ms of real post-dispatch lag per run.AGENT_DEVICE_RUNNER_REPAIR_TEXT_ENTRY route=synthesized-first-responder.Reviewer notes
--delay-ms) now commit-waits per character, which doubles as per-char drop detection; the inter-char delay usually absorbs the wait.