Skip to content

iOS: native drag & drop in workspace list + create workspace in group - #7384

Merged
azooz2003-bit merged 45 commits into
mainfrom
feat-ios-workspace-dnd
Jul 8, 2026
Merged

azooz2003-bit merged 45 commits into
mainfrom
feat-ios-workspace-dnd

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Jul 5, 2026 •

Copy link
Copy Markdown
Collaborator

Adds native drag and drop to the iOS workspace list and a group-header context menu to create a workspace inside a group.

Mac side: new mobile workspace.move verb (dispatch in Sources/TerminalController.swift, implementation in Sources/TerminalController+WorkspaceMove.swift, ticket authorization in Sources/Mobile/MobileHostService.swift) applying group membership + ordering through the existing reorder/group logic, and an optional group_id threaded through the mobile workspace.create path. Also fixes a pre-existing downward before-move off-by-one in WorkspaceReorderCoordinator.workspaceReorderPlan(tabId:before:); the first commit adds the failing regression test, the second the fix (CI red→green on the Commits tab).

iOS side: long-press drag on workspace rows with drop targets on rows and group headers (reorder, move into a group at a position, drag out to ungroup). Drop intent is computed by a pure MobileWorkspaceDropIntentResolver in CmuxMobileShellModel with unit tests (reorder, header append, between members, ungroup, self-drop no-op), restricted to the displayed Mac's rows, disabled during search/filter and when disconnected. Group headers get a context menu "New Workspace in Group" which creates and selects the workspace in that group. New strings localized EN+JA with accessibility hints.

Tests: CmuxMobileShellModel, CmuxMobileRPC, CmuxMobileShell, CmuxWorkspaces package tests; cmuxTests/MobileHostAuthorizationTests gains four workspace.move ticket-scope cases. Note: MobileCoreRPCConnectWaiterTests.cancelledPostConnectWaiterDoesNotCloseTransportForSurvivor is a pre-existing flake under full-suite load (file untouched here; passes 5/5 in isolation).

🤖 Generated with Claude Code


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


Summary by cubic

Adds native drag‑and‑drop to the iOS workspace list and a full group‑header menu, including creating a workspace in a group. Actions are capability‑ and Mac‑scoped‑ticket gated, failures show in a single toast, and pending moves persist across refreshes.

  • New Features

    • iOS: Native List.onMove in flat and grouped lists via MobileWorkspaceListItem.moveIntent; supports reorder, move into a group, ungroup, and header‑anchor moves; disabled when filtered or when the snapshot spans multiple/unknown windows; group footers clarify in‑group vs top‑level drop zones.
    • UI: Group headers add Pin/Unpin, Rename (sheet), New Workspace in Group, and Ungroup/Delete with confirmations; the workspace detail title menu gates Rename/Mark Read/Close via capability‑aware closures and uses the same failure toast.
    • RPC + Capabilities: Adds workspace.move and workspace.group.action; workspace.create accepts optional group_id. The host advertises workspace.move.v1, workspace.group_actions.v1, and workspace.create_in_group.v1. The app shows move/group/create‑in‑group only when those are supported and a valid Mac‑scoped attach ticket is present; MobileCoreRPCClient exposes attachTicket and preserves it for workspace.move/workspace.group.action.
  • Bug Fixes

    • Auth: Preserve attach‑ticket context for workspace.move/workspace.group.action; ignore stale/unknown tickets for broad RPCs like workspace.list/workspace.create; enforce Mac‑scoped tickets for create‑in‑group; authorize workspace.action/workspace.close with workspace‑scoped tickets for the targeted workspace; trim and accept whitespace‑only workspaceID as Mac‑scoped; add scoped‑ticket regression tests and fix workspace‑ticket auth test compile.
    • Reorder: Fix downward before/after off‑by‑ones; header‑adjacent gaps are no‑ops; mixed/cross‑window snapshots are gated off; pending workspace moves survive list refreshes and reconcile after resync; reorder UI respects capability + Mac‑scoped‑ticket gating.
    • Create/Move: Disconnected createWorkspaceRequest returns notConnected and never creates locally; single‑flight is target‑aware; blank group_id ungroups; prevent duplicate error toasts for workspace create.
    • UI: Stabilize SwiftUI list diff keys to reduce flicker; fix iOS title‑menu type‑check by simplifying capability gating; make pending group destructive‑action state Equatable.
    • Tests: Stabilize the pairing retry deadline test to ensure a single connection attempt and deterministic teardown.

Written for commit 8f3a563. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added workspace moving and reordering, including moving workspaces between groups and placing them before another workspace.
    • Added the ability to create a new workspace inside a specific group.
    • Added a group header menu action for creating a workspace in that group.
    • Improved drag-and-drop behavior in workspace lists with clearer reorder guidance.
  • Bug Fixes

    • Workspace moves now use the correct authorization and routing behavior.
    • Reordering and list updates now stay in sync more reliably after moves and creates.

Note

Medium Risk
Broad changes to workspace mutation RPCs and attach-ticket authorization across shell, RPC client, and UI; incorrect gating or routing could allow unauthorized Mac-wide edits or mis-target secondary Macs, though coverage is heavy on ticket scope and encoding tests.

