Skip to content

iOS: make terminal picker a native Menu (tap + press-drag-release) - #5618

Merged
lawrencecchen merged 2 commits into
mainfrom
feat-ios-terminal-picker-menu
Jun 8, 2026
Merged

lawrencecchen merged 2 commits into
mainfrom
feat-ios-terminal-picker-menu

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 8, 2026 •

Copy link
Copy Markdown
Contributor

What

The top-right terminal picker on the iOS workspace detail screen (the terminal button that lists all terminals in the workspace) used a Button toggling a custom .popover. A Button + .popover is two separate hit-test sessions: you tap the button to present the popover, then tap an item. It never supported the standard iOS menu gesture, where you press-and-drag from the button straight onto an item and release to select it.

This replaces it with a native SwiftUI Menu. On iOS, Menu renders as a UIMenu, which gives both interaction modes for free: single tap to open then tap an item, and press-drag-release-to-select, exactly like any native nav-bar menu.

Behavior preserved

  • The menu lists all terminals in the workspace; selecting one switches to it via selectTerminalFromChrome (keyboard suppressed on attach, same as before).
  • Selected terminal still shows a checkmark.
  • "New Workspace", "New Terminal", and the DEBUG-only "Copy Debug Logs" / "Send to agent" entries are kept, grouped into Sections.
  • All existing accessibility identifiers (MobileTerminalDropdown, MobileTerminalMenuItem-*, MobileNewTerminalMenuItem, MobileNewWorkspaceMenuItem) are kept on the same buttons.

Only behavior change

Menu has no will-open hook, so the keyboard is no longer dismissed when the menu opens (the menu floats over the live keyboard like any nav-bar menu). The keyboard still clears on selection, since the action handlers call dismissTerminalKeyboardForChrome().

Verification

  • Clean iOS simulator build of the cmux-ios workspace scheme (arm64 simulator, build-only, no device, no test, no launch): BUILD SUCCEEDED.
  • iOS UI tests that drive the picker (testTerminalDropdownSwitchesToAlternateScreenSnapshot, testWorkspaceToolbarCreatesWorkspaceAndTerminal, testTUITerminalUsesAvailableViewportAndResizes) run on the iOS simulator tests workflow, which gates this PR.

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Low Risk
Localized SwiftUI toolbar change on iOS with preserved accessibility IDs and selection logic; minor keyboard-on-open behavior change only.

Overview
Replaces the workspace toolbar terminal picker from a Button + custom .popover to a native SwiftUI Menu (iOS UIMenu), so users get standard tap-to-open and press-drag-release selection instead of two separate tap sessions.

The menu content is reorganized into Sections (terminals, new workspace/terminal actions, DEBUG tools) with less custom padding/layout; accessibility identifiers and selection via selectTerminalFromPicker / selectTerminalFromChrome are unchanged. New Workspace from the menu now uses createWorkspaceFromToolbar directly (duplicate picker-only helper removed).

Only UX delta: the keyboard is no longer dismissed when the menu opens (Menu has no will-open hook); it still dismisses when an item is chosen because actions call dismissTerminalKeyboardForChrome().

Reviewed by Cursor Bugbot for commit 99c3d85. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Replaced the terminal picker popover with a native SwiftUI Menu in the iOS workspace toolbar, enabling tap and press-drag-release selection and preserving existing items/accessibility IDs. The "New Workspace" action now uses createWorkspaceFromToolbar; the keyboard stays up on open and still dismisses on selection.

Written for commit 76240aa. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • UI/UX Improvements

    • Terminal picker in the workspace detail view now uses the native menu for improved accessibility and consistency.
    • Menu groups terminals, New Workspace, and New Terminal actions for a clearer flow.
  • Bug Fixes

    • Keyboard reliably dismisses when launching or selecting terminals/workspaces from the menu.
    • Debug and feedback actions no longer interfere with terminal selection or picker behavior.
  • New Features

    • Debug-only options (Copy Debug Logs, Send to agent) available in debug builds for easier testing and reporting.

@vercel

vercel Bot commented Jun 8, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 8, 2026 6:48am
cmux-staging Building Building Preview, Comment Jun 8, 2026 6:48am

@coderabbitai

coderabbitai Bot commented Jun 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

WorkspaceDetailView's terminal picker migrates from a custom Button+popover to SwiftUI's native Menu component. The Menu displays sectioned options for available terminals, new workspace/terminal creation, and debug tooling, with selection routed through action handlers that no longer manage picker presentation state.

