Repository navigation
Fix iOS 27 keyboard dock tracking - #9958
Conversation
📝 WalkthroughWalkthroughThe PR adds an iOS 27 keyboard dock geometry path based on keyboard frame notifications. Older systems continue using ChangesKeyboard dock geometry
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant iOSKeyboard
participant GhosttySurfaceView
participant GhosttySurfaceHostView
iOSKeyboard->>GhosttySurfaceView: keyboardDidChangeFrameNotification
GhosttySurfaceView->>GhosttySurfaceView: calculate keyboard overlap
GhosttySurfaceView->>GhosttySurfaceHostView: update manual dock constraint
GhosttySurfaceView->>GhosttySurfaceView: synchronize terminal viewport
Possibly related PRs
Suggested reviewers: Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (2 errors, 1 warning)
✅ Passed checks (22 passed)
✨ Finishing Touches📝 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: 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/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swift`:
- Around line 22-30: Update the shared
assertTerminalDockPinnedToSoftwareKeyboard assertion to branch on
keyboardDockSource: for "notification", compare the terminal dock edge against
keyboardDockTargetTop; for "layoutGuide", preserve the existing keyboardGuideTop
assertion.
🪄 Autofix
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: 8fbf6ff6-136a-4e8c-a720-b855779b6a08
📒 Files selected for processing (4)
Packages/iOS/CmuxMobileSupport/Sources/CmuxMobileSupport/UITestConfig.swiftPackages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceHostView.swiftPackages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swiftios/cmuxUITests/cmuxUITests.swift
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f74fa7a. Configure here.
There was a problem hiding this comment.
Actionable comments posted: 1
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)
8548-8572: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse the shared keyboard dock assertion.
Line 8548 through Line 8572 duplicates the target parsing and geometry checks in
assertTerminalDockPinnedToSoftwareKeyboard. Keep the explicit"notification"source assertion. Then call the shared helper. This keeps all keyboard dock tests on one assertion contract.🤖 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` around lines 8548 - 8572, In the test block around assertTerminalDockPinnedToSoftwareKeyboard, retain the explicit notification source assertion, remove the duplicated dockTargetTop/composerMinY/composerMaxY/toolbarMaxY parsing and geometry assertions, and invoke the shared assertTerminalDockPinnedToSoftwareKeyboard helper instead. Keep the existing notification-fallback context and helper contract.
🤖 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/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swift`:
- Around line 1005-1010: Remove keyboardHeightOverrideForTesting from the
production visibility decision in GhosttySurfaceView.swift:1005-1010, leaving
MobileKeyboardTransition as the sole source for willBeVisible. At
GhosttySurfaceView.swift:1062-1070, remove override-specific constraint updates
from the production notification path and relocate any unavoidable test forcing
to a dedicated debug-only facility outside Sources/**/*.swift.
---
Outside diff comments:
In `@ios/cmuxUITests/cmuxUITests.swift`:
- Around line 8548-8572: In the test block around
assertTerminalDockPinnedToSoftwareKeyboard, retain the explicit notification
source assertion, remove the duplicated
dockTargetTop/composerMinY/composerMaxY/toolbarMaxY parsing and geometry
assertions, and invoke the shared assertTerminalDockPinnedToSoftwareKeyboard
helper instead. Keep the existing notification-fallback context and helper
contract.
🪄 Autofix
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: 41ee14f1-35dd-4152-9253-f3a3780d9739
📒 Files selected for processing (2)
Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swiftios/cmuxUITests/cmuxUITests.swift
The guide-locked rewrite (#10594/#10687) left iOS 27 on a notification seat that consumes the full keyboard notification stream: did-frame disagreement reseats and steady-state tracker re-derivations. iOS 27 misreports keyboard frames outside the will transaction (#10518 recorded this when it quarantined the rebuilt path away from that OS), so those corrections moved a perfectly settled composer bar after toggles - the regression against the #9958/#10006 path that shipped will-only and was rated perfect in dogfood. Restore that contract, scoped so iOS <= 26 behavior is untouched: - iOS 27 seats ignore keyboardDidChangeFrame entirely and skip the steady-state tracker heal in layoutSubviews (attach recovery in didMoveToWindow stays - it fixes real workspace-switch wedges and did not exist on the old path). - Interrupted legs rebase from live presentation frames before the next will leg (the #10006 reversal contract), folding the live dock bottom into the seat constraint and re-deriving clip and wrapper models from that edge before stripping animations. - TerminalKeyboardSeatSelection (replacing the stale, unused TerminalKeyboardDockPathSelection) owns the seat decision table: guide seat on iOS <= 26, will-only notification seat on iOS 27+, full-stream notification seat behind the iOS <= 26 kill switch. - CMUX_UITEST_FORCE_IOS27_KEYBOARD_SEAT (DEBUG) runs the exact iOS 27 path on any simulator OS; the dock probe reports keyboardSeatWillOnly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The guide-locked rewrite (#10594/#10687) left iOS 27 on a notification seat that consumes the full keyboard notification stream: did-frame disagreement reseats and steady-state tracker re-derivations. iOS 27 misreports keyboard frames outside the will transaction (#10518 recorded this when it quarantined the rebuilt path away from that OS), so those corrections moved a perfectly settled composer bar after toggles - the regression against the #9958/#10006 path that shipped will-only and was rated perfect in dogfood. Restore that contract, scoped so iOS <= 26 behavior is untouched: - iOS 27 seats ignore keyboardDidChangeFrame entirely and skip the steady-state tracker heal in layoutSubviews (attach recovery in didMoveToWindow stays - it fixes real workspace-switch wedges and did not exist on the old path). - Interrupted legs rebase from live presentation frames before the next will leg (the #10006 reversal contract), folding the live dock bottom into the seat constraint and re-deriving clip and wrapper models from that edge before stripping animations. - TerminalKeyboardSeatSelection (replacing the stale, unused TerminalKeyboardDockPathSelection) owns the seat decision table: guide seat on iOS <= 26, will-only notification seat on iOS 27+, full-stream notification seat behind the iOS <= 26 kill switch. - CMUX_UITEST_FORCE_IOS27_KEYBOARD_SEAT (DEBUG) runs the exact iOS 27 path on any simulator OS; the dock probe reports keyboardSeatWillOnly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…10810) * fix(ios): unbreak the iOS build after the workspace-groups merge Two iOS-only compile errors from #10662 (the Mac target builds; both break every iOS build, so main's iOS lane is red): - MobileWorkspaceAggregation assigns a MobileWorkspaceGroupPreview.ID to anchorWorkspaceID, which is typed MobileWorkspacePreview.ID. Convert through rawValue, the same mapping the type's own initializer uses for exactly this empty-header fallback. - WorkspaceListTableCoordinator.groupActionCapabilities(for:) is fileprivate but called from the context-menu actions extension in WorkspaceListTableCoordinator+Actions.swift, a different file. Make it internal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test(ios): iOS 27 will-only keyboard seat must keep a settled dock still Red half of the regression pair. Forces the iOS 27 keyboard seat via CMUX_UITEST_FORCE_IOS27_KEYBOARD_SEAT and asserts the seat reports the will-only contract, a settled composer bar does not move without a will notification, and rapid reversals keep dock and render pinned. Red today because the force env and the will-only seat do not exist: iOS 27 hosts consume the full notification stream, whose non-will frames misreport on that OS and hop the settled dock. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * iOS 27: restore the will-only keyboard seat contract The guide-locked rewrite (#10594/#10687) left iOS 27 on a notification seat that consumes the full keyboard notification stream: did-frame disagreement reseats and steady-state tracker re-derivations. iOS 27 misreports keyboard frames outside the will transaction (#10518 recorded this when it quarantined the rebuilt path away from that OS), so those corrections moved a perfectly settled composer bar after toggles - the regression against the #9958/#10006 path that shipped will-only and was rated perfect in dogfood. Restore that contract, scoped so iOS <= 26 behavior is untouched: - iOS 27 seats ignore keyboardDidChangeFrame entirely and skip the steady-state tracker heal in layoutSubviews (attach recovery in didMoveToWindow stays - it fixes real workspace-switch wedges and did not exist on the old path). - Interrupted legs rebase from live presentation frames before the next will leg (the #10006 reversal contract), folding the live dock bottom into the seat constraint and re-deriving clip and wrapper models from that edge before stripping animations. - TerminalKeyboardSeatSelection (replacing the stale, unused TerminalKeyboardDockPathSelection) owns the seat decision table: guide seat on iOS <= 26, will-only notification seat on iOS 27+, full-stream notification seat behind the iOS <= 26 kill switch. - CMUX_UITEST_FORCE_IOS27_KEYBOARD_SEAT (DEBUG) runs the exact iOS 27 path on any simulator OS; the dock probe reports keyboardSeatWillOnly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * review: poll the settled dock over a window; document the attach-heal trust tradeoff CodeRabbit findings on #10810: the post-settle stability check now samples the dock edge five times over 1.5s and requires every sample at the settled edge (a transient hop between two single samples could previously pass), and healKeyboardModelFromTracker documents why the will-only seat keeps ATTACH recovery despite the tracker recording did frames iOS 27 can misreport (settled end frame at attach, next will leg corrects a bad record, and skipping recovery wedges the dock after workspace switches mid-transition). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

iOS 27 beta can leave the host keyboard layout guide at the screen bottom while the keyboard is visible. On OS major 27 only, the existing host-owned dock now uses keyboard frame notifications to update one bottom constraint with UIKit animation timing. Every other OS keeps the current layout-guide path. The focused UI test forces the iOS 27 policy on the hosted simulator, enters the production composer, and checks the dock and terminal viewport against the visible software keyboard.
Note
Medium Risk
Changes how keyboard height drives terminal chrome layout on iOS 27 only, with regression coverage; non-27 behavior remains on the layout-guide path.
Overview
On iOS 27, where
UIKeyboardLayoutGuidecan stay at the screen bottom while the software keyboard is visible, the terminal bottom dock (composer/toolbar) now follows keyboard frame notifications instead of the layout guide. A single manual bottom constraint is updated from notification overlap, animated with UIKit’s keyboard timing (includingkeyboardDidChangeFramewith zero duration when needed), and the terminal viewport stays aligned during the transition.All other OS versions keep pinning the dock to
keyboardLayoutGuide.topAnchor; layout-guide sync is skipped on the notification path so the stale guide does not compete.DEBUG / tests:
UITestConfig.forceIOS27KeyboardDockWorkaround(CMUX_UITEST_FORCE_IOS27_KEYBOARD_DOCK=1) forces the iOS 27 path on older simulators. Dock debug snapshots addkeyboardDockSourceandkeyboardDockTargetTop; UI tests assert the dock edge against the active source, and a new test opens the production composer under the forced policy and checks alignment with the visible keyboard.Reviewed by Cursor Bugbot for commit e99c3d8. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by cubic
Fixes keyboard dock tracking on iOS 27 by bypassing the broken
UIKeyboardLayoutGuideand driving the dock with keyboard frame notifications. Other iOS versions keep the layout-guide path; animations and the terminal viewport stay in sync.keyboardLayoutGuidepath on other iOS.UITestConfig.forceIOS27KeyboardDockWorkaround(env:CMUX_UITEST_FORCE_IOS27_KEYBOARD_DOCK=1) to force the iOS 27 path in CI.keyboardDockSourceandkeyboardDockTargetTopto validate geometry in tests.Written for commit e99c3d8. Summary will update on new commits.
Summary by CodeRabbit
New Features
Bug Fixes
Testing