Overview
Adds native list reordering on iOS for flat and grouped workspace lists: List.onMove drives workspace.move on the Mac, with optimistic ordering, group footer rows for clearer in-group vs top-level drops, and a pure moveIntent resolver plus local order appliers in the shell model. Reorder is disabled while searching/filtering, when the snapshot isn’t a single known window, or when move capability / Mac-scoped attach ticket policy blocks it.

Extends Mac-backed workspace mutations beyond rename/pin/read/close: move, group actions (workspace.group.action), and create in group (group_id on workspace.create). Shell APIs now return Result<Void, MobileWorkspaceMutationFailure> instead of failing silently; the shell maps RPC/connection errors and surfaces failures via a bottom toast (create/move/group/detail actions share the same path). Group headers gain pin/rename, new workspace in group, ungroup/delete (with confirmations); workspace detail menus wire through the same capability-aware closures.

Authorization and RPC framing change for Mac-wide mutations: MobileShellWorkspaceMutationTicketPolicy allows move/group/create-in-group only when the attach ticket is non-expired and has an empty workspaceID (Mac-wide scope). MobileCoreRPCClient exposes attachTicket and always keeps attach-ticket context on workspace.move and workspace.group.action so workspace-scoped tickets are rejected on the host instead of falling back to Stack-only auth. Host capability flags workspace.move.v1, workspace.group_actions.v1, and workspace.create_in_group.v1 are ANDed with that ticket policy for UI and per-row actionCapabilities.

Create workspace is refactored into createWorkspaceRequest with single-flight coalescing (same group only; different group returns busy), no local create when disconnected or for in-group without connection, and create failures no longer set global pairing connectionError.

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

azooz2003-bit and others added 2 commits July 4, 2026 18:47
WorkspaceReorderCoordinator.workspaceReorderPlan(tabId:before:) plans toIndex
as the before-target's index with the dragged row still present, so
remove-then-insert places the dragged workspace after the target for any
downward move.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a mobile workspace.move verb (dispatch in TerminalController, body in
TerminalController+WorkspaceMove.swift, ticket auth in MobileHostService) that
applies group membership and ordering via the existing reorder/group logic,
and threads an optional group_id through the mobile workspace.create path.

iOS: long-press drag on workspace rows with drop targets on rows and group
headers (reorder, move into group at position, ungroup), drop intent computed
by a pure MobileWorkspaceDropIntentResolver in CmuxMobileShellModel (unit
tested), disabled during search/filter and when disconnected. Group headers
gain a context menu with New Workspace in Group. Strings localized EN+JA.

Fixes the downward before-move off-by-one in WorkspaceReorderCoordinator
(regression test in previous commit).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 5, 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 8, 2026 2:05am
cmux-staging Building Building Preview, Comment Jul 8, 2026 2:05am

@coderabbitai

coderabbitai Bot commented Jul 5, 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

This PR adds a workspace.move RPC method across the mobile RPC client, host authorization, and macOS TerminalController, plus a moveWorkspace API in MobileShellComposite, a MobileWorkspaceMoveIntent model with SwiftUI list move resolution, drag-and-drop reordering and group-scoped workspace creation in WorkspaceListView/WorkspaceShellView, and a macOS reorder planning index fix, with corresponding tests and localized strings.

Changes

Workspace move flow

Layer / File(s) Summary
Mac RPC and authorization
Sources/TerminalController+WorkspaceMove.swift, Sources/TerminalController.swift, Sources/Mobile/MobileHostService.swift, cmux.xcodeproj/project.pbxproj
Adds v2MobileWorkspaceMove for relocation/reordering, dispatches "workspace.move" in mobileHostHandleRPC, adds workspace-scoped ticket authorization via ticketWorkspaceAuthorizationError, and registers the new source file in the Xcode project.
Mac authorization tests
cmuxTests/MobileHostAuthorizationTests.swift
Adds workspace.move authorization tests (forbidden/allowed across foreign, matching, created, and mac-scoped workspaces) and a helper for building scoped workspace.move requests, plus formatting-only adjustments elsewhere.
iOS RPC client and composite APIs
Packages/iOS/CmuxMobileRPC/Sources/.../MobileCoreRPCClient.swift, .../MobileCoreRPCClientTests.swift, Packages/iOS/CmuxMobileShell/Sources/.../MobileShellComposite+WorkspaceActions.swift, MobileShellComposite.swift
Includes workspace.move in the Stack Auth fallback check, adds moveWorkspace and group-aware createWorkspace(inGroup:)/createRemoteWorkspace(inGroup:) on MobileShellComposite, with request-encoding and auth-context tests.
Move intent model and resolver
Packages/iOS/CmuxMobileShellModel/Sources/.../MobileWorkspaceMoveIntent.swift, MobileWorkspaceListItem+MoveIntent.swift, tests
Adds MobileWorkspaceMoveIntent and a moveIntent(...) resolver translating SwiftUI list moves into group-aware move intents, with tests for reorder/group/ungroup/no-op cases.
WorkspaceListView actions and drag/drop UI
Packages/iOS/CmuxMobileShellUI/Sources/.../WorkspaceListView+Actions.swift, WorkspaceListView+DragDrop.swift, WorkspaceListView.swift, WorkspaceGroupHeaderRow.swift, ios/cmux/Resources/Localizable.xcstrings
Adds selection/close-confirmation helpers, optimistic flat/grouped reorder logic gated by enablesWorkspaceReorder, group-scoped "New Workspace in Group" context menu, and drag accessibility strings.
WorkspaceShellView closures and wiring
Packages/iOS/CmuxMobileShellUI/Sources/.../WorkspaceShellView+WorkspaceActions.swift, WorkspaceShellView.swift
Adds rename/pin/unread/close/move and group action closures, group-scoped workspace creation helpers, and wires them into WorkspaceListView initializers while removing prior duplicate closure definitions.

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