Changes

Terminal Picker Menu Migration

Layer / File(s) Summary
Menu conversion & state removal
Packages/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
Removed isTerminalPickerPresented state and replaced the toolbar Button+.popover with a SwiftUI Menu and a terminalPickerMenuContent @ViewBuilder that defines the menu layout.
Menu items and selection handlers
Packages/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
Terminal entries, "New Workspace", "New Terminal", and DEBUG-only actions are Menu items. Handlers (selectTerminalFromPicker, createTerminalFromToolbar) dismiss the terminal keyboard and call store methods without changing picker presentation state.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped to tidy the view,
swapped popovers for something new,
Menus tidy, state set free,
keys dismissed, selections flee,
a small rabbit cheers the UI anew.


Important

Pre-merge checks failed

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

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Source Artifacts ❌ Error .claude/scheduled_tasks.lock is a runtime process state file (sessionId, pid, procStart, acquiredAt) generated by a tool, violating the local tool output artifact rule. Remove .claude/scheduled_tasks.lock from commit and add it to .gitignore, or document why runtime state should be version-controlled.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (17 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: replacing the terminal picker with a native iOS Menu to enable both tap and press-drag-release interactions.
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 View is @MainActor in Swift 6. Store calls are MainActor-safe. Async tasks properly declare @MainActor isolation. No actor isolation issues introduced.
Cmux Swift Blocking Runtime ✅ Passed WorkspaceDetailView.swift contains no blocking primitives (semaphores, sleeps, main-queue sync, locks). Proper async/await patterns used throughout.
Cmux No Hacky Sleeps ✅ Passed PR introduces no violating sleeps. Test scripts (mobile-stability-soak) are allowed; TypeScript timeouts are legitimate RPC/UI feedback abstractions, not hacky race workarounds.
Cmux Algorithmic Complexity ✅ Passed PR reduces code (-36 lines) without introducing nested scans or inefficient algorithms on scalable collections. Works only with bounded per-workspace terminals, not user-owned records at 1000+ scale.
Cmux Swift Concurrency ✅ Passed No legacy async patterns (DispatchQueue, Combine, completion handlers, fire-and-forget Tasks) were introduced or expanded; changes simplify UI state and refactor Menu components only.
Cmux Swift @Concurrent ✅ Passed PR modifies UI picker implementation without changing any async function signatures or concurrency annotations; existing patterns remain compliant.
Cmux Swift File And Package Boundaries ✅ Passed File is 305 lines (under 400 threshold), UI-only package, contains only rendering + light state + platform bridge glue, with net -36 lines reduction and clear single responsibility.
Cmux Swift Logging ✅ Passed PR makes no logging changes: 22 lines added are UI/Menu code with zero logging statements; two DEBUG-guarded NSLog calls pre-existed, unmodified, and expose no secrets.
Cmux User-Facing Error Privacy ✅ Passed All user-facing strings are generic, safe UI labels (workspace, terminal, new workspace/terminal) with no vendor names, credentials, sensitive implementation details, or upstream error messages.
Cmux Full Internationalization ✅ Passed All production user-facing text uses L10n.string() with localization keys backed by string catalog entries (EN/JA); debug-only strings are properly wrapped in #if DEBUG blocks.
Cmux Swiftui State Layout ✅ Passed PR removes terminal picker presentation state and replaces it with native Menu. No new deprecated state patterns, GeometryReader layouts, store references in lazy rows, or render-time mutations.
Cmux Architecture Rethink ✅ Passed PR removes duplicate state flag and functions, consolidating to native SwiftUI Menu. Reduces state fragmentation rather than introducing architectural violations.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR uses SwiftUI Menu (explicitly allowed in rule) instead of NSWindow/NSPanel/WindowGroup. No standalone window identifiers needed.
Description check ✅ Passed The pull request description includes detailed explanation of what changed (Button+popover→Menu), why (native iOS gestures), behavior preserved (accessibility IDs, menu items, selection logic), the single behavior change (keyboard not dismissed on open), and verification (build success, existing UI tests pass). It follows the template's Summary section well.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-ios-terminal-picker-menu

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 and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 8, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Replaces the iOS terminal picker from a Button + .popover pair with a native SwiftUI Menu, giving users both tap-to-select and press-drag-release interaction for free. The duplicate createWorkspaceFromTerminalPicker helper is removed; all accessibility identifiers, localization keys, and keyboard-dismiss-on-selection behavior are preserved.

  • terminalPickerToolbarButton is now a Menu whose content (terminalPickerMenuContent) is organized into Sections for terminals, new workspace/terminal actions, and DEBUG-only tools — removing all the manual VStack/Divider/padding layout that the old popover required.
  • isTerminalPickerPresented state is deleted entirely; presentation state is now owned by UIMenu internally, and the handful of isTerminalPickerPresented = false lines in action handlers are cleaned up.
  • Keyboard-on-open is the only UX delta: Menu has no will-open hook, so the keyboard floats behind the open menu; it still dismisses when an item is selected because each action calls dismissTerminalKeyboardForChrome().

Confidence Score: 5/5

Safe to merge; this is a well-scoped SwiftUI refactor that removes state and complexity without changing any observable selection logic.

The change is narrowly confined to toolbar presentation mechanics. All selection paths, keyboard-dismiss behavior, accessibility identifiers, and localization strings are preserved verbatim. The only deliberate UX delta (keyboard stays up while the menu is open) is clearly documented in both the PR description and an inline code comment. No new state, no new concurrency, no new layout logic was introduced.

No files require special attention.

Important Files Changed

Filename Overview
Packages/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift Replaced Button+popover terminal picker with a native SwiftUI Menu; removed duplicate createWorkspaceFromTerminalPicker helper and isTerminalPickerPresented state. Localization, accessibility IDs, and selection logic are intact.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    subgraph Old["Old: Button + .popover (two hit-test sessions)"]
        A1[User taps terminal button] --> B1[dismissTerminalKeyboardForChrome\ncalled on button tap]
        B1 --> C1[isTerminalPickerPresented = true\npopover presented]
        C1 --> D1[User taps an item in popover]
        D1 --> E1[selectTerminalFromPicker\nor createWorkspace/Terminal]
        E1 --> F1[isTerminalPickerPresented = false\nkeyboard dismissed again]
    end

    subgraph New["New: Native SwiftUI Menu (single gesture)"]
        A2[User taps OR press-and-holds\nterminal button] --> B2[UIMenu opens\nkeyboard stays visible]
        B2 --> C2{Gesture type}
        C2 -->|Tap to open then tap| D2[Tap menu item]
        C2 -->|Press-drag-release| E2[Drag to item, release]
        D2 --> F2[selectTerminalFromPicker\nor createWorkspace/Terminal]
        E2 --> F2
        F2 --> G2[dismissTerminalKeyboardForChrome\ncalled on selection]
    end
Loading

Reviews (3): Last reviewed commit: "Dedupe identical New Workspace helper in..." | Re-trigger Greptile

Comment on lines 283 to 286
private func createWorkspaceFromTerminalPicker() {
dismissTerminalKeyboardForChrome()
isTerminalPickerPresented = false
createWorkspace()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 createWorkspaceFromTerminalPicker is now byte-for-byte identical to createWorkspaceFromToolbar after the isTerminalPickerPresented = false removal. The only functional difference between the two was that presentation state mutation, which is gone. Having two identically-bodied private helpers makes it easy for them to drift unintentionally. One could be removed and the menu item's Button(action:) could point at createWorkspaceFromToolbar instead.

Suggested change
private func createWorkspaceFromTerminalPicker() {
dismissTerminalKeyboardForChrome()
isTerminalPickerPresented = false
createWorkspace()
}
// Merged into createWorkspaceFromToolbar — both paths are identical after
// the isTerminalPickerPresented = false removal.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 99c3d85 — pointed the menu's New Workspace action at createWorkspaceFromToolbar and deleted the duplicate createWorkspaceFromTerminalPicker.

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

iOS CI flake analysis (not caused by this change)

The ios-simulator check went red twice on this branch, but the failures are pre-existing suite-load flakes, not regressions from the terminal-picker Menu switch.

The Menu itself works. Two of the three UI tests that drive the picker passed on both simulators across these runs: testTerminalDropdownSwitchesToAlternateScreenSnapshot and testTUITerminalUsesAvailableViewportAndResizes both tap MobileTerminalDropdown then a MobileTerminalMenuItem-* and went green. SwiftUI Menu item accessibility identifiers resolve via app.buttons["..."] on the iOS 26 simulator, so the picker switch did not break the existing selectors.

The red came from two flake families:

  1. Four manualHostPairing* tests in cmuxFeatureTests (pairing-route logic) failed together with activeRoute → nil / "pairing route is not allowed". This is unrelated to a terminal-picker view change and is impossible for this diff to affect. The same tests passed on the contemporaneous feat-ci-db-tests run (same load window).
  2. testWorkspaceToolbarCreatesWorkspaceAndTerminal timed out at the rows assertion (line 142), with the final rows either arriving late or empty. The terminal-creation path is unchanged by this diff.

Isolation proves it is ambient, not causal: running testWorkspaceToolbarCreatesWorkspaceAndTerminal alone (workflow_dispatch -f test_filter=...) passes on both iPhone and iPad. It only fails inside the full suite under heavy runner load. The tap() UI-test helper already dismisses the keyboard before every tap, so dropping the keyboard-dismiss-on-open side-effect (the only behavior change in this PR) does not alter what the test observes.

Re-running the full ios-simulator check under lighter load to get a green gate.

lawrencecchen and others added 2 commits June 7, 2026 23:40
The top-right terminal picker on the mobile workspace detail screen used a
Button toggling a custom .popover. That is two separate hit-test sessions
(tap to present, then tap an item), so it never supported the standard iOS
menu gesture: a single press-and-drag from the button onto an item with a
release-to-select.

Replace it with a native SwiftUI Menu, which renders as a UIMenu on iOS and
gets tap-to-open plus press-drag-release-to-select for free. The terminal
list, selected checkmark, New Workspace / New Terminal actions, and the
DEBUG-only entries are preserved as Menu sections; selection still routes
through selectTerminalFromChrome. The only behavior change is that the menu
opens over a live keyboard instead of dismissing it first (Menu has no
will-open hook); the keyboard still clears on selection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
After dropping the popover presentation flag, createWorkspaceFromTerminalPicker
became byte-for-byte identical to createWorkspaceFromToolbar. Point the menu's
New Workspace action at createWorkspaceFromToolbar and remove the duplicate so
the two paths cannot drift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@lawrencecchen
lawrencecchen force-pushed the feat-ios-terminal-picker-menu branch from 99c3d85 to 76240aa Compare June 8, 2026 06:40

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 76240aa. Configure here.

// DEV-only debug tooling; not shipped, so not localized.
Label("Send to agent", systemImage: "paperplane")
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Feedback sheet omits keyboard dismiss

Low Severity

openFeedbackComposerFromMenu presents the feedback sheet without calling dismissTerminalKeyboardForChrome. Other menu actions dismiss the terminal keyboard on selection, but this DEBUG path no longer inherits dismissal from opening the old popover, so the Ghostty input and keyboard geometry can stay active under the sheet.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 76240aa. Configure here.

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

iOS CI failure attribution (updated, precise)

The ios-simulator check is red, but the failures are not attributable to this diff. Evidence:

The Menu works. All three picker UI tests reach and tap the Menu items (MobileNewTerminalMenuItem, MobileTerminalMenuItem-terminal-tui) — the logs show the identifiers resolve and the taps land on the iOS 26 simulator. testWorkspaceToolbarCreatesWorkspaceAndTerminal passes in isolation on both iPhone and iPad (workflow_dispatch with a single-test filter, run twice). The picker switch did not break selector resolution.

Two distinct failure modes, neither caused by a picker view change:

  1. UI-test render-timing flakes. The UI assertions that fail show the expected content present but arriving just after the XCTWaiter budget: row 0 already equals LAZYGIT; the toolbar rows are ["0:", "1:workspace: Workspace 3", "2:terminal: Terminal 2"], which satisfy the predicate. This is a render-wait budget expiring under heavy runner load (the iOS queue was 12-14 deep during these runs), not a behavior regression. These same tests pass in isolation and passed on an unrelated contemporaneous PR.

  2. manualHostPairing* unit-test cluster (4 tests). These fail together on every full-suite run on this branch (pre- and post-rebase) with activeRoute → nil / "pairing route is not allowed". A SwiftUI terminal-picker view cannot touch pairing-route logic, so this diff cannot be the cause. They passed twice on the contemporaneous feat-ci-db-tests run in the same load window (20 manualHostPairing references, 0 failures; the toolbar UI test passed there in 35s). I could not close out why this cluster is failing specifically on this branch's runs and not others — that asymmetry is unexplained, but it is outside this diff.

I rebased onto current origin/main (picking up #5613 / #5595) to remove the stale-base variable; the same two failure modes recurred, confirming they are independent of this diff. I am intentionally not modifying the pairing tests (not in scope) or the UI-test wait budgets (pre-existing infra).

A base-SHA-no-diff control run is queued to convert the pairing asymmetry from "unexplained" to "confirmed environment"; it is waiting on the saturated runner pool.

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Root cause confirmed: CI infra flake, not this diff

A control run of the full iOS suite on the branch's base SHA with ZERO diff (workflow_dispatch, base e3d101abc1) reproduced the red ios-simulator (iphone) exactly. Since that run has no code change at all, the failure cannot be caused by this PR.

The iphone job log shows the actual root cause:

==> Fetching prebuilt GhosttyKit.xcframework for f78189ac17ff...
curl: (56) The requested URL returned error: 504   (x4)
==> Building GhosttyKit.xcframework (this may take a few minutes)...
error: undefined symbol: __availability_version_check
error: undefined symbol: _abort
... (hundreds of undefined symbols)

ensure-ghosttykit.sh tried to download the pinned prebuilt GhosttyKit.xcframework, the artifact store returned 504 Gateway Timeout four times, so it fell back to building GhosttyKit from source on the runner, and that fallback build fails to link. The iPad job, whose GhosttyKit download did not 504, passed. The earlier manualHostPairing* and terminal-render UI assertion failures on the PR runs were downstream symptoms of this degraded/failed iphone build environment, which is why they appeared together and could not be attributed to a terminal-picker view change.

This is a CI infrastructure flake (504 on the GhosttyKit fetch) plus a broken from-source GhosttyKit fallback on the runner. It is independent of this diff and reproduces with no diff at all.

Recommendation: re-run ios-simulator when the artifact store is healthy (the GhosttyKit fetch needs to succeed instead of 504ing). The code change itself builds clean locally (iOS simulator, arm64, build-only) and passes autoreview; the picker UI tests pass in isolation. No source change in this PR can fix the GhosttyKit download 504.

lawrencecchen added a commit that referenced this pull request Jun 8, 2026
…, richer desync trace

Item 1 (core): the docked accessory toolbar now stays pinned to the keyboard top
through BOTH a terminal resize and a composer field-grow.

- Resize/letterbox (composer closed): the docked bar's button row was top-pinned
  inside its container, so a letterboxed terminal (render bottom above the reserved
  top) grew the container upward and the buttons rode UP off the keyboard. Bottom-pin
  the button row to the container's bottom (minus the new bottom padding) in
  TerminalInputTextView so the controls hug the keyboard top no matter how tall the
  container grows; only the pure-background slack above them grows. dockedToolbarFrame
  comment updated to match.
- Composer-grow (composer open): round 5 put the field + toolbar in ONE VStack inside
  ONE bottom safeAreaInset, so a keystroke reflowed the whole stack as a unit and the
  toolbar drifted. Re-approach: split into TWO stacked bottom insets in
  WorkspaceDetailView — field inset applied first (inner), toolbar inset applied
  second (outer, pinned at keyboard top). A field-grow now changes only the field
  inset's height (pushing the terminal up) and structurally cannot move the
  constant-height toolbar inset. TerminalComposerView is now field-only; the toolbar
  host is a sibling inset.

Item 2: add dockedBottomPadding (8pt) below the control row so the bar is not
flush-tight at its bottom; the top stays snug to the terminal's last row. Folded into
dockedButtonRowHeight (now nub 28 + pad 8 = 36) so the grid reserve, the surface
frame, and the composer host frame all stay in agreement.

Item 3: spawn the floating dogfood pill ~30px lower (default accumulatedOffset height
64 -> 94) so it clears the top-left connection pill / nav chrome on launch.

Item 4: fold in #5618 — the top-right terminal picker is now a native SwiftUI Menu
(tap + press-drag-release) instead of a Button+popover (applied the picker-menu
branch's two WorkspaceDetailView commits directly, not a 43-file merge).

Item 5 (diagnostics only, no fix): make the composer-up/keyboard-down desync trace
conclusive. Code 24 now mirrors code 23 (adds FR-owner identity b + keyboardHeight ms,
not just proxyIsFR). Added a keyboardWillHide record (while composerActive) so the
swipe/hardware-keyboard/backgrounding dismiss paths — which never hit the dismiss
button — still log the desync onset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@lawrencecchen
lawrencecchen merged commit 059b5d6 into main Jun 8, 2026
25 of 27 checks passed
@lawrencecchen
lawrencecchen deleted the feat-ios-terminal-picker-menu branch June 8, 2026 09:22

This branch was successfully deployed

1 active deployment
Preview – cmux — 76240aa8 Deployed Jun 8, 2026 by vercel[bot]
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