Skip to content

Group tab context menu actions - #8208

Closed
lawrencecchen wants to merge 1 commit into
mainfrom
feat-tab-context-menu-grouping
Closed

lawrencecchen wants to merge 1 commit into
mainfrom
feat-tab-context-menu-grouping

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jul 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Group tab context actions by intent: current tab, create, arrange, contextual, view/state, and developer.
  • Add Close Tab beside Rename Tab.
  • Route context-menu closes through the shared close request path so pinned-tab and close-warning behavior stays consistent.
  • Preserve conditional browser, conversation, and SSH actions without empty separators.

Depends on manaflow-ai/bonsplit#186.

Verification


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


Summary by cubic

Reorganized the tab context menu by intent and routed context‑menu closes through the shared close path to keep pinned‑tab and close‑warning behavior consistent. Also adds Close Tab next to Rename Tab.

Written for commit 4c82096. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added support for closing tabs through the tab context menu.
    • Added the configured close-tab keyboard shortcut to the tab context menu.
    • Added a context-menu action for disconnecting remote connections while preserving their configuration.
  • Bug Fixes

    • Improved tab closing behavior when initiated through middle-click or the context menu.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c161c8c9-7ebc-4207-b83a-eb179b3cbdf4

📥 Commits

Reviewing files that changed from the base of the PR and between 6bbe17b and 4c82096.

📒 Files selected for processing (2)
  • Sources/Workspace.swift
  • vendor/bonsplit

📝 Walkthrough

Walkthrough

Workspace.swift aligns context-menu and middle-click tab closing, maps the close action to its shortcut, routes explicit close handling, and dispatches remote disconnection without clearing configuration. The Bonsplit submodule reference is also updated.

Changes

Tab context routing

Layer / File(s) Summary
Workspace tab action integration
Sources/Workspace.swift, vendor/bonsplit
Context-menu tab closes share middle-click close handling, .close uses the .closeTab shortcut and existing close flow, .disconnectRemote preserves configuration, and the Bonsplit revision is updated.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: austinywang, azooz2003-bit

🚥 Pre-merge checks | ✅ 25
✅ Passed checks (25 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately reflects the main change to tab context menu actions.
Description check ✅ Passed The description has a solid summary and verification section; it only omits some template boilerplate like demo video, review trigger, and checklist.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 Workspace and BonsplitDelegate are @MainActor; the new tab-context routing stays in that actor and adds no new sendability or background-access debt.
Cmux Swift Blocking Runtime ✅ Passed PASS: Diff only reroutes tab-close actions and menu entries; no new waits/sleeps/locks were added, and existing vendor/bonsplit blocking sites were unchanged.
Cmux Browser Automation Off-Main ✅ Passed The PR only changes tab-context menu handling in Workspace.swift and a bonsplit submodule pointer; no browser.* socket-worker routing or policy coverage was added or moved off-main.
Cmux Expensive Synchronous Load ✅ Passed Diff only remaps close/context-menu actions; no expensive sync agent-history load was added or moved onto a new interactive path.
Cmux Cache Substitution Correctness ✅ Passed Diff only routes tab-close/context-menu actions and menu plumbing; no persistence/history/snapshot read was replaced by a cache or opportunistic value.
Cmux No Hacky Sleeps ✅ Passed Diff only changes Swift code and a submodule pointer; no TypeScript/JS/shell/build/runtime sleep or polling logic was introduced.
Cmux Algorithmic Complexity ✅ Passed Changed code is O(1) switches plus a 4-item shortcut list; no nested scans, per-target rescans, or hot-path sorting/filtering were added.
Cmux Swift Concurrency ✅ Passed The diff only adjusts synchronous tab-context routing and a submodule pointer; it adds no new DispatchQueue, Combine, completion-handler, or fire-and-forget Task patterns.
Cmux Swift @Concurrent ✅ Passed Changed tab-close/context-action paths are synchronous; no @concurrent, nonisolated async, or UI-isolated heavy async helper was introduced.
Cmux Swift Package Boundaries ✅ Passed Diff only tweaks app-target tab/menu wiring in Workspace.swift and a vendored submodule; no reusable domain logic was moved into the app target.
Cmux Swiftpm Lockfiles ✅ Passed Only Sources/Workspace.swift and vendored submodule vendor/bonsplit changed; no cmux-owned .gitignore, Package.swift, or Package.resolved diffs violate the rule.
Cmux Swift Logging ✅ Passed The PR’s changed Swift hunks add no print/debugPrint/dump/NSLog/Logger usage; submodule diff also adds no logging lines.
Cmux User-Facing Error Privacy ✅ Passed PASS: The diff only adds generic tab actions/labels and close routing; no user-facing errors, alerts, or messages expose vendor/internal details.
Cmux Full Internationalization ✅ Passed The diff only changes context-menu behavior and adds already-localized Close Tab strings in Bonsplit’s en/ja resources; no new untranslated user-facing copy.
Cmux Swiftui State Layout ✅ Passed Changes only reroute tab-context actions and add a snapshot bool; no new SwiftUI state owners, GeometryReader layout measurement, lazy-row store refs, or render-time state writes.
Cmux Architecture Rethink ✅ Passed The diff only reuses the shared close-request path (.contextMenu and .close) and adds one existing remote-disconnect route; no new timing, observers, or duplicate lifecycle owner.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Diff only changes tab-close/context-menu routing in Workspace.swift and a bonsplit submodule bump; no standalone window IDs or close-shortcut ownership changed.
Cmux Source Artifacts ✅ Passed Changed paths are Workspace.swift and a deliberate vendor/bonsplit submodule bump; no logs, caches, build output, or scratch dirs were added.
Cmux No Test Or Debug Seam In Production Source ✅ Passed Changed Sources/Workspace.swift only routes tab-close/context actions; no DEBUG/test-only members, debug hooks, or visibility-widening seams were added.
Cmux No Ambient Global State ✅ Passed Diff only changes methods inside Workspace; no new file-scope funcs/vars/singletons or static-namespace helpers. Submodule update is pointer-only.
✨ 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-tab-context-menu-grouping

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 Jul 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Groups the tab context menu into intent-based sections (current tab, create, arrange, contextual, view/state, developer), adds a "Close Tab" item beside "Rename Tab", and routes context-menu closes through the shared onTabCloseRequest callback (new .contextMenu source, aliased to .middleClick) so pinned-tab gating and close-warning dialogs fire consistently. The vendor/bonsplit submodule is bumped to the companion PR that introduces the new TabContextAction cases and the .contextMenu close source.

  • Workspace.swift: adds .contextMenu to the onTabCloseRequest source switch (routed to markTabStripMiddleClickClose), registers the .close keyboard shortcut in buildContextMenuShortcuts, adds a no-op .close delegate case (bonsplit fires onTabCloseRequest independently), and wires up .disconnectRemote to disconnectRemoteConnection(clearConfiguration: false).
  • vendor/bonsplit: submodule bump delivers the context menu grouping, new actions, and close-source changes; the PR verifies 203 tests pass and English/Japanese localization files pass plutil -lint.

Confidence Score: 4/5

The close-tab routing and shortcut wiring changes are tight and coherent; the main risk to verify before merging is the disconnectRemoteConnection call that does not gate on the right-clicked tab's identity.

The new .disconnectRemote handler calls disconnectRemoteConnection(clearConfiguration: false) directly on the workspace without verifying that the right-clicked tab owns the remote session, which could disconnect a connection the user did not intend to affect when the menu is opened on a local tab while a remote pane is active. The rest of the changes (.contextMenu close routing, no-op .close delegate case, shortcut registration) are architecturally sound and consistent with the existing patterns.

The case .disconnectRemote: handler in Sources/Workspace.swift (around line 12658) and the bonsplit submodule's localization coverage beyond English and Japanese deserve a closer look before this lands.

Important Files Changed

Filename Overview
Sources/Workspace.swift Routes context-menu close through the shared tab-close request path (.contextMenu aliased to .middleClick), adds .close shortcut mapping, and implements .disconnectRemote and no-op .close delegate cases. Changes are surgical and coherent.
vendor/bonsplit Submodule bump adds new TabContextAction cases (.close, .disconnectRemote), groups context menu items, and introduces the .contextMenu close-source variant in the shared tab-close path. PR verification confirms 203 tests pass; bonsplit localization is verified for English and Japanese only.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant Bonsplit as Bonsplit (context menu)
    participant Delegate as Workspace (BonsplitDelegate)
    participant CloseReq as onTabCloseRequest

    User->>Bonsplit: right-click → Close Tab
    Bonsplit->>Delegate: didRequestTabContextAction(.close, tab)
    Delegate-->>Bonsplit: break (no-op — bonsplit owns close)
    Bonsplit->>CloseReq: "source = .contextMenu"
    CloseReq->>Delegate: markTabStripMiddleClickClose(surfaceId)
    Note over Delegate: tabStripCloseButtonByTabId[id] = false<br/>Pinned-tab / close-warning<br/>checks applied by bonsplit

    User->>Bonsplit: right-click → Disconnect Remote
    Bonsplit->>Delegate: didRequestTabContextAction(.disconnectRemote, tab)
    Delegate->>Delegate: disconnectRemoteConnection(clearConfiguration: false)
    Note over Delegate: Workspace-level disconnect,<br/>tab.id not used to guard
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"}}}%%
sequenceDiagram
    participant User
    participant Bonsplit as Bonsplit (context menu)
    participant Delegate as Workspace (BonsplitDelegate)
    participant CloseReq as onTabCloseRequest

    User->>Bonsplit: right-click → Close Tab
    Bonsplit->>Delegate: didRequestTabContextAction(.close, tab)
    Delegate-->>Bonsplit: break (no-op — bonsplit owns close)
    Bonsplit->>CloseReq: "source = .contextMenu"
    CloseReq->>Delegate: markTabStripMiddleClickClose(surfaceId)
    Note over Delegate: tabStripCloseButtonByTabId[id] = false<br/>Pinned-tab / close-warning<br/>checks applied by bonsplit

    User->>Bonsplit: right-click → Disconnect Remote
    Bonsplit->>Delegate: didRequestTabContextAction(.disconnectRemote, tab)
    Delegate->>Delegate: disconnectRemoteConnection(clearConfiguration: false)
    Note over Delegate: Workspace-level disconnect,<br/>tab.id not used to guard
Loading

Reviews (1): Last reviewed commit: "Group tab context menu actions" | Re-trigger Greptile

Comment thread Sources/Workspace.swift
Comment on lines +12658 to +12659
case .disconnectRemote:
disconnectRemoteConnection(clearConfiguration: false)

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.

P1 disconnectRemoteConnection ignores tab.id

The handler disconnects the workspace-level SSH connection without verifying that the right-clicked tab.id corresponds to the panel that owns the remote session. For a workspace where the context menu is shown on a local (non-SSH) tab while a remote connection exists in a sibling pane, this silently disconnects the connection the user did not intend to affect. Other SSH-related guard patterns in this switch (e.g. .reload, .toggleAudioMute) validate via panelIdFromSurfaceId before acting. At minimum, consider gating with guard panelIdFromSurfaceId(tab.id) != nil or checking remoteConfiguration ownership before dispatching.

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!

Comment thread vendor/bonsplit
@@ -1 +1 @@
Subproject commit 10563e2fda6fc18c47adf1864d55e0e25087a864
Subproject commit da56587cd6f1a618f3cf1b96011f6765eb95f377

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 Localization coverage limited to English and Japanese

The PR verification confirms plutil -lint for English and Japanese bonsplit string catalogs, but the cmux app ships strings across 20 locales (ar, bs, da, de, es, fr, it, km, ko, nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, zh-Hant in addition to en and ja). Any new user-facing text in the bonsplit context menu (e.g. "Close Tab", grouping separators, "Disconnect") that is defined in bonsplit's own string catalog needs translated entries for every locale the catalog already supports. If bonsplit's catalog covers more than two locales, the missing translations will fall back to English at runtime for those locales, violating the full-internationalization rule.

Rule Used: Flag production user-facing text that is not fully... (source)

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Superseded by the Rust cmux TUI implementation: #8225. This PR targeted the macOS tab strip by mistake.

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