macOS workspace reorder planning fix

Layer / File(s) Summary
Reorder index adjustment and tests
Packages/macOS/CmuxWorkspaces/Sources/.../WorkspaceReorderCoordinator.swift, Packages/macOS/CmuxWorkspaces/Tests/.../WorkspaceCoordinatorTests.swift
Adjusts before/after reorder target index calculation to account for the dragged tab's current index, adds two new downward-move tests, and applies formatting-only comment/blank-line changes elsewhere.

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

Sequence Diagram(s)

sequenceDiagram
  participant WorkspaceListView
  participant MobileShellComposite
  participant MobileCoreRPCClient
  participant TerminalController
  participant MobileHostService

  WorkspaceListView->>MobileShellComposite: moveWorkspace(id, groupID, beforeID)
  MobileShellComposite->>MobileCoreRPCClient: sendWorkspaceMutation("workspace.move")
  MobileCoreRPCClient->>TerminalController: mobileHostHandleRPC("workspace.move")
  TerminalController->>MobileHostService: ticketAuthorizationError(workspace.move)
  MobileHostService-->>TerminalController: authorization result
  TerminalController->>TerminalController: v2MobileWorkspaceMove(params)
  TerminalController-->>MobileCoreRPCClient: v2MobileWorkspaceList result
  MobileCoreRPCClient-->>MobileShellComposite: updated workspace list
  MobileShellComposite-->>WorkspaceListView: refreshed workspace order
Loading

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


Important

Pre-merge checks failed

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

❌ Failed checks (4 errors)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error TerminalController+WorkspaceMove.swift adds a new v2MainSync block, and v2MainSync is DispatchQueue.main.sync, a blocking main-queue hop in production. Replace the sync main-queue hop with async/MainActor-owned state access, or route the move mutation through an actor without DispatchQueue.main.sync.
Cmux Algorithmic Complexity ❌ Error WorkspaceListView+DragDrop.swift recomputes filteredWorkspaces/groupedWorkspaces inside .onMove, so each drag event can do repeated full filter/sort passes over ~1000 workspaces. Pass the same precomputed snapshot into moveIntent (or cache the derived list) so each move event uses one traversal.
Cmux Full Internationalization ❌ Error workspace.move adds new hardcoded English error responses in Sources/TerminalController+WorkspaceMove.swift with no matching localized catalog entries. Route those messages through String(localized:defaultValue:) (or equivalent) and add matching translated Localizable.xcstrings entries for every supported locale in that catalog.
Cmux Architecture Rethink ❌ Error WorkspaceListView adds optimisticFlatWorkspaces/optimisticGroupedItems plus onChange resets, a parallel order cache/observer side channel outside the store. Keep row order owned by one source of truth (store/coordinator); if optimism is needed, use a single pending-move token or snapshot-derived rendering, not parallel cached arrays.
✅ Passed checks (21 passed)
Check name Status Explanation
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 New UI callbacks hop back with Task { @mainactor }; MobileShellComposite, TerminalController, and MobileHostService are already @MainActor, and MobileWorkspaceMoveIntent is a plain Sendab...
Cmux Browser Automation Off-Main ✅ Passed PR only changes mobile workspace move/drag code; it does not touch browser.* automation paths or worker-router policy, so the off-main WebKit rule is not implicated.
Cmux Expensive Synchronous Load ✅ Passed Diff adds only UI move/create plumbing and workspace-move auth/reorder logic; no new synchronous agent-history/session JSONL load appears on interactive paths.
Cmux Cache Substitution Correctness ✅ Passed PASS: the optimistic workspace snapshots are transient UI state, have a nil fallback and onChange invalidation, and the persisted move RPC still uses authoritative IDs.
Cmux No Hacky Sleeps ✅ Passed No changed TypeScript/JS/shell/runtime script uses sleeps, timers, polling, or fixed waits; only build config and localized strings changed.
Cmux Swift Concurrency ✅ Passed No new legacy concurrency pattern: the added Tasks are SwiftUI/callback-boundary bridges, and no new DispatchQueue/Combine/completion-handler async work was introduced.
Cmux Swift @Concurrent ✅ Passed New async UI paths stay on MainActor or hop via Task/@mainactor; network work is delegated to the nonisolated RPC client, with no invalid or missing @concurrent use.
Cmux Swift File And Package Boundaries ✅ Passed Pass: the PR keeps move logic in small package files; added production Swift files stay under 400 lines, and only small, focused edits touch existing oversized UI/service files.
Cmux Swiftpm Lockfiles ✅ Passed PR only adds a source file to cmux.xcodeproj; no Package.resolved, Package.swift, or .gitignore changes, so the SwiftPM lockfile policy isn’t violated.
Cmux Swift Logging ✅ Passed No new/changed print/debugPrint/NSLog calls or Logger additions appeared in the touched files; the remaining Logger uses were pre-existing.
Cmux User-Facing Error Privacy ✅ Passed workspace.move errors are generic and only echo request UUIDs; no new user-facing text exposes vendor names, tokens, raw upstream messages, or other secrets.
Cmux Swiftui State Layout ✅ Passed Changed SwiftUI rows receive snapshots/closures; no new ObservableObject, GeometryReader, or render-time state writes were introduced.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Touched files are workspace-list/model/RPC code only; no NSWindow/WindowGroup/NSWindowController changes or cmuxAuxiliaryWindowIdentifiers edits were added.
Cmux Source Artifacts ✅ Passed Changed paths are hand-written source, tests, and localization assets; no logs, build output, caches, or scratch dirs appear in the diff.
Cmux No Test Or Debug Seam In Production Source ✅ Passed Changed production sources add only product drag/drop/move-intent logic; no new debug/test-only seam or #if DEBUG accessor appears in the diff.
Cmux No Ambient Global State ✅ Passed PASS: the new move API and helpers all live on existing types/extensions; I found no new file-scope funcs, mutable globals, or singletons.
Title check ✅ Passed The title clearly summarizes the main change: native iOS workspace drag/drop plus create-workspace-in-group support.
Description check ✅ Passed The description is detailed and on-topic, covering the main changes, rationale, and testing context.
✨ 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-workspace-dnd

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 5, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds native iOS drag-and-drop reordering to the mobile workspace list and the ability to create workspaces inside groups, backed by two new Mac-side RPC verbs (workspace.move, workspace.group.action) with full attach-ticket authorization gating.

  • Drag-and-drop: MobileWorkspaceListMoveIntentResolver translates SwiftUI List.onMove index pairs into typed MobileWorkspaceMoveIntent values; optimistic local reorder is applied immediately and cleared on server reconciliation via onChange observers. Synthetic groupFooter items split "inside group" vs "top-level" drop zones.
  • Authorization: MobileHostService.ticketAuthorizationResultIfNeeded correctly enforces Mac-scoped attach tickets for the three new mutation methods (workspace.move, workspace.group.action, and workspace.create with group_id), with three new capability tokens gating feature availability.
  • Off-by-one fix: WorkspaceReorderCoordinator.workspaceReorderPlan(tabId:before:after:) corrects downward-move index arithmetic (idx - 1 for before:, no + 1 for after: when moving downward).

Confidence Score: 5/5

Safe to merge — authorization logic is correct, off-by-one fix is verified, no data-loss or security issues found.

All authorization paths handle missing/stale tokens correctly. The off-by-one fix in WorkspaceReorderCoordinator is mathematically sound. Optimistic state is properly cleared on server reconciliation. Synthetic groupFooter placement is correct (anchor continue guard prevents double-emit). Full EN+JA localization with correct positional format specifiers.

No files require special attention.

Important Files Changed

Filename Overview
Sources/Mobile/MobileHostService.swift Adds ticketAuthorizationResultIfNeeded enforcing Mac-scoped attach tickets for workspace.move, workspace.group.action, and group-targeted workspace.create; all token-missing and stale-token branches handled correctly.
Sources/TerminalController+WorkspaceMove.swift New Mac-side v2MobileWorkspaceMove handler; correctly re-queries groupId after addWorkspaceToGroup to avoid reference-semantics stale read; mobileWorkspaceMoveTopLevelTargetIndex adjusts for source-removal index shift.
Sources/TerminalController+WorkspaceGroupAction.swift New Mac-side group action handler; guards memberCount > 0 before delete and memberCount < tabs.count to prevent closing all workspaces.
Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/Coordinators/WorkspaceReorderCoordinator.swift Off-by-one fix for downward moves: before: uses idx-1, after: drops the +1 for downward case; arithmetic is correct.
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkspaceListItem.swift Adds groupFooter synthetic item; footer placement for anchor-only groups is correct because anchor workspace is skipped by the existing continue guard before any append.
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkspaceListMoveIntentResolver.swift Pure resolver translating onMove index pairs to typed MobileWorkspaceMoveIntent; handles self-drop, collapsed groups, footer-adjacent drops, and no-op suppression correctly.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView+DragDrop.swift Optimistic reorder with isWorkspaceMovePending guard; onChange observers clear optimistic state on server reconciliation; hasSingleKnownWindow gates cross-window drag correctly.
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+WorkspaceCreateRequest.swift Tracks createWorkspaceTaskGroupID alongside in-flight task; returns .busy when a different groupID is targeted, preventing silent group-target drop.
Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCClient.swift Exposes attachTicket for Mac-scoped mutation gating; workspace.move and workspace.group.action correctly return false from ticket-reset check to preserve attach-ticket context.
ios/cmux/Resources/Localizable.xcstrings 522 lines of new EN+JA localizations; positional format specifiers (%1$@/%2$@) used correctly in JA composite strings where word order differs from EN.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User as iOS User
    participant LV as WorkspaceListView
    participant R as MoveIntentResolver
    participant Shell as MobileShellComposite
    participant RPC as MobileCoreRPCClient
    participant Auth as MobileHostService
    participant TC as TerminalController
    participant TM as TabManager

    User->>LV: Drag workspace row (onMove)
    LV->>R: moveIntent(sourceOffsets:destination:)
    R-->>LV: MobileWorkspaceMoveIntent
    LV->>LV: Apply optimistic order (MobileWorkspaceOrderMoveApplier)
    LV->>LV: "isWorkspaceMovePending = true"
    LV->>Shell: moveWorkspace(intent)
    Shell->>RPC: workspace.move RPC (with attachTicket)
    RPC->>Auth: ticketAuthorizationResultIfNeeded(request)
    Auth->>Auth: Check Mac-scoped attach ticket
    Auth-->>RPC: nil (authorized)
    RPC->>TC: v2MobileWorkspaceMove
    TC->>TM: addWorkspaceToGroup / moveWorkspace
    TM-->>TC: Updated tab order
    TC-->>RPC: Success response
    RPC-->>Shell: Result
    Shell-->>LV: Reconciled workspace list
    LV->>LV: onChange clears optimistic state
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 as iOS User
    participant LV as WorkspaceListView
    participant R as MoveIntentResolver
    participant Shell as MobileShellComposite
    participant RPC as MobileCoreRPCClient
    participant Auth as MobileHostService
    participant TC as TerminalController
    participant TM as TabManager

    User->>LV: Drag workspace row (onMove)
    LV->>R: moveIntent(sourceOffsets:destination:)
    R-->>LV: MobileWorkspaceMoveIntent
    LV->>LV: Apply optimistic order (MobileWorkspaceOrderMoveApplier)
    LV->>LV: "isWorkspaceMovePending = true"
    LV->>Shell: moveWorkspace(intent)
    Shell->>RPC: workspace.move RPC (with attachTicket)
    RPC->>Auth: ticketAuthorizationResultIfNeeded(request)
    Auth->>Auth: Check Mac-scoped attach ticket
    Auth-->>RPC: nil (authorized)
    RPC->>TC: v2MobileWorkspaceMove
    TC->>TM: addWorkspaceToGroup / moveWorkspace
    TM-->>TC: Updated tab order
    TC-->>RPC: Success response
    RPC-->>Shell: Result
    Shell-->>LV: Reconciled workspace list
    LV->>LV: onChange clears optimistic state
Loading

Reviews (24): Last reviewed commit: "Make workspace group pending action equa..." | Re-trigger Greptile

Comment on lines +2 to +4
public struct MobileWorkspaceDropIntentResolver: Sendable {
/// Creates a resolver.
public init() {}

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 Stateless struct is a hidden static namespace

MobileWorkspaceDropIntentResolver holds zero stored properties and its public init() body is empty. Every call site is MobileWorkspaceDropIntentResolver().intent(...) — the instance is constructed and immediately discarded. The three private helpers (validGroupID, workspaceAfterGroup, changesOrder) also operate purely on their arguments with no self access. Per the cmux no-ambient-global-state rule, an empty struct whose entire API is instance methods on stateless data is the same anti-pattern as a static-only namespace type. The canonical fix is to promote intent to public static func intent(workspaces:groups:draggedWorkspaceID:target:) and the three helpers to private static func, or expose the single public entry-point as a file-scoped free function — both are explicitly permitted by the rule and eliminate the meaningless init() allocation at every drop site.

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
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 92ec77e: resolver is now an enum with static intent/helpers; call sites no longer allocate.

Comment thread Sources/TerminalController+WorkspaceMove.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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/Coordinators/WorkspaceReorderCoordinator.swift (1)

193-205: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Adjust the after branch for downward moves

before already compensates for the removal shift; after still does idx + 1 unconditionally, so reorderWorkspace(tabId: a, after: b) can land a after the wrong sibling when a starts before b.

Suggested fix
         if let afterId {
             guard let idx = model.tabs.firstIndex(where: { $0.id == afterId }) else { return nil }
-            return workspaceReorderPlan(tabId: tabId, toIndex: idx + 1)
+            let targetIndex = currentIndex < idx ? idx : idx + 1
+            return workspaceReorderPlan(tabId: tabId, toIndex: targetIndex)
         }
🤖 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/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/Coordinators/WorkspaceReorderCoordinator.swift`
around lines 193 - 205, The after-branch in
workspaceReorderPlan(tabId:before:after:) is still using idx + 1
unconditionally, which can misplace items when the moved tab starts before the
afterId tab. Update the WorkspaceReorderCoordinator logic to mirror the
before-case adjustment by compensating for the removal shift when currentIndex
is less than the target index, then pass the corrected index into
workspaceReorderPlan(tabId:toIndex:).
🤖 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 `@cmuxTests/MobileHostAuthorizationTests.swift`:
- Around line 433-465: The new authorization tests in
MobileHostAuthorizationTests and the workspaceMoveAuthorizationError helper only
verify ticket scoping, but they do not cover the actual workspace move behavior
in TerminalController+WorkspaceMove. Add a behavior-level test for
v2MobileWorkspaceMove that exercises a reorder/move without group_id and asserts
the existing group membership is preserved, so the mutation logic is validated
directly instead of only the auth gate.

In
`@Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileWorkspaceListItemTests.swift`:
- Around line 181-246: Add test coverage for the untested .afterWorkspace path
in MobileWorkspaceDropIntentResolver.intent(), since current cases only cover
.beforeWorkspace and .groupHeader. Extend MobileWorkspaceListItemTests with
scenarios that exercise dropping after a workspace in both mid-list and
end-of-list positions, verifying the computed
MobileWorkspaceMoveIntent.beforeWorkspaceID uses the next item or nil when
appending. Use the existing helpers and symbols
MobileWorkspaceDropIntentResolver, MobileWorkspaceMoveIntent, and
.afterWorkspace to place the new tests alongside the current drop intent cases.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDragDropModifier.swift`:
- Around line 10-27: Replace the overlay-based GeometryReader in
WorkspaceDragDropModifier.body with onGeometryChange for the height measurement
used by dropTarget. Update the WorkspaceDragDropModifier view so it captures the
row height via onGeometryChange(for:of:action:) and uses that value inside the
dropDestination handler, keeping the existing isEnabled, draggable, and
performDrop flow intact. Avoid introducing any extra layout wrapper just to read
proxy.size.height.

In
`@Packages/macOS/CmuxWorkspaces/Tests/CmuxWorkspacesTests/WorkspaceCoordinatorTests.swift`:
- Around line 153-164: Add a sibling regression test in
WorkspaceCoordinatorTests for the downward after case handled by
reorderWorkspace(tabId:after:), mirroring
reorderWorkspaceBeforeDownwardMoveInsertsAtExpectedSlot. Use the same setup with
CoordinatorStubTab instances a, b, and c, call reorder.reorderWorkspace(tabId:
a.id, after: b.id), and assert the final tab order matches the expected
downward-move slot. This will cover the remaining off-by-one path in
WorkspaceReorderCoordinator.swift and lock in the fix for the after branch.

---

Outside diff comments:
In
`@Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/Coordinators/WorkspaceReorderCoordinator.swift`:
- Around line 193-205: The after-branch in
workspaceReorderPlan(tabId:before:after:) is still using idx + 1
unconditionally, which can misplace items when the moved tab starts before the
afterId tab. Update the WorkspaceReorderCoordinator logic to mirror the
before-case adjustment by compensating for the removal shift when currentIndex
is less than the target index, then pass the corrected index into
workspaceReorderPlan(tabId:toIndex:).
🪄 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: d5e7d668-05d6-4e9b-bd91-eca7a09871c5

📥 Commits

Reviewing files that changed from the base of the PR and between b3e791c and 8e06d1b.

📒 Files selected for processing (23)
  • Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCClient.swift
  • Packages/iOS/CmuxMobileRPC/Tests/CmuxMobileRPCTests/MobileCoreRPCClientTests.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+WorkspaceActions.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkspaceDropIntentResolver.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkspaceDropTarget.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkspaceMoveIntent.swift
  • Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileWorkspaceListItemTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDragDropModifier.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceGroupHeaderRow.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView+Actions.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView+DragDrop.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceShellView+WorkspaceActions.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceShellView.swift
  • Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/Coordinators/WorkspaceReorderCoordinator.swift
  • Packages/macOS/CmuxWorkspaces/Tests/CmuxWorkspacesTests/WorkspaceCoordinatorTests.swift
  • Sources/Mobile/MobileHostService.swift
  • Sources/TerminalController+WorkspaceMove.swift
  • Sources/TerminalController.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/MobileHostAuthorizationTests.swift
  • ios/cmux/Resources/Localizable.xcstrings

Comment thread cmuxTests/MobileHostAuthorizationTests.swift
@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

azooz2003-bit and others added 4 commits July 4, 2026 20:50
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The after branch planned toIndex in pre-removal index space; downward
moves landed one row too low. Mirror the before-branch fix by
compensating for the dragged tab's removal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… afterWorkspace drops

Greptile: the resolver held no state, so its instance API was a hidden
static namespace; promote intent and helpers to static on an enum.
CodeRabbit: add tests for the previously uncovered afterWorkspace
branch (mid-list insert and end-of-list append).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Read the post-mutation groupId from the model instead of the captured
reference so the anchor-protection guard stays correct even if the
coordinator replaces the workspace instance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment on lines +1448 to 1455
private static func ticketWorkspaceAuthorizationError(authorization: MobileAttachTicketAuthorization, workspaceSelection: String?) -> MobileHostRPCError? {
if let workspaceSelection, authorization.createdWorkspaceIDs.contains(workspaceSelection) { return nil }
let ticket = authorization.ticket
if !ticket.workspaceID.isEmpty {
guard let workspaceSelection, workspaceSelection == ticket.workspaceID else { return scopedTicketError }
}
return nil
}

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 The new ticketWorkspaceAuthorizationError does not trim ticket.workspaceID before comparing it against workspaceSelection, while the existing ticketTerminalAuthorizationError — which this function mirrors — always trims first. Two concrete failure modes follow: (1) a ticket whose workspaceID is pure whitespace (e.g. " ") is not caught by the isEmpty guard, so the comparison workspaceSelection == " " never matches a real UUID and every workspace.move on that ticket gets a forbidden response; (2) a ticket with trailing whitespace in workspaceID fails to match the correctly-formed workspaceSelection for the same reason. Both cases would surface as silent authorization failures that are hard to trace back to the missing trim.

Suggested change
private static func ticketWorkspaceAuthorizationError(authorization: MobileAttachTicketAuthorization, workspaceSelection: String?) -> MobileHostRPCError? {
if let workspaceSelection, authorization.createdWorkspaceIDs.contains(workspaceSelection) { return nil }
let ticket = authorization.ticket
if !ticket.workspaceID.isEmpty {
guard let workspaceSelection, workspaceSelection == ticket.workspaceID else { return scopedTicketError }
}
return nil
}
private static func ticketWorkspaceAuthorizationError(authorization: MobileAttachTicketAuthorization, workspaceSelection: String?) -> MobileHostRPCError? {
if let workspaceSelection, authorization.createdWorkspaceIDs.contains(workspaceSelection) { return nil }
let ticket = authorization.ticket
let ticketWorkspaceID = ticket.workspaceID.trimmingCharacters(in: .whitespacesAndNewlines)
if !ticketWorkspaceID.isEmpty {
guard let workspaceSelection, workspaceSelection == ticketWorkspaceID else { return scopedTicketError }
}
return nil
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 32881e7: workspaceID is trimmed before the empty check and comparison, mirroring ticketTerminalAuthorizationError, with tests for padded and whitespace-only ticket workspaceIDs.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Sources/TerminalController+WorkspaceMove.swift (1)

12-15: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Empty group_id is rejected even though it's meant to mean "no group".

mobileWorkspaceMoveGroupID intentionally treats a present-but-empty/whitespace group_id as nil (line 122). But the caller at Line 13 can't tell that apart from a malformed UUID string — both produce targetGroupID == nil while v2HasNonNullParam is true, so any client that sends group_id: "" to mean "ungroup" gets invalid_params instead of being ungrouped. Right now the only caller (iOS composite) omits the key entirely for ungroup, so this is a latent bug rather than an active one, but it contradicts the helper's own documented behavior and will misfire for any other caller/test that sends an explicit empty string.

🐛 Proposed fix to only flag genuinely malformed values
-        let targetGroupID = mobileWorkspaceMoveGroupID(params: params)
-        if v2HasNonNullParam(params, "group_id"), targetGroupID == nil {
-            return .err(code: "invalid_params", message: "Missing or invalid group_id", data: nil)
-        }
+        let targetGroupID = mobileWorkspaceMoveGroupID(params: params)
+        if v2HasNonNullParam(params, "group_id"), targetGroupID == nil,
+           let rawGroupID = v2RawString(params, "group_id")?.trimmingCharacters(in: .whitespacesAndNewlines),
+           !rawGroupID.isEmpty {
+            return .err(code: "invalid_params", message: "Missing or invalid group_id", data: nil)
+        }

Also applies to: 119-126

🤖 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 `@Sources/TerminalController`+WorkspaceMove.swift around lines 12 - 15, The
current validation in TerminalController+WorkspaceMove wrongly rejects an
explicitly empty or whitespace group_id even though mobileWorkspaceMoveGroupID
treats that as “no group”. Update the check around
mobileWorkspaceMoveGroupID(params:) and v2HasNonNullParam(params, "group_id") so
only genuinely malformed non-empty values return invalid_params, while an
empty/blank group_id is allowed to flow through as nil and ungroup the
workspace. Keep the nil-handling behavior aligned with
mobileWorkspaceMoveGroupID and adjust any related validation logic in the same
workspace-move path so explicit empty strings are not treated as errors.
🤖 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/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkspaceDropIntentResolver.swift`:
- Line 2: MobileWorkspaceDropIntentResolver is a caseless enum used only as a
static namespace, which conflicts with the no-ambient-global-state guideline.
Replace the enum with a constructable type and move the static helper(s) into
instance methods, then update the CmuxMobileShellUI call site(s) to use an
injected/resolved instance of MobileWorkspaceDropIntentResolver instead of
calling it statically.

---

Outside diff comments:
In `@Sources/TerminalController`+WorkspaceMove.swift:
- Around line 12-15: The current validation in TerminalController+WorkspaceMove
wrongly rejects an explicitly empty or whitespace group_id even though
mobileWorkspaceMoveGroupID treats that as “no group”. Update the check around
mobileWorkspaceMoveGroupID(params:) and v2HasNonNullParam(params, "group_id") so
only genuinely malformed non-empty values return invalid_params, while an
empty/blank group_id is allowed to flow through as nil and ungroup the
workspace. Keep the nil-handling behavior aligned with
mobileWorkspaceMoveGroupID and adjust any related validation logic in the same
workspace-move path so explicit empty strings are not treated as errors.
🪄 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: 227d9357-c193-41aa-a99c-edfe719211cb

📥 Commits

Reviewing files that changed from the base of the PR and between 8e06d1b and b2d7428.

📒 Files selected for processing (6)
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkspaceDropIntentResolver.swift
  • Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileWorkspaceListItemTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView+DragDrop.swift
  • Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/Coordinators/WorkspaceReorderCoordinator.swift
  • Packages/macOS/CmuxWorkspaces/Tests/CmuxWorkspacesTests/WorkspaceCoordinatorTests.swift
  • Sources/TerminalController+WorkspaceMove.swift

…-safe insertion mapping

On iPhone compact List, .dropDestination(for: String.self) never participated
as a drop target (drops ended with operation=0). Replace with onDrag
NSItemProvider plain-text vending, ForEach.onInsert(of:) for positional drops,
and onDrop(of:) on group headers. The insertion-index to drop-target mapping
lives in CmuxMobileShellModel as MobileWorkspaceListItem.insertionDropTarget,
never crosses group headers (ambiguous header-adjacent gaps are no-ops), and
is unit tested for all header-adjacent edge cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@blacksmith-sh

This comment has been minimized.

Mirror ticketTerminalAuthorizationError: a whitespace-padded ticket
workspaceID no longer causes silent forbidden responses, and a
whitespace-only workspaceID is treated as Mac-scoped. Adds tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileWorkspaceListItemTests.swift`:
- Around line 268-316: Add a missing test for the header-to-workspace boundary
in MobileWorkspaceListItemTests. The current insertionTarget coverage skips the
case where a drop occurs immediately after an expanded group header and before
its first member, which should resolve through insertionDropTarget’s (_,
.some(.workspace)) path. Add a test near the existing insertionTarget cases that
uses a groupHeader followed by its first indented workspace and asserts the
target is before that workspace.
🪄 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: cea38aa5-63b7-4444-8a68-98d919b84c89

📥 Commits

Reviewing files that changed from the base of the PR and between b2d7428 and 3ade789.

📒 Files selected for processing (8)
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkspaceListItem+DropTarget.swift
  • Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileWorkspaceListItemTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileWorkspaceDragPayload.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDragDropModifier.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView+DragDrop.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView.swift
  • Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/Coordinators/WorkspaceReorderCoordinator.swift
  • Packages/macOS/CmuxWorkspaces/Tests/CmuxWorkspacesTests/WorkspaceCoordinatorTests.swift

iOS List never delivers drops of its own rows to custom drop targets
(dropDestination, onDrop, onInsert all end with operation=0), so the
provider-based plumbing could not work. Use ForEach.onMove in both flat and
grouped presentations: MobileWorkspaceListItem.moveIntent maps onMove's
pre-removal (source, destination) to a move intent, derives group membership
from the landing gap, rejects header moves and identity drops, and is unit
tested. Optimistic row order applies locally and reconciles after the
authoritative Mac resync, including rollback on failed moves. Removes the
dead drop-target plumbing and its localization key.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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/WorkspaceListView`+DragDrop.swift:
- Around line 27-51: The drag-and-drop intent calculation is mixing snapshots:
`moveFlatRows` builds `items` from `sourceWorkspaces` but still passes
`filteredWorkspaces` into `MobileWorkspaceListItem.moveIntent`, which can cause
anchor resolution and no-op detection to use stale order. Update `moveFlatRows`
to pass the same snapshot used for `items` (and apply the same fix in
`moveGroupedRows`) so `moveIntent` evaluates against the optimistic/current
ordering consistently.
🪄 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: 0f2794ab-f415-4b23-abc7-4090698f59bd

📥 Commits

Reviewing files that changed from the base of the PR and between 32881e7 and 89fbc7e.

📒 Files selected for processing (7)
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkspaceListItem+MoveIntent.swift
  • Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileWorkspaceListItemTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView+DragDrop.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceShellView+WorkspaceActions.swift
  • cmuxTests/MobileHostAuthorizationTests.swift
  • ios/cmux/Resources/Localizable.xcstrings
💤 Files with no reviewable changes (1)
  • ios/cmux/Resources/Localizable.xcstrings

Group headers now carry the full workspace-style context menu: Pin/Unpin
Group, Rename Group (sheet), New Workspace in Group, and Ungroup / Delete
Group behind confirmations, backed by a new mobile workspace.group.action
verb (dispatch in TerminalController, body in
TerminalController+WorkspaceGroupAction.swift, anchor-scoped ticket auth
with tests) routed to the existing desktop group logic.

Workspace actions stay enabled while disconnected: every list mutation now
returns a typed Result, and failures surface in a shared bottom capsule
toast (X to dismiss, finger-tracking drag with 50% threshold and
spring-back, Clock-injected auto-dismiss with cancellation, one at a time)
instead of silently disabling affordances. Create keeps single-flight
dedup across both entrypoints.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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 using default effort 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 8f3a563. Configure here.

return .failure(.rejected(hostDisplayName: connectedHostName))
}
}
return .failure(.rejected(hostDisplayName: connectedHostName))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Duplicated error-to-failure mapping across two files

Low Severity

The MobileShellConnectionError-to-MobileWorkspaceMutationFailure mapping in createRemoteWorkspace's catch block is a near-exact copy of the workspaceMutationFailure helper in MobileShellComposite+WorkspaceActions.swift. The same switch over .connectionClosed, .requestTimedOut, .rpcError normalization list, etc. is inlined in both places. If a new error case or RPC code is added, both must be updated in lockstep or they'll silently diverge.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8f3a563. Configure here.

This branch was successfully deployed

1 active deployment
Preview – cmux — 8f3a563e Deployed Jul 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