Skip to content

iOS #6271: top-right "+" adds a terminal to the current workspace - #6865

Closed
austinywang wants to merge 5 commits into
mainfrom
issue-6271-ios-top-right-button-makes-a
Closed

austinywang wants to merge 5 commits into
mainfrom
issue-6271-ios-top-right-button-makes-a

Conversation

@austinywang

@austinywang austinywang commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #6271

Problem

From TestFlight feedback (cmux BETA, iPhone): the prominent "+" button in the
top-right of the iOS workspace navbar — next to the terminal (>_) picker — spun
up a whole new workspace with a terminal. Testers found this unintuitive and
expected it to add a terminal to the current workspace (the macOS default).

The misleading button was the overlapping-cards plus.square.on.square
affordance. A prior change (#6362) removed that top-level button and buried both
"New Workspace" and "New Terminal" in the picker menu, so on current main there
was no obvious one-tap way to add a terminal at all.

Fix

  • Bring back a prominent top-bar "+" (newTerminalToolbarButton) immediately
    to the left of the terminal picker, in all three iOS navbars (terminal /
    browser / chat). It uses the plain plus glyph (macOS "new tab") and adds a
    terminal to the current workspace
    .
  • The action is pinned by a pure value type, MobileTerminalPrimaryAddButton, in
    CMUXMobileCore (the one mobile package CI exercises headlessly via
    swift test), so the wiring is guarded by a real CI gate and can't silently
    regress back to "new workspace".
  • New Workspace stays reachable from the picker menu (its layered
    plus.square.on.square glyph) — no capability lost.
  • createTerminalFromToolbar now also exits agent-chat mode (it already closed
    the browser) so the freshly-created terminal is actually surfaced from any
    overlay pane.
  • MobileNavTitleWidth reserves trailing width for the always-present "+" so a
    long workspace title can't underlap the bar buttons.

Tests

Two-commit red→green (verified locally):

  • Commit 1 ships MobileTerminalAddAffordanceTests with the affordance
    pinned to the buggy .newWorkspace → swift test --package-path Packages/Shared/CMUXMobileCore fails (3 expectations).
  • Commit 2 flips it to .newTerminalInCurrentWorkspace and wires the button
    → green.

The test runs in CI's mobile-core-package job (Packages/Shared/** path
filter). The SwiftUI toolbar wiring itself isn't headlessly unit-testable
(CmuxMobileShellUI is UIKit-bound and not in any CI test scheme), so the
guarded seam is the pure affordance contract plus the new
MobileTerminalNewTerminalButton accessibility identifier.

Localization

No new user-facing strings — the button reuses the already-localized
mobile.terminal.new ("New Terminal" / "新規ターミナル", en + ja).

🤖 Generated with Claude Code


Summary by cubic

Restores a top-bar “+” on iOS that adds a terminal to the current workspace, matching macOS and fixing #6271. Overlays only close when creation succeeds, and the new terminal is shown immediately.

  • Bug Fixes
    • Added an always-present “+” left of the terminal picker in terminal/browser/chat navbars; uses the plain plus glyph and adds a terminal to the current workspace (CmuxMobileShellUI).
    • Kept “New Workspace” in the picker menu with the layered icon; no capability removed.
    • Guarded overlay teardown: createTerminal now returns Bool; we only close the browser and exit agent chat after a successful creation (with preview tests for success/missing workspace).
    • Reserved nav title width for the “+” and trailing controls to prevent overlap; updated width tests.
    • Modeled the add affordances in CMUXMobileCore (MobileTerminalAddAffordance) and pinned the primary action and glyphs with a unit test to prevent regressions.

Written for commit cb26337. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added a prominent “New Terminal” (+) toolbar button in workspace views, with distinct options for adding to the current workspace or creating a new workspace.
  • Bug Fixes
    • Improved behavior so selecting the “+” button consistently exits chat/browser modes and shows the newly created terminal.
    • Updated title bar sizing to reserve space for the always-visible “+” button (and the optional chat toggle).
  • Tests
    • Added coverage to verify the primary “+” action and its system image/icon mapping.

… affordance

Regression test (red) for the TestFlight complaint that the prominent "+"
button next to the iOS terminal picker spins up a whole new WORKSPACE instead
of adding a terminal to the current one.

Models the two add affordances (new-terminal-in-current-workspace vs
new-workspace) as a pure value type in CMUXMobileCore — the only mobile package
CI exercises headlessly via `swift test` — so the contract is guarded by a real
CI gate. This commit ships the test with the affordance deliberately pinned to
the buggy `.newWorkspace`; the next commit flips it and wires the button.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 26, 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 Jul 5, 2026 3:25am
cmux-staging Building Building Preview, Comment Jul 5, 2026 3:25am

@coderabbitai

coderabbitai Bot commented Jun 26, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@austinywang, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 30 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c660c2c7-9d5b-4aab-93f9-79cedfa8086b

📥 Commits

Reviewing files that changed from the base of the PR and between 0f75651 and cb26337.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (9)
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/MobileTerminalAddAffordance.swift
  • Packages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/MobileTerminalAddAffordanceTests.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellCompositePreviewTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileLeadingToolbarTitleWidth.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+AgentChat.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+TerminalAdd.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileLeadingToolbarTitleWidthTests.swift
📝 Walkthrough

Walkthrough

Adds a public mobile terminal add-affordance enum, wires the iOS top-right “+” button to create a terminal in the current workspace, keeps the alternate new-workspace action available, updates trailing title width reservation, and adds tests for the affordance and icon mapping.

Changes

Mobile terminal add flow

Layer / File(s) Summary
Affordance contract
Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/MobileTerminalAddAffordance.swift, Packages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/MobileTerminalAddAffordanceTests.swift
Defines the current-workspace and new-workspace add affordances, pins the primary navbar button, and verifies the primary case and system image mappings.
Toolbar flow
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+TerminalAdd.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
Adds the “New Terminal” toolbar button, places it in the chat, browser, and detail toolbars, and routes the tap to terminal or workspace creation while resetting browser and chat state for terminal creation.
Title width reservation
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileNavTitleWidth.swift
Adds fixed trailing width for the always-present new-terminal button and includes it in the capped title width calculation.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant newTerminalToolbarButton
  participant performPrimaryAdd
  participant MobileTerminalAddAffordance
  participant WorkspaceDetailView

  User->>newTerminalToolbarButton: tap "+"
  newTerminalToolbarButton->>performPrimaryAdd: invoke action
  performPrimaryAdd->>MobileTerminalAddAffordance: read primaryNavbarButton
  MobileTerminalAddAffordance-->>performPrimaryAdd: newTerminalInCurrentWorkspace

  alt primaryNavbarButton == newTerminalInCurrentWorkspace
    performPrimaryAdd->>WorkspaceDetailView: createTerminalFromToolbar()
    WorkspaceDetailView->>WorkspaceDetailView: close browser / clear chat state
  else primaryNavbarButton == newWorkspace
    performPrimaryAdd->>WorkspaceDetailView: createWorkspaceFromToolbar()
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • manaflow-ai/cmux#5618 — Shares WorkspaceDetailView toolbar creation handlers and terminal/workspace routing changes.
  • manaflow-ai/cmux#6420 — Also changes MobileNavTitleWidth.cap(contentWidth:hasChatToggle:) and trailing-width reservation logic.
  • manaflow-ai/cmux#6362 — Touches the same WorkspaceDetailView new-workspace / new-terminal action surface.

Poem

🐰 I tapped the plus and hopped with glee,
one workspace stayed, a terminal could be.
New icon here, new icon there,
widths fit snug in rabbit air.

🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (24 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the iOS + button now adds a terminal to the current workspace and references the bug fix.
Description check ✅ Passed The description is detailed and covers the problem, fix, and testing, matching the core template requirements.
Linked Issues check ✅ Passed The code remaps the top-right + to add a terminal in the current workspace and keeps new workspace available in the picker.
Out of Scope Changes check ✅ Passed The added layout and overlay-handling updates are directly tied to surfacing the new terminal and preventing navbar overlap.
Cmux Swift Actor Isolation ✅ Passed PASS: The PR adds a pure Sendable enum and UI-only toolbar/view logic; no new implicit MainActor value models, unsafe Sendable refs, or background store access were introduced.
Cmux Swift Blocking Runtime ✅ Passed Edited Swift files add no semaphores, sync waits, sleeps, polling, or locks; only allowed UI animations appear.
Cmux Browser Automation Off-Main ✅ Passed PASS: The PR only adds iOS UI/core affordance code; it doesn't touch browser socket-automation commands, ControlCommandExecutionPolicy, or socket-worker routing.
Cmux Expensive Synchronous Load ✅ Passed Diff only adds toolbar/value-type wiring and state toggles; no expensive agent-history load or large-file parsing was introduced on main-actor/interactive paths.
Cmux Cache Substitution Correctness ✅ Passed Touched files only add a UI affordance enum/button and layout sizing; no persistence/history/undo/snapshot cache substitution path was introduced.
Cmux No Hacky Sleeps ✅ Passed PR changes are Swift-only, and the diff adds no sleeps, timers, polling, or fixed delays; the review rule is out of scope here.
Cmux Algorithmic Complexity ✅ Passed New code is O(1) or one-pass formatting; no nested scans, repeated sorts/filters, or batch rescans were added on scalable collections.
Cmux Swift Concurrency ✅ Passed The diff adds only synchronous enum/UI/layout code and sync toolbar actions; no new DispatchQueue, Combine, completion-handler, or fire-and-forget Task patterns were introduced.
Cmux Swift @Concurrent ✅ Passed The PR adds only sync UI wiring and pure helpers; no changed function has invalid/missing @concurrent, and the async refresh helper is intentionally UI-bound.
Cmux Swift File And Package Boundaries ✅ Passed Small focused UI glue plus a tiny testable core enum; only 11 lines touched in the 883-line view, with core logic extracted to CMUXMobileCore.
Cmux Swiftpm Lockfiles ✅ Passed No cmux-owned .gitignore ignores Package.resolved; vendor ignore is allowed, and the root Xcode Package.resolved is present with package refs.
Cmux Swift Logging ✅ Passed No added print/NSLog/Logger in touched code; the only NSLog found is an unchanged #if DEBUG helper, which the rule allows.
Cmux User-Facing Error Privacy ✅ Passed Patch adds toolbar affordance wiring/tests only; no user-facing error, alert, or recovery copy exposes disallowed details.
Cmux Full Internationalization ✅ Passed The new toolbar label uses existing localized key mobile.terminal.new; its catalog already has en/ja translations and no locale resources were changed.
Cmux Swiftui State Layout ✅ Passed PASS: The patch adds toolbar actions and a pure value-type affordance; no new ObservableObject/@published, GeometryReader, lazy-row store refs, or render-time state writes appear in the changed code.
Cmux Architecture Rethink ✅ Passed Adds a shared pure affordance contract and reuses one action path; no timing hacks, observers, or split ownership were introduced.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: the PR only changes toolbar/core SwiftUI code; no NSWindow/NSPanel/WindowGroup or cmux.* identifier assignments appear, so the auxiliary-window rule doesn’t apply.
Cmux Source Artifacts ✅ Passed All changed paths are intentional Swift source/test files; no logs, screenshots, caches, build outputs, or scratch dirs appear in the diff.
Cmux No Test Or Debug Seam In Production Source ✅ Passed No new test/debug seam was added in production Sources; the widened methods are used by real toolbar code, and the regression checks live in Tests/.
Cmux No Ambient Global State ✅ Passed The diff adds only value-type enum cases/static constants and instance methods; no new top-level funcs, globals, or singletons appear.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-6271-ios-top-right-button-makes-a

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.

@greptile-apps

greptile-apps Bot commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Restores an always-present "+" toolbar button in all three iOS workspace navbars (terminal/browser/chat) that adds a terminal to the current workspace, fixing issue #6271 where it was creating a whole new workspace instead. The action is guarded by a core unit test that pins MobileTerminalAddAffordance.primaryNavbarButton to .newTerminalInCurrentWorkspace, so a future refactor can't silently revert the behavior.

  • createTerminal() now returns Bool, allowing createTerminalFromToolbar() to guard overlay teardown: browser close and animated chat-mode exit now only fire after a successful creation, and a missing-workspace test validates the false path.
  • MobileLeadingToolbarTitleWidth gains newTerminalReserve: 52 so the always-present "+" is budgeted in the nav title truncation math, with updated tests asserting exact pixel values instead of loose upper bounds.
  • New Workspace remains accessible from the picker menu under its layered plus.square.on.square glyph; no capabilities are removed.

Confidence Score: 5/5

Safe to merge — the change is well-scoped iOS UI wiring with no shared-state mutations or concurrency hazards.

The new terminal button is pure UI, the core affordance contract is guarded by a headless unit test, createTerminal() returning Bool makes overlay teardown conditional on actual creation, and all math in the title-width reserve is backed by updated exact-value assertions. The previously flagged animation and namespace concerns are fully addressed in this revision. No actor-isolation, blocking-runtime, ambient-state, or localization issues are introduced.

No files require special attention.

Important Files Changed

Filename Overview
Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/MobileTerminalAddAffordance.swift New pure-value enum modeling the two add affordances; primaryNavbarButton is a static let constant (not ambient state) pinned to .newTerminalInCurrentWorkspace. Clean.
Packages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/MobileTerminalAddAffordanceTests.swift Regression-guard tests pin the primary button to .newTerminalInCurrentWorkspace and verify glyph mapping. Two distinct test cases cover the contract fully.
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift createTerminal() gains @discardableResult and returns Bool; false paths (in-flight task guard, missing workspace) are correctly handled. The remote path returns true eagerly on dispatch, consistent with its async nature.
Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellCompositePreviewTests.swift New createTerminalReportsFalseForMissingWorkspace test covers the false path; existing tests updated to assert the Bool return value. Clean additions.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+TerminalAdd.swift New extension file adds newTerminalToolbarButton and performPrimaryAdd(). The switch on the static-let affordance is a good future-proofing pattern; localization key is reused from the existing picker item.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift createTerminal closure type changed to () -> Bool; createTerminalFromToolbar and createWorkspaceFromToolbar widened from private to internal for the production cross-file extension split (not a test seam). Chat-mode exit now guarded and animated.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+AgentChat.swift Adds newTerminalToolbarButton to the trailing HStack before the picker; frame width updated from (96/44) to (148/96) to account for the always-present new-terminal button. Math is correct.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileLeadingToolbarTitleWidth.swift Adds newTerminalReserve: 52 (44pt button + 8pt HStack spacing) and folds it unconditionally into the trailing reserve. Clean.
Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileLeadingToolbarTitleWidthTests.swift Tests updated to include newTerminalReserve; iPhoneWidthCapsTitleBeforeTrailingControlsOverflow tightened to an exact value assertion (89pt with chat, 140pt without). titleGainsRoomWithoutBackButton switched to 320pt — calculation remains correct.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["User taps '+' (newTerminalToolbarButton)"] --> B["performPrimaryAdd()"]
    B --> C{"MobileTerminalAddAffordance\n.primaryNavbarButton"}
    C -->|".newTerminalInCurrentWorkspace\n(pinned by unit test)"| D["createTerminalFromToolbar()"]
    C -->|".newWorkspace"| E["createWorkspaceFromToolbar()"]
    D --> F["createTerminal() → Bool"]
    F -->|"false: no workspace / task in-flight"| G["Early return — overlays stay open"]
    F -->|"true: terminal created / dispatched"| H["dismissTerminalKeyboardForChrome()"]
    H --> I["browserStore.closeBrowser()"]
    I --> J["withAnimation: isChatMode = false"]
    J --> K["pinnedChatSessionID = nil"]
    K --> L["Terminal surfaces in detailSurfaceContent"]
    M["Picker menu 'New Terminal'"] --> D
    N["Picker menu 'New Workspace'"] --> E
    style C fill:#f0a,color:#fff
    style G fill:#faa
    style L fill:#afa
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["User taps '+' (newTerminalToolbarButton)"] --> B["performPrimaryAdd()"]
    B --> C{"MobileTerminalAddAffordance\n.primaryNavbarButton"}
    C -->|".newTerminalInCurrentWorkspace\n(pinned by unit test)"| D["createTerminalFromToolbar()"]
    C -->|".newWorkspace"| E["createWorkspaceFromToolbar()"]
    D --> F["createTerminal() → Bool"]
    F -->|"false: no workspace / task in-flight"| G["Early return — overlays stay open"]
    F -->|"true: terminal created / dispatched"| H["dismissTerminalKeyboardForChrome()"]
    H --> I["browserStore.closeBrowser()"]
    I --> J["withAnimation: isChatMode = false"]
    J --> K["pinnedChatSessionID = nil"]
    K --> L["Terminal surfaces in detailSurfaceContent"]
    M["Picker menu 'New Terminal'"] --> D
    N["Picker menu 'New Workspace'"] --> E
    style C fill:#f0a,color:#fff
    style G fill:#faa
    style L fill:#afa
Loading

Reviews (7): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment on lines +35 to +38
public enum MobileTerminalPrimaryAddButton {
/// What tapping the prominent "+" does.
public static let affordance: MobileTerminalAddAffordance = .newTerminalInCurrentWorkspace
}

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 Caseless enum used as a static namespace

MobileTerminalPrimaryAddButton is a caseless enum whose only purpose is to hold a single static let — exactly the pattern the no-ambient-global-state rule flags. The same constant can live as a static let member on MobileTerminalAddAffordance directly (via an extension), which avoids introducing a second uninhabitable type and keeps the test surface on the same type that owns the cases.

Rule Used: Flag new ambient global state in production Swift:... (source)

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.

Already addressed by the current shape: the primary binding lives directly on MobileTerminalAddAffordance.primaryNavbarButton; the separate caseless namespace enum is gone.

— Claude Code

Comment on lines +29 to +36
func performPrimaryAdd() {
switch MobileTerminalPrimaryAddButton.affordance {
case .newTerminalInCurrentWorkspace:
createTerminalFromToolbar()
case .newWorkspace:
createWorkspaceFromToolbar()
}
}

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 Chat mode exit is not animated, inconsistent with other exit paths

Both toggleChatMode() and the onExitChat closure wrap isChatMode = false in withAnimation(.snappy(duration: 0.28)), so the .move(edge: .top).combined(with: .opacity) transition plays. Here the assignment is bare, so tapping "+" while in chat mode instantly snaps the pane away rather than easing it out.

Suggested change
func performPrimaryAdd() {
switch MobileTerminalPrimaryAddButton.affordance {
case .newTerminalInCurrentWorkspace:
createTerminalFromToolbar()
case .newWorkspace:
createWorkspaceFromToolbar()
}
}
func performPrimaryAdd() {
withAnimation(.snappy(duration: 0.28)) {
switch MobileTerminalPrimaryAddButton.affordance {
case .newTerminalInCurrentWorkspace:
createTerminalFromToolbar()
case .newWorkspace:
createWorkspaceFromToolbar()
}
}
}

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.

Addressed in 4f321d0: the chat-mode exit path in createTerminalFromToolbar() now wraps isChatMode = false in withAnimation(.snappy(duration: 0.28)) after terminal creation is accepted.

— Claude Code

@austinywang
austinywang force-pushed the issue-6271-ios-top-right-button-makes-a branch 2 times, most recently from d4dd49b to 0f75651 Compare June 26, 2026 10:52

@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/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift`:
- Around line 843-852: Add a guard in createTerminalFromToolbar before tearing
down browser/chat state, because createTerminal() can no-op when workspace
lookup fails or a terminal creation task is already in flight. Use a
canCreateTerminal-style check (mirroring createWorkspaceFromToolbar and the
createTerminal(in:) early-return conditions) to confirm terminal creation can
actually proceed, and only then call browserStore.closeBrowser, clear
isChatMode/pinnedChatSessionID, and invoke createTerminal().
🪄 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

Run ID: 5412db22-dde6-477b-af17-3bb1ec9ccf52

📥 Commits

Reviewing files that changed from the base of the PR and between d4dd49b and 0f75651.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (5)
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/MobileTerminalAddAffordance.swift
  • Packages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/MobileTerminalAddAffordanceTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileNavTitleWidth.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+TerminalAdd.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift

TestFlight feedback: the prominent "+" button next to the iOS terminal picker
spun up a whole new WORKSPACE, which testers found unintuitive — they expected
it to add a terminal to the workspace they were already in (the macOS default).

- Flip `MobileTerminalPrimaryAddButton.affordance` to
  `.newTerminalInCurrentWorkspace`, turning the red regression test green.
- Add a prominent top-bar "+" (`newTerminalToolbarButton`) immediately left of
  the terminal picker in all three iOS navbars (terminal / browser / chat),
  routed through the pinned affordance to `createTerminalFromToolbar`. New
  Workspace stays reachable from the picker menu (its layered
  `plus.square.on.square` glyph), so no capability is lost.
- Surface the new terminal even from an overlay: `createTerminalFromToolbar`
  now also exits agent-chat mode (it already closed the browser).
- Reserve trailing width for the always-present "+" in `MobileNavTitleWidth`
  so a long workspace title can't underlap the bar buttons.

Reuses the already-localized `mobile.terminal.new` string (en + ja); no new
user-facing copy. Grows WorkspaceDetailView.swift by 5 lines; budget refreshed.

Fixes #6271

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@austinywang
austinywang force-pushed the issue-6271-ios-top-right-button-makes-a branch from 0f75651 to 63c4582 Compare June 26, 2026 11:01
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

@blacksmith-sh

This comment has been minimized.

…ght-button-makes-a

# Conflicts:
#	.github/swift-file-length-budget.tsv
@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — cb263377 Deployed Jul 5, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

iOS: top-right + button makes a new WORKSPACE+terminal; expected a new terminal in the same workspace

3 participants