Repository navigation
Fix iOS keyboard focus ownership after photo picker - #9371
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe PR adds a terminal input-session state machine and UIKit coordinator. ChangesiOS input session and tap coordination
Foreground connection recovery
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related issues
Possibly related PRs
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 error, 2 warnings, 1 inconclusive)
✅ Passed checks (21 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ios/cmuxUITests/cmuxUITests.swift`:
- Around line 6588-6591: Update the composer-field setup in cmuxUITests to wait
for the field to be hittable, not merely existent, before calling field.tap().
Reuse the existing predicate helper and keep the interaction as a single user
tap before the reducer assertions.
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/GhosttySurfaceCoordinator`+Artifacts.swift:
- Around line 373-390: Replace the cached-snapshot focus authority in
ghosttySurfaceView(_:inputPolicyForTapAtCol:row:) with the shared input-session
decision flow using a freshness token. Treat
cachedVisibleTextForArtifactHitTesting() only as guidance for artifact
detection; when the snapshot is missing or stale, defer input rather than
returning immediate focus, while preserving reliable artifact decisions. Add a
regression test covering a path added after the cached snapshot was created.
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/GhosttySurfaceRepresentable.swift`:
- Around line 194-197: Remove the unused clickGeneration property from
GhosttySurfaceRepresentable and delete its increment in stopMountedTasks(),
leaving the surrounding click and focus task handling unchanged.
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalComposerView.swift`:
- Around line 344-348: Gate the simultaneous tap gesture in TerminalComposerView
on the same enabled-state condition used by the composer field’s .disabled
modifier, so requestInputFocus() is not called when .locksComposerField is true.
Preserve the existing focus request behavior when the field is enabled.
In
`@Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swift`:
- Line 2871: Replace the `.sceneWillResignActive` emission in
`prepareForReuseAfterDetach()` with a dedicated `TerminalInputSessionEvent` such
as `.surfaceDetached`. Handle this event in the session reducer by clearing
input intent and resigning the surface without changing the scene phase, so an
active scene remains active and `inputScene` is not reported as inactive.
- Around line 2816-2822: Remove the conditional inputActualOwnerDidChange(nil)
call from resignCurrentInput. Keep synchronizeActualInputOwner and
inputSession.send(.releaseFocus) unchanged, relying on
TerminalInputSessionCoordinator’s actualOwnerDidChange publication as the sole
writer of active input ownership state.
In
`@Packages/iOS/CmuxMobileTerminalKit/Tests/CmuxMobileTerminalKitTests/TerminalInputSessionReducerTests.swift`:
- Around line 4-201: Add a test in TerminalInputSessionReducerTests covering
lifecycleBoundary: retain a composer request after an initial failed focus
completion, then verify handling .lifecycleBoundary emits [.focus(.composer)].
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: de11d7fe-df3b-4dac-9f4b-96d89399b888
📒 Files selected for processing (13)
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/GhosttySurfaceCoordinator+Artifacts.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/GhosttySurfaceRepresentable.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalComposerView.swiftPackages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceTapDisposition.swiftPackages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView+Artifacts.swiftPackages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swiftPackages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceViewDelegate.swiftPackages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/TerminalInputSessionCoordinator.swiftPackages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/TerminalInputTextView.swiftPackages/iOS/CmuxMobileTerminalKit/Sources/CmuxMobileTerminalKit/TerminalInputSessionReducer.swiftPackages/iOS/CmuxMobileTerminalKit/Tests/CmuxMobileTerminalKitTests/TerminalInputSessionReducerTests.swiftios/cmuxUITests/cmuxUITests.swift
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
ios/cmuxUITests/cmuxUITests.swift (1)
6610-6610: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winWait for the composer field to become hittable after picker dismissal.
waitForKeyboardDismissalconfirms keyboard removal. It does not confirm thatPhotosPickerrestored the field hit target. Wait forfieldto become hittable before this first recovery tap.Proposed fix
+ XCTAssertTrue(waitForHittable(field, timeout: 4)) field.tap()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ios/cmuxUITests/cmuxUITests.swift` at line 6610, Before the first recovery tap on field in the picker-dismissal flow, explicitly wait until field is hittable; keep waitForKeyboardDismissal for keyboard state, then perform field.tap() only after the element’s hit-test readiness is confirmed.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@ios/cmuxUITests/cmuxUITests.swift`:
- Line 6610: Before the first recovery tap on field in the picker-dismissal
flow, explicitly wait until field is hittable; keep waitForKeyboardDismissal for
keyboard state, then perform field.tap() only after the element’s hit-test
readiness is confirmed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: d7ab0889-23f5-46dc-b202-952d294bb01b
📒 Files selected for processing (8)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/GhosttySurfaceCoordinator+Artifacts.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalComposerView.swiftPackages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView+Artifacts.swiftPackages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swiftPackages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceViewDelegate.swiftPackages/iOS/CmuxMobileTerminalKit/Sources/CmuxMobileTerminalKit/TerminalInputSessionReducer.swiftPackages/iOS/CmuxMobileTerminalKit/Tests/CmuxMobileTerminalKitTests/TerminalInputSessionReducerTests.swiftios/cmuxUITests/cmuxUITests.swift
|
Verification at a8bc02d Passed: reducer 14/14, transition planner 6/6, letterbox geometry 24/24, iOS SwiftPM builds, iOS 26.5 and 18.4 Photos Cancel first-retap recovery, iOS 26.5 photo-selection first-retap recovery, exact or sub-point dock pinning, continuous rotation/foreground evidence, and a continuous 20-terminal-tap plus 10-owner-alternation stress recording. Blocked: hosted XCUITests fail before selection on the unchanged WorkspaceListTableCoordinatorDropTests compile error from main; Instruments cannot produce an exportable simulator trace; live presence attachment returns HTTP 404; Aziz is unavailable, so the signed final build remains queued. Formal verify-implementation approval remains incomplete. The PR is ready for phone dogfood and has not been merged. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+ConnectionRecovery.swift:
- Around line 100-105: Update the secondary aggregation condition in the
connection recovery flow to also require a non-nil remoteClient alongside
multiMacAggregationEnabled, trigger.reschedulesSecondaryAggregation, and
connectionState == .connected. This must prevent scheduling aggregation when the
connection is marked connected but no live remote client exists.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: c1066168-948e-433f-a928-4ea2b8959901
📒 Files selected for processing (2)
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+ConnectionRecovery.swiftPackages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellForegroundResumeTests.swift
|
Verification update at ec983f9:\n\n- The phone traces exposed a foreground-recovery versus secondary-aggregation route race. Foreground recovery now stays the sole route owner while disconnected, and aggregation requires both connected state and a live foreground client.\n- The new clientless-foreground regression failed before the guard and passes after it. The seven-test foreground recovery suite passed 20 consecutive iterations, 140 behavior tests total.\n- Computer Use passed the isolated iOS 26.5 Photos Cancel paths for the first terminal tap and first composer tap. The composer accepted input, and both bottom bars stayed attached immediately above the keyboard.\n- Fresh macOS build: https://github.com/manaflow-ai/cmux/actions/runs/30727950319\n- Final focused hosted run: https://github.com/manaflow-ai/cmux/actions/runs/30728806181\n- The signed iPhone app embeds ec983f9, bundle dev.cmux.ios.kbpin, team 7WLXT3NR37. Aziz is currently unavailable, so the exact app is queued for automatic install, launch, sign-in, and pairing.\n\nRemaining verification: observe the final recovery build attached on Aziz for at least 60 seconds. No merge requested. |
Fixes first-tap keyboard recovery and bottom-dock coordination in workspace detail.
The terminal, composer, photo picker, and scene lifecycle now share one input-session reducer. Ordinary terminal taps focus synchronously before asynchronous artifact/click work. UIKit responder results remain the source of actual ownership.
Tests:
Hosted XCUITests are currently blocked before execution by the unrelated WorkspaceListTableCoordinatorDropTests compile error already present on main.
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Fixes iOS keyboard focus after the Photos picker by centralizing terminal/composer input ownership so the first tap reliably restores the keyboard and keeps the bottom dock aligned using UIKit’s keyboard guide. Also restricts foreground-only recovery by requiring a live client for secondary aggregation and skipping it during redial/validation.
Bug Fixes
PhotosPickerfor both terminal and composer; no stale owner after dismissal.Refactors
TerminalInputSessionReducerandTerminalInputSessionCoordinator; UIKit/SwiftUI first-responder changes flow through one owner.GhosttySurfaceViewDelegate.inputPolicyForTap…andTerminalInputTapIntent; immediate focus only with a fresh artifact snapshot, otherwise deferred until classification.TerminalComposerViewroutes focus andPhotosPickerlifecycle through the input session; SwiftUI@FocusStatemirrors UIKit;TerminalInputTextViewreports first-responder changes.TerminalDockKeyboardTransition*,TerminalKeyboardHeightAnimation).Written for commit 91b1677. Summary will update on new commits.
Summary by CodeRabbit
New Features
Bug Fixes
Tests