Skip to content

fix(swift-ios): defer composer focus until after view updates - #12655

Open
saphid wants to merge 1 commit into
pingdotgg:t3code/rebuild-mobile-app-swiftfrom
saphid:fix/swiftui-composer-focus-cycle
Open

saphid wants to merge 1 commit into
pingdotgg:t3code/rebuild-mobile-app-swiftfrom
saphid:fix/swiftui-composer-focus-cycle

Conversation

@saphid

@saphid saphid commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor

What Changed

Defer native composer first-responder changes until after SwiftUI finishes the current view update. Superseded focus requests are ignored, and pending attachment focus is cancelled as soon as focus is cleared. The keyboard-preservation test now observes focus after the queued change runs.

Why

Tapping the composer can freeze the app. FeatureComposerTextInput.updateUIView calls becomeFirstResponderWhenAttached, which runs becomeFirstResponder inside SwiftUI's update pass. The focus change starts another graph update, and the main thread spins in AG::Graph::print_cycle indefinitely. The user cannot type or navigate until the app is killed. The unsafe path came from #7607 (efaec0bd8e), which replaced the @FocusState field with FeatureComposerTextInput. Physical-iPhone watchdog reports show this exact stack.

Moving responder changes outside the update pass, and checking that a queued request still matches the current binding before applying it, breaks the cycle. This affects the native SwiftUI composer on iPhone and iPad only; no provider, wire contract, or connection-mode change.

Verification

  • Reproduced and fixed in the Simulator (iOS 27 Simulator, Debug build, paired to a disposable local server): open a thread, tap the composer, type, tap Back.
    • On 157476f1fb the app froze in 5 of 5 runs: 100% CPU, typed text never appears, Back ignored. A process sample shows the main thread in FeatureComposerTextInput.updateUIView → becomeFirstResponderWhenAttached → becomeFirstResponder → AG::Graph::print_cycle.
    • With this change the same script worked in 4 of 4 runs: text typed, CPU under 3%.
  • Focused tests with this change on 157476f1fb (iOS 27 Simulator): 69 passed across FeatureVoiceInputTests, FeaturePastedTextTests and FeatureComposerPowerTests. CI Contract fixtures and native tests passes on the head.

UI Changes

No layout change. Same scripted flow on both builds.

Before (157476f1fb): the composer tap freezes the app; typing and Back do nothing.

Before: tapping the composer freezes the app

After: the composer focuses and accepts text.

After: the composer focuses and accepts typed text

Real-time recordings: before.mp4 · after.mp4.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Implementation: Claude Fable 5.1 in the original T3 Code Claude agent thread. Handoff and PR preparation: GPT-6 Astra in Codex. Simulator reproduction and verification: Claude Opus 5.5 in Claude Code.

🤖 Generated with Claude Code

Fix the synchronous responder path introduced by pingdotgg#7607 (efaec0b). Carry the tested two-file fix from 45d15ad7ee onto the upstream SwiftUI branch.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 19, 2026
@saphid
saphid marked this pull request as ready for review September 27, 2026 20:30
@macroscopeapp

macroscopeapp Bot commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved at 4f46c4d

Macroscope's review found this PR approvable — This is a contained fix to existing iOS composer focus handling that moves responder changes outside SwiftUI’s update pass and guards stale requests. The only other change updates a focused test to observe the deferred main-queue behavior.

You can add or adjust custom eligibility rules. Learn more.

@saphid

saphid commented Sep 27, 2026

Copy link
Copy Markdown
Contributor Author

@t3dotgg this is ready for your review and merge.

Merge order: independent of the SwiftUI thread-sync chain (#10758 → #10759 → #10761 → #10762 → #10763 → #10764 → #10765 → #10766 → #10767); it can merge at any time.

Evidence at head 4f46c4d722:

  • CI: all checks pass on this head.
  • iOS 27 Simulator: on upstream 157476f1fb, tapping the composer froze the app in 5 of 5 runs (100% CPU, SwiftUI graph cycle from becomeFirstResponderWhenAttached, matching the phone watchdog reports). With this PR, 0 of 4 valid runs froze and text typed normally (videos in the description). 69 focused tests pass.

Independent review: GPT-6 Astra xhigh (read-only) found no defects. Its only note was a missing unit test for responder timing, which was declined because it needs a hosted UIKit responder harness; the Simulator evidence above covers the behaviour.

This branch has not been deployed

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

Labels

size:M 30-99 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant