Skip to content

fix(tips): raise the post-tip keyboard from UIKit instead of @FocusState - #527

Merged
bmc08gt merged 1 commit into
mainfrom
worktree-fix-tip-keyboard
Jul 27, 2026
Merged

fix(tips): raise the post-tip keyboard from UIKit instead of @FocusState#527
bmc08gt merged 1 commit into
mainfrom
worktree-fix-tip-keyboard

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Problem

After #524, completing a tip opened the chat but only focused the message field — the keyboard never came up.

Root cause

#524 set the composer's SwiftUI @FocusState = true on appear. That updates SwiftUI's focus state — the caret and the isComposing bar-morph, which is why the field looked focused — but it does not present the system keyboard. The composer lives inside a nested UIHostingController bridged through UIKit (ChatScreenRepresentableChatScreenViewController), and a programmatic @FocusState doesn't cross that hosting boundary. Only a real becomeFirstResponder does — which is why tapping the field (a genuine UIKit responder event) always raised the keyboard.

Every working focus-on-appear in the app (EnterPhoneScreen, LoginScreen, etc.) is a pure-SwiftUI NavigationStack/sheet screen — some raise the keyboard with only a 100 ms delay, mid-transition. So the 350 ms .task delay was never the issue; the hosting boundary was, and no amount of extra delay would have fixed it.

Fix

Raise the keyboard from the UIKit layer that already owns keyboard behavior (per ChatScreenViewController's own doc: "All scroll, keyboard, and flow-under behavior lives in the UIKit screen"):

  • ChatScreenViewController.viewDidAppear — the exact moment the push transition has settled — calls becomeFirstResponder() on the composer's text field.
  • Guarded by a didFocusComposer flag → strict once-per-open (tighter than the old .task, which could re-fire on re-appear).
  • Removed the @FocusState/.task focus hack and its focusOnAppear plumbing through ConversationBottomBar / ConversationComposer.
  • The isComposing bar-morph is preserved — when the keyboard rises, SwiftUI syncs @FocusState and the existing .onChange(of: isFocused) fires.

The .tipConversationWithKeyboard route and openKeyboardfocusOnAppearfocusesComposerOnAppear chain are unchanged; only the mechanism that acts on the flag moved from SwiftUI to UIKit.

Testing

  • ./Scripts/build.sh --deviceBUILD SUCCEEDED
  • Installed to a physical device; verified a completed tip opens the chat with the keyboard raised.



The post-tip open (#524) set the composer's SwiftUI @focusstate on appear.
That updates SwiftUI's focus state — the caret and the isComposing bar-morph —
but never presents the system keyboard, because the composer is hosted inside a
nested UIHostingController bridged through UIKit (ChatScreenRepresentable →
ChatScreenViewController). A programmatic @focusstate doesn't cross that hosting
boundary; only a real becomeFirstResponder does. Taps worked because a tap is a
real UIKit responder event. Bumping the .task delay would never have helped —
every working focus-on-appear in the app is a pure-SwiftUI screen.

Raise the keyboard from the UIKit layer that already owns keyboard behavior:
ChatScreenViewController.viewDidAppear (the point the push transition has
settled) calls becomeFirstResponder() on the composer field, guarded so it's a
strict once-per-open. Removes the @FocusState/.task focus hack and its plumbing
through ConversationBottomBar/ConversationComposer. The isComposing morph still
works — when the keyboard rises SwiftUI syncs @focusstate and the existing
onChange(of: isFocused) fires.
@bmc08gt
bmc08gt merged commit 8918ebb into main Jul 27, 2026
@bmc08gt
bmc08gt deleted the worktree-fix-tip-keyboard branch July 27, 2026 21:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant