Skip to content

Fix iOS 27 keyboard re-open animation - #10006

Merged
azooz2003-bit merged 19 commits into
mainfrom
fix-ios27-keyboard-toggle-animation
Aug 14, 2026
Merged

azooz2003-bit merged 19 commits into
mainfrom
fix-ios27-keyboard-toggle-animation

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Aug 11, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes the keyboard snapping upward after successive toggle taps on iOS 27.

The notification-driven dock now pairs keyboard frame transition legs by geometry and generation. A matching didChangeFrame settles the animation without replacing it with a zero-duration layout, while stale completions cannot overwrite the newest target. An unmatched completion can converge only before this observer has seen a transition.

Regression coverage posts an interrupted hide/show notification sequence against the real terminal surface.

Verification in progress:

  • iOS package-convention lint passed
  • Swift source parsing passed
  • hosted iPhone simulator suite dispatched
  • isolated iOS 27 simulator video and frame analysis pending

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Stops iOS 27 keyboard re-open snaps by animating the dock and terminal together in a host view and freezing renderer geometry until settle. Previously keyboardLayoutGuide could lag and reversals replaced in‑flight animations; now iOS 27 uses notification-driven transitions bound to a generation, rebases only when a prior leg is active, and keeps the rendered terminal edge pinned to the dock.

  • Changes
    • CmuxMobileTerminal: Adds GhosttySurfaceHostView to own terminal clipping, dock placement, and keyboard motion; uses notifications on iOS 27 and the system guide elsewhere; freezes the renderer during animation and folds the settled edge into the model; rebases from live frames before reversals; exposes host hooks (beginHostedKeyboardTransition, finishHostedKeyboardTransition, updateHostedKeyboardLayoutGuide); samples terminal–dock presentation gap; defers geometry sync while keyboard presentation is active; propagates theme background to the host.
    • CmuxMobileSupport: Extends MobileKeyboardTransition with beginFrame and beginOverlap(...) to pair legs and reject superseded completions.
    • CmuxMobileTerminal: Fences verified replay by draining pending native/local scroll and waiting for local apply; updates scroll paths to carry an interaction generation.
    • CmuxMobileTerminalKit: Adds .requestVisibleFocus to reassert UIKit focus without reloading input views, fixing the iOS 27 toggle focus handoff.
    • CmuxMobileShellUI: Defers verified replay reveal until restore/scroll require re-fencing; binds toast settings state.
    • Tests: UI tests require the terminal edge to track the dock through rapid and in-place keyboard-control reversals; unit tests cover stale/delayed keyboard notifications and scroll-drain fencing.
    • CI: iOS simulator artifacts build with ONLY_ACTIVE_ARCH=YES.

Written for commit 76728bd. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved iOS keyboard transition handling when frame information is incomplete.
    • Prevented outdated keyboard notifications from overriding newer transitions.
    • Improved keyboard-driven layout and dock positioning during overlapping show and hide animations.
    • Improved keyboard layout updates after view resizing or reattachment.
    • Preserved existing system layout guide behavior while making notification-based transitions more reliable.
  • Tests

    • Added coverage for overlapping transitions, stale events, view reattachment, and resizing scenarios.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds initial keyboard-frame overlap support and lifecycle tracking for overlapping iOS 27 keyboard notifications. GhosttySurfaceView uses lifecycle decisions and generations to animate, converge, settle, or ignore dock updates. Tests cover stale, detached, duplicate, and resized-view scenarios.

Changes

Keyboard transition handling

Layer / File(s) Summary
Keyboard geometry contract
Packages/iOS/CmuxMobileSupport/Sources/CmuxMobileSupport/MobileKeyboardTransition.swift
MobileKeyboardTransition stores beginFrame, falls back to endFrame when needed, and calculates overlap from either frame.
Notification lifecycle resolution
Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swift
KeyboardNotificationTransitionLifecycle records transition legs and generations. It settles matching completions, converges unknown completions, and ignores stale completions.
Surface integration and validation
Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swift, Packages/iOS/CmuxMobileTerminal/Tests/CmuxMobileTerminalTests/KeyboardNotificationTransitionLifecycleTests.swift
GhosttySurfaceView applies lifecycle decisions to dock geometry and resets state on detachment. Tests cover stale, detached, duplicate, and resized-owner overlap resolution.

Estimated code review effort: 4 (Complex) | ~45 minutes

Mergeability Score: 🟡 Moderate · up to a5a25

The PR improves keyboard animation handling during rapid toggles, but a legitimate repeated transition can still be ignored when it returns to earlier geometry, causing incorrect or missing animation in that sequence. This should be addressed before merge.

Sequence Diagram(s)

sequenceDiagram
  participant UIKit
  participant GhosttySurfaceView
  participant KeyboardNotificationTransitionLifecycle
  participant DockGeometry
  UIKit->>GhosttySurfaceView: Post keyboard frame notification
  GhosttySurfaceView->>KeyboardNotificationTransitionLifecycle: Resolve begin/end overlap
  KeyboardNotificationTransitionLifecycle-->>GhosttySurfaceView: Return decision and generation
  GhosttySurfaceView->>DockGeometry: Animate, converge, settle, or ignore dock update
Loading

Possibly related PRs

Suggested reviewers: lawrencecchen


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (2 errors, 1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Cmux Swift Package Boundaries ❌ Error The diff adds private KeyboardNotificationTransitionLifecycle in GhosttySurfaceView.swift; it is a CGRect/UInt64 state machine, while tests require GhosttyRuntime and UIWindow. Move the state machine to the CmuxMobileSupport target and expose MobileKeyboardNotificationTransitionLifecycle; keep notification and view-animation glue in GhosttySurfaceView.
Cmux No Test Or Debug Seam In Production Source ❌ Error The PR adds KeyboardNotificationTransitionLifecycle.Decision.debugName to production GhosttySurfaceView.swift; its new debug… member name matches the explicit seam failure condition. Rename the production logging label to a neutral name, or isolate a genuinely debug-only facility in a dedicated debug file; move test observation to Tests via @testable import. See #6452.
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Cmux User-Facing Error Privacy ❓ Inconclusive Placeholder only; investigation is still in progress. Awaiting repository evidence.
✅ Passed checks (21 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed The diff adds only Sendable value data and a private UI lifecycle struct; UI methods remain MainActor-bound, tests are @MainActor, and no Sendable reference, protocol, or background UI access is in...
Cmux Swift Blocking Runtime ✅ Passed The cumulative iOS diff adds notification-generation state and UIView animation handling; added-line scanning found no semaphores, waits, sleeps, delayed dispatch, main sync, polling, or locks.
Cmux Browser Automation Off-Main ✅ Passed The PR range changes only iOS keyboard transition code and tests; it adds no browser.* socket command, worker routing, WebKit/AppKit automation, or related policy behavior.
Cmux Expensive Synchronous Load ✅ Passed The PR diff only adds keyboard frame/lifecycle logic and UIKit tests; it adds no agent-history loader, file/JSON/JSONL read, directory scan, or synchronous cache access on an interactive path.
Cmux Cache Substitution Correctness ✅ Passed The diff only adds transient UIKit keyboard-frame and dock-animation lifecycle state; it does not replace reads in persistence, history, undo, or snapshot paths.
Cmux No Hacky Sleeps ✅ Passed The PR range changes only three .swift files. The rule explicitly scopes TypeScript, JavaScript, shell, and non-Swift build/runtime scripts, so this check is inapplicable.
Cmux Algorithmic Complexity ✅ Passed The new lifecycle scans only recentLegs, which is explicitly capped at 16; the other production scan is pre-existing, and added collection logic in tests is exempt.
Cmux Swift Concurrency ✅ Passed PR diff adds no DispatchQueue/DispatchGroup, Combine, or Task patterns; it adds synchronous lifecycle state and tests, while existing UIView/NotificationCenter callbacks remain framework boundaries.
Cmux Swift @Concurrent ✅ Passed The cumulative Swift diff adds no async, await, nonisolated, or @concurrent code. New work is synchronous; added @MainActor code is UI-bound and valid.
Cmux Swiftpm Lockfiles ✅ Passed The cumulative diff changes only two Swift sources and one test; no Package.swift, Package.resolved, .gitignore, workflow, or Xcode project files changed.
Cmux Swift Logging ✅ Passed The diff adds an OSLog log.debug with keyboard transition metadata only; it adds no print/NSLog/file logging and exposes no secrets or personal data.
Cmux Full Internationalization ✅ Passed The PR adds no user-facing copy or localization keys. New literals are internal state names in a log.debug call, and the diff changes no catalogs, locale files, or web messages.
Cmux Swiftui State Layout ✅ Passed The exact PR diff changes only UIKit-based MobileKeyboardTransition, GhosttySurfaceView, and UIKit tests; it adds no SwiftUI state, GeometryReader, lazy-row store, or render-time mutation.
Cmux Architecture Rethink ✅ Passed The PR keeps keyboard lifecycle state in the existing GhosttySurfaceView owner, uses paired platform callbacks, and adds no sleeps, delayed dispatch, polling, locks, or observer wiring.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed The PR changes UIKit views and test-only UIWindow fixtures only; it adds no NSWindow, NSPanel, controller, Window, or WindowGroup code. The auxiliary-window lint passed.
Cmux Source Artifacts ✅ Passed The PR changes only three .swift paths under Sources and Tests; no logs, screenshots, recordings, scratch directories, caches, build output, or dependency artifacts appear in the diff.
Cmux No Ambient Global State ✅ Passed Focused Swift diff adds a private, constructable lifecycle struct with instance state and methods, plus type-owned APIs; no new top-level mutable var, global API func, static namespace, or singleton.
Title check ✅ Passed The title clearly identifies the main change: fixing the iOS 27 keyboard re-open animation.
Description check ✅ Passed The description explains the change and verification status, but it omits the template's Demo Video, Review Trigger, and Checklist sections.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-ios27-keyboard-toggle-animation

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 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 131-140: In GhosttySurfaceView.swift lines 131-140, update the
keyboard completion handling to avoid recording or resolving transition legs
while the geometry owner has no window, allowing the first post-attach did
notification to use the converge path. In
KeyboardNotificationTransitionLifecycleTests.swift lines 54-78, add coverage
that posts will while detached, attaches the view, then posts did and asserts
convergence to the settled target.

In
`@Packages/iOS/CmuxMobileTerminal/Tests/CmuxMobileTerminalTests/KeyboardNotificationTransitionLifecycleTests.swift`:
- Around line 98-104: Update probeValues(_:) to construct the dictionary with a
uniquing initializer instead of Dictionary(uniqueKeysWithValues:), preserving
the parsed key/value pairs while allowing duplicate keys to be handled without a
runtime trap so the downstream test assertion can fail normally.
🪄 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: bbe051a6-a709-4bd3-8265-e44f47038e6e

📥 Commits

Reviewing files that changed from the base of the PR and between 006d2c8 and 2ee2b36.

📒 Files selected for processing (3)
  • Packages/iOS/CmuxMobileSupport/Sources/CmuxMobileSupport/MobileKeyboardTransition.swift
  • Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swift
  • Packages/iOS/CmuxMobileTerminal/Tests/CmuxMobileTerminalTests/KeyboardNotificationTransitionLifecycleTests.swift

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 77-89: Use MobileKeyboardTransition.beginFrame and endFrame,
rather than owner-dependent overlaps, as the identity stored and compared by
Leg.matches in GhosttySurfaceView.swift:77-89. At
GhosttySurfaceView.swift:1127-1135, pass raw notification frames to resolve
while retaining overlap calculations only for dock layout. In
KeyboardNotificationTransitionLifecycleTests.swift:25-78, add a
will/owner-resize/did scenario asserting the final dock target uses the
post-resize overlap.
🪄 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: c07768e1-1a0f-4b13-8a29-fc8d5d7a2c19

📥 Commits

Reviewing files that changed from the base of the PR and between 1237f6e and c9c0341.

📒 Files selected for processing (3)
  • Packages/iOS/CmuxMobileSupport/Sources/CmuxMobileSupport/MobileKeyboardTransition.swift
  • Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swift
  • Packages/iOS/CmuxMobileTerminal/Tests/CmuxMobileTerminalTests/KeyboardNotificationTransitionLifecycleTests.swift

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/Tests/CmuxMobileTerminalTests/KeyboardNotificationTransitionLifecycleTests.swift`:
- Around line 177-188: Update
KeyboardNotificationTransitionLifecycle.resolve(phase: .will, ...) to reject
duplicate or superseded will notifications instead of creating a new generation
during an active reversal. Preserve the authoritative typed lifecycle state and
keep the existing reversal target and keyboardUp values unchanged when the stale
hide leg arrives.
🪄 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: d17d62c1-e954-4a2a-98ea-6844b8df3c74

📥 Commits

Reviewing files that changed from the base of the PR and between cb528ea and 6ef20e5.

📒 Files selected for processing (1)
  • Packages/iOS/CmuxMobileTerminal/Tests/CmuxMobileTerminalTests/KeyboardNotificationTransitionLifecycleTests.swift

Comment on lines +177 to +188
// UIKit can deliver a duplicate of the superseded hide leg after the
// reversal starts. That older will must not reclaim the dock target.
postKeyboardFrameChange(
UIResponder.keyboardWillChangeFrameNotification,
beginFrame: shownFrame,
endFrame: hiddenFrame
)

let probe = probeValues(view.composerDockProbeValue)
#expect(probe["keyboardDockSource"] == "notification")
#expect(probe["keyboardTransitionTarget"] == "300.000")
#expect(probe["keyboardUp"] == "1")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject a superseded duplicate will notification before this test can pass.

KeyboardNotificationTransitionLifecycle.resolve(phase: .will, ...) currently records every will leg. The notification at Lines 179-183 creates a new hide generation. GhosttySurfaceView then applies a zero dock target and sets keyboardUp to 0.

Add stale-leg detection for duplicate will notifications, or otherwise preserve the active reversal generation. Do not change these assertions to match the stale hide target.

As per path instructions, keyboard lifecycle tracking must use one authoritative typed source and ignore stale notifications.

🤖 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
`@Packages/iOS/CmuxMobileTerminal/Tests/CmuxMobileTerminalTests/KeyboardNotificationTransitionLifecycleTests.swift`
around lines 177 - 188, Update
KeyboardNotificationTransitionLifecycle.resolve(phase: .will, ...) to reject
duplicate or superseded will notifications instead of creating a new generation
during an active reversal. Preserve the authoritative typed lifecycle state and
keep the existing reversal target and keyboardUp values unchanged when the stale
hide leg arrives.

Source: Path instructions

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 133-138: Update the stale-transition check in GhosttySurfaceView’s
transition handling to use an authoritative transition/event identity, not only
beginFrame/endFrame geometry, so a repeated A → B → A `will` event is accepted
rather than returned as `.ignoreStale`. Ensure the identity is available before
assigning the local generation, and add a regression test covering the A → B → A
sequence.
🪄 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: 3ef7cbc0-ec5c-4cd2-8c3d-a7bb3b918fee

📥 Commits

Reviewing files that changed from the base of the PR and between 6ef20e5 and a5a2590.

📒 Files selected for processing (1)
  • Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swift

@cursor

cursor Bot commented Aug 13, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@cursor

cursor Bot commented Aug 13, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@azooz2003-bit
azooz2003-bit merged commit e9fb69b into main Aug 14, 2026
6 checks passed
@azooz2003-bit
azooz2003-bit deleted the fix-ios27-keyboard-toggle-animation branch August 14, 2026 04:20
azooz2003-bit added a commit that referenced this pull request Aug 14, 2026
azooz2003-bit added a commit that referenced this pull request Aug 14, 2026
azooz2003-bit added a commit that referenced this pull request Aug 26, 2026
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>
azooz2003-bit added a commit that referenced this pull request Aug 26, 2026
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>
azooz2003-bit added a commit that referenced this pull request Aug 26, 2026
…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>
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