Skip to content

Fail closed for unresolved synthesized coordinate fill on iOS #1721

Open

Description

@thymikee

Problem

The iOS coordinate fill path can use synthesized-first-responder-replacement when the requested coordinate does not resolve to a concrete text-entry element, a refresh point exists, and the XCTest channel is penalized. In that state, replacement is dispatched through the application-level first responder without concrete element identity or post-write verification.

If focus still belongs to a previous field, an unresolved coordinate fill can therefore replace text in the wrong input while returning a completed result with verified: nil. This behavior predates #1700; it is separate from the bare-type witness fix merged there. It is also distinct from #1718, which tracks parent-owned touch-point parity for direct selectors and Maestro.

Relevant seams:

  • RunnerTests+SynthesizedTextEntry.swift: shouldUseSynthesizedFirstResponderReplacement and runSynthesizedReplacementRoute
  • RunnerTests+TextEntry.swift: coordinate target resolution and refresh-point fallback
  • src/platforms/apple/interactions.ts: coordinate fill dispatches replacement mode

Closure

  • Fail closed when coordinate fill cannot bind synthesized replacement to the intended text-entry target, or introduce a concrete target/commit signal that provides equivalent safety.
  • Add a red/green iOS regression proving an unresolved coordinate fill cannot mutate a previously focused field.
  • Preserve the penalized-channel replacement path for a concretely resolved coordinate target.
  • Report a structured actionable failure instead of an unverified success when safe replacement cannot be established.
  • Update the interaction guarantee classification and contract coverage if the fix changes a declared dispatch-path guarantee.

Activity

  1. thymikee commented on Oct 8, 2026

    @thymikee
    MemberAuthor

    Re-checked against main after #3201. Only partly fixed, so keeping this open.

    #3201 covers the case where an input sits under the point: coordinateTapTextInputIdentityAt binds the target to that input's identity before the tap (RunnerTests+TypeExecution.swift), and reads and clears then refuse any other field.

    The case this issue names still behaves the same. When nothing is under the point, or the probe can't answer, inputAtPoint is nil, so the target is unbound. shouldUseSynthesizedFirstResponderReplacement(hasResolvedElement: false, hasRefreshPoint: true, penalized: true) still sends it down the replacement route (RunnerTests+SynthesizedTextEntry.swift). The route types through the app-level first responder. Its clear read and commit wait go through resolveUnboundTextEntryElement, which falls back to focusedTextInput. If an earlier field still has focus, the fill replaces that field's text and the commit wait reports success on the wrong field.

    Smallest fix: on the penalized coordinate route, when inputAtPoint is nil, refuse with a typed reason (no text input at the point) before the replacement route runs. Add the red/green regression from the closure list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions