Skip to content

Chrome-style live sidebar reorder: rows animate on mousemove, groups drag as blocks - #8467

Closed
lawrencecchen wants to merge 14 commits into
mainfrom
task-sidebar-fluid-reorder
Closed

lawrencecchen wants to merge 14 commits into
mainfrom
task-sidebar-fluid-reorder

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jul 19, 2026 •

Copy link
Copy Markdown
Contributor

Dragging in the AppKit sidebar previously showed only a 2px indicator line; the reorder happened after mouseup. Now drags that begin in the sidebar animate rows apart around a moving slot on every mousemove, Chrome-vertical-tabs style: a floating snapshot of the dragged row tracks the pointer (X-locked, lift shadow), the origin slot closes immediately, and escape or dropping outside animates everything back with no model change.

Group headers are now draggable and move their whole group as one block (the drop resolver already planned anchor drags as top-level block moves; the table just refused to start them). Dragging a workspace across a group's member span previews the join live and the floating row indents; the collapsed-group and pinned-tier semantics come from the existing resolver unchanged.

Mechanism: the system NSDraggingSession stays alive underneath. Our own drop overlay hides the drag image destination-side (it auto-restores on exit, so cross-window drags and bonsplit interop keep today's indicator path), resolves the same SidebarWorkspaceReorderDropResolver plan on every draggingUpdated, and a new pure permutation resolver (CmuxFoundation, unit-tested) maps that plan's indicator onto a visible row order which the table animates via moveRow. The model commits once on drop from the last previewed plan, so preview and commit agree by construction. Authoritative applies landing mid-drag are deferred when content-only and degrade the session to the classic indicator path when structural.

Verified on a fleet Mac (aws-m4pro-4, ardrec GUI session) with real cursor drags over the ARD protocol: mid-drag frames show rows reordered before mouseup for a workspace drag, a group-header block drag, and a group join with indent; escape restored the original order with no commit. 10 new unit tests on the permutation resolver pass.


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


Summary by cubic

Adds Chrome‑style live reorder in the sidebar. Rows animate around a moving slot with a floating snapshot; group headers drag their whole block. Direct pointer tracking runs local drags and hands off cleanly to the system session.

  • New Features

    • Live preview with a floating row (indent from commit destination, “+N” badge), block‑center thresholds, a sticky last‑member boundary, and a “keep current preview while riding the block” rule; a no‑op zone restores on return to origin. The widened corridor keeps preview/commit even slightly outside the sidebar.
    • The system drag session stays active: the drag image hides on enter and auto‑restores on exit; cross‑window/foreign drags keep the classic indicator; Esc cancels with no commit.
  • Refactors

    • Introduced SidebarWorkspaceDirectReorderGesture and direct pointer tracking in the table to own press/drag with a drag threshold and a reliable system‑drag handoff; rebuilt the local session around immutable drag state with SidebarWorkspaceTableLocalReorderSession.
    • In CmuxFoundation, added SidebarWorkspaceReorderMovePlanner and SidebarWorkspaceReorderPreviewPermutation; resolver accepts stickyDestination and the preview refreshes targets after each permutation. Localized the “+N” badge string.
    • Tests: unit tests for the planner, permutation, sticky boundary, corridor, and gesture lifecycle; new XCUITests for slow/held drags and reorder gestures.

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

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added continuous local live-reorder previews in the sidebar for both individual workspaces and grouped headers, including an animated floating “dragged block” with correct indentation and a “+N” indicator for omitted snapshots.
    • Introduced smarter reorder planning to generate efficient table move steps and preview permutations.
  • Bug Fixes
    • Improved drag-and-drop lifecycle handling, including reliable deferred apply behavior, hover reconciliation during active previews, and consistent drag-image suppression.
    • Refined press/selection flow to prevent double-applying selection during drags.
  • Tests
    • Expanded unit tests for reorder preview permutations and move-step planning.
  • Documentation
    • Updated inline guidance for the reorder preview/commit flow.

Drags that begin in the sidebar table now animate rows apart around a
moving slot while a floating snapshot of the dragged row tracks the
pointer; group headers are draggable and move their whole block. The
system drag session stays alive underneath (image hidden while over
the source sidebar), so cross-window drops and bonsplit interop keep
the existing indicator path. The model commits once on drop from the
same resolved plan the preview showed; escape or dropping outside
restores the original order.
@coderabbitai

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

Adds live workspace drag-reorder previews for the AppKit sidebar, including grouped permutation logic, validated table move planning, floating drag visuals, deferred applies, and controller/action wiring for resolving and committing reorder plans.

Changes

Workspace reorder preview

Layer / File(s) Summary
Preview permutation and coverage
Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/SidebarDrop/SidebarWorkspaceReorderPreviewPermutation.swift, Packages/macOS/CmuxFoundation/Tests/CmuxFoundationTests/SidebarWorkspaceReorderPreviewPermutationTests.swift
Defines preview row/order models, computes grouped and ungrouped insertion permutations, and tests valid, no-op, grouped, and invalid indicators.
Table move planning and validation
Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/SidebarDrop/SidebarWorkspaceReorderMovePlan.swift, Packages/macOS/CmuxFoundation/Tests/CmuxFoundationTests/SidebarWorkspaceReorderMovePlanTests.swift
Generates validated table-row move steps and tests single-row, grouped, reversible, exhaustive, and rejected reorder plans.
Local reorder session lifecycle
Sources/Sidebar/AppKitList/SidebarWorkspaceTableLocalReorderSession.swift
Adds drag-session state management, floating row snapshots, animated previews, drop settlement, cancellation handling, and deferred apply coordination.
Table integration and interaction wiring
Sources/Sidebar/AppKitList/SidebarWorkspaceTableController.swift, Sources/Sidebar/AppKitList/SidebarWorkspaceTableActions.swift, Sources/ContentView.swift, Sources/SidebarWorkspaceReorderDropView.swift, Sources/Sidebar/AppKitList/SidebarWorkspaceTableViewImpl.swift, cmux.xcodeproj/project.pbxproj, cmuxTests/SidebarWorkspaceTableTests.swift, Resources/Localizable.xcstrings
Connects plan resolution and commit callbacks to drag lifecycle events, selection, row hiding, hover handling, drop routing, compilation, test fixtures, and floating-row localization.

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

Sequence Diagram(s)

sequenceDiagram
  participant NSTableView
  participant SidebarWorkspaceTableLocalReorderController
  participant SidebarWorkspaceTableController
  participant SidebarWorkspaceTableActions
  NSTableView->>SidebarWorkspaceTableLocalReorderController: Start workspace drag
  SidebarWorkspaceTableLocalReorderController->>SidebarWorkspaceTableController: Resolve preview plan
  SidebarWorkspaceTableController->>SidebarWorkspaceTableActions: resolveWorkspaceReorderPlan
  SidebarWorkspaceTableActions-->>SidebarWorkspaceTableController: Return reorder plan
  SidebarWorkspaceTableController->>NSTableView: Animate preview order and hide moving rows
  SidebarWorkspaceTableLocalReorderController->>SidebarWorkspaceTableActions: commitWorkspaceReorderPlan
Loading

Possibly related PRs


Important

Pre-merge checks failed

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

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

Check name Status Explanation Resolution
Cmux Algorithmic Complexity ❌ Error Sources/Sidebar/AppKitList/SidebarWorkspaceTableLocalReorderSession.swift:339 still scans all rows and does current.blockRowIds.contains(...) per row on a drag hot path. Replace the filter/contains loop with a Set-based single pass (or cached block indices) so pointer checks and settle/restore paths stay linear for ~1000 rows.
Cmux Full Internationalization ❌ Error New sidebar.reorder.floating.moreRows uses String(localized:) but Resources/Localizable.xcstrings only has en/ja; the catalog already supports 20 locales. Add translations for ar, bs, da, de, es, fr, it, km, ko, nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, and zh-Hant for the new key.
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Cmux Swift Logging ❓ Inconclusive placeholder2 pending2
✅ Passed checks (21 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed New UI types are intentionally @MainActor; new Foundation models are pure Sendable values, with no added shared-mutable Sendable refs or background-store access.
Cmux Swift Blocking Runtime ✅ Passed New reorder/session code uses callbacks and UI animation timing only; no added semaphores, sleeps, sync waits, polling, or locks.
Cmux Browser Automation Off-Main ✅ Passed The commit only changes Sources/Sidebar/AppKitList/SidebarWorkspaceTableLocalReorderSession.swift; no browser.* routing, socketWorkerMethods, or ControlCommandExecutionPolicy changes are present.
Cmux Expensive Synchronous Load ✅ Passed No agent-history loader or large-file parse was added; the only changed code is sidebar drag/reorder logic, with no RestorableAgentSessionIndex.load or similar on main/interactive paths.
Cmux Cache Substitution Correctness ✅ Passed Only transient drag-preview/session state is cached; no persistence/history/undo/snapshot path swaps a fresh authoritative read for a cache.
Cmux No Hacky Sleeps ✅ Passed PASS: HEAD changes only a Swift file; the no-hacky-sleeps rule covers non-Swift runtime code, and no changed non-Swift files contain sleeps/delays.
Cmux Swift Concurrency ✅ Passed New code stays synchronous; the only completion handler is an AppKit animation boundary, and no new DispatchQueue/Combine/fire-and-forget Task pattern was added.
Cmux Swift @Concurrent ✅ Passed Checked the changed Swift files and the new code is synchronous or @MainActor-bound; no added @concurrent, nonisolated async, or un-hopped heavy async call sites.
Cmux Swift Package Boundaries ✅ Passed Reusable reorder planners live in CmuxFoundation with tests; Sources/ changes are AppKit session/controller glue and UI composition, which the rule allows.
Cmux Swiftpm Lockfiles ✅ Passed The commit only changes SidebarWorkspaceTableLocalReorderSession.swift; no .gitignore, workflow, Package.swift, Package.resolved, or Xcode package-reference files were touched.
Cmux User-Facing Error Privacy ✅ Passed No production user-facing error/alert/recovery copy was added; the only new localized text is the generic “+%lld” badge for hidden rows.
Cmux Swiftui State Layout ✅ Passed PASS: The only SwiftUI file change just adds AppKit sidebar-reorder closures; no new ObservableObject/@published, GeometryReader layout changes, or render-time state writes were introduced.
Cmux Architecture Rethink ✅ Passed The PR adds a single MainActor reorder-session owner with explicit delegate/plan bridges; no sleeps, polling, locks, or duplicated lifecycle ownership were introduced.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed No touched file adds a standalone NSWindow/NSPanel/WindowGroup; the new UI is an NSView floating row, and cmuxAuxiliaryWindowIdentifiers is unchanged.
Cmux Source Artifacts ✅ Passed Changed path is a hand-written Swift source file; it isn’t a local artifact, cache, log, screenshot, or build output per the rule.
Cmux No Test Or Debug Seam In Production Source ✅ Passed No added #if DEBUG/test-only members or test-hook names appear in the production diff; the new APIs are production drag/reorder hooks used by the sidebar flow.
Cmux No Ambient Global State ✅ Passed No new ambient global state: added types are instance-owned, injected via the controller; no new file-scope funcs/vars or singleton APIs in the diff.
Title check ✅ Passed The title matches the main change: live sidebar reorder previews with grouped blocks.
Description check ✅ Passed The description covers what changed, why, and testing, though the template sections are not fully formatted and the demo video/checklist are incomplete.
✨ 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 task-sidebar-fluid-reorder

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.

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 86cd5cff61

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

current.remove(at: fromIndex)
current.insert(ids[targetIndex], at: targetIndex)
}
table.endUpdates()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Refresh drop targets after preview moves rows

After a preview permutation changes rows and calls moveRow, reorderDropView.targets still contains the old workspace/frame mapping because the active geometry refresh path is not called here. In a local drag such as moving A below C and then moving the pointer back over the top row, the next draggingUpdated resolves against stale targets (the top frame still names A), so the preview/lastPlan can stick to or commit the previous slot instead of the row currently under the pointer. Please refresh active drop targets after the preview order changes.

Useful? React with 👍 / 👎.

Comment on lines +159 to +160
guard case .group(let groupId) = scope else { return nil }
return groupId

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Derive indent from commit action, not indicator scope

indicatorScope describes where to render the indicator, not necessarily whether the workspace will join that group: the resolver has root-lane group-boundary plans that render with .group(...) while committing usesTopLevelRows: true with explicitGroupId == nil. Returning the scope's group here makes the floating row indent as if it will become a group member even though the drop commits a top-level move, so root drops around expanded/collapsed groups preview the wrong hierarchy. Please derive this from the reorder action's explicit destination group instead of the render scope.

Useful? React with 👍 / 👎.

@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 `@Sources/Sidebar/AppKitList/SidebarWorkspaceTableController.swift`:
- Around line 1088-1094: The reorder loop in localReorderApplyOrder performs
repeated linear searches and array removal/insertion, causing quadratic work
during live preview. Replace it with an indexed position map or equivalent
one-pass move plan that tracks each identifier’s current index while applying
moves, preserving the resulting order and table.moveRow behavior with
near-linear complexity.
🪄 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: 1c8e3ec5-65fa-4d4a-a938-7c3d14f86740

📥 Commits

Reviewing files that changed from the base of the PR and between af987b6 and 86cd5cf.

📒 Files selected for processing (9)
  • Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/SidebarDrop/SidebarWorkspaceReorderPreviewPermutation.swift
  • Packages/macOS/CmuxFoundation/Tests/CmuxFoundationTests/SidebarWorkspaceReorderPreviewPermutationTests.swift
  • Sources/ContentView.swift
  • Sources/Sidebar/AppKitList/SidebarWorkspaceTableActions.swift
  • Sources/Sidebar/AppKitList/SidebarWorkspaceTableController.swift
  • Sources/Sidebar/AppKitList/SidebarWorkspaceTableLocalReorderSession.swift
  • Sources/SidebarWorkspaceReorderDropView.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/SidebarWorkspaceTableTests.swift

Comment thread Sources/Sidebar/AppKitList/SidebarWorkspaceTableController.swift Outdated
@greptile-apps

greptile-apps Bot commented Jul 19, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR replaces the 2px indicator-line drop preview in the AppKit sidebar with a Chrome-style live reorder: a floating snapshot of the dragged row (or whole group block) tracks the pointer while sibling rows animate apart in real time, with a no-commit cancel path on Escape and clean settle/restore animations.

  • New SidebarWorkspaceTableLocalReorderController and SidebarWorkspaceReorderFloatingRowView own the direct-pointer session lifecycle: immutable pickup-time geometry drives all hit-tests and target reconstruction so animated table frames never feed back into the planner; structural applies degrade the session immediately while content-only applies are deferred and flushed on teardown.
  • SidebarWorkspaceReorderMovePlanner in CmuxFoundation plans O(block × n) moveRow steps (one per dragged row rather than one per displaced row), addressing the O(n²) concern from the prior review; the planner's nil-return contract prevents table/data-source desync on stale or malformed preview orders.
  • Group-header drag is re-enabled in pasteboardWriterForRow by removing the header guard; the existing resolver already treats anchor drags as top-level block moves, and foreign surfaces ignore the payload type.

Confidence Score: 5/5

Safe to merge; the new live-reorder path is well-isolated behind an immutable session snapshot, the model commits exactly once from the previewed plan, and the degrade/restore paths cover structural applies, cross-window handoff, and Escape.

The only finding is a missing-locale gap in the new xcstrings entry: sidebar.reorder.floating.moreRows covers en and ja but not the other 19 supported locales. Functionally the fallback default value produces the correct string everywhere, so no user-visible regression exists.

Resources/Localizable.xcstrings — the new sidebar.reorder.floating.moreRows key needs translations added for the remaining 19 supported locales.

Important Files Changed

Filename Overview
Sources/Sidebar/AppKitList/SidebarWorkspaceTableLocalReorderSession.swift New 987-line session controller for live drag preview; immutable pickup snapshot, floating row view with lift/settle animations, correct sticky-destination state machine, and careful lifecycle.
Sources/Sidebar/AppKitList/SidebarWorkspaceTableController.swift Integrates SidebarWorkspaceTableLocalReorderController; adds group-header drag support by removing the header guard in pasteboardWriterForRow; defers/degrades applies correctly during live sessions.
Sources/Sidebar/AppKitList/SidebarWorkspaceTableViewImpl.swift Adds synchronous pointer-tracking loop for direct reorder with input coalescing, system-drag handoff escalation, and a manual-handoff guard.
Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/SidebarDrop/SidebarWorkspaceReorderMovePlan.swift New pure SidebarWorkspaceReorderMovePlanner that plans O(block x n) moveRow steps; guards prevent desync by returning nil for non-block permutations.
Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/SidebarDrop/SidebarWorkspaceReorderPreviewPermutation.swift New pure SidebarWorkspaceReorderPreviewPermutation maps a resolved indicator onto a visible row order; handles group-scope, top-level, and end-of-list indicators correctly.
Resources/Localizable.xcstrings Adds sidebar.reorder.floating.moreRows catalog key, but only with en and ja entries; 19 other supported locales are missing translations.

Sequence Diagram

sequenceDiagram
    participant TV as SidebarWorkspaceTableViewImpl
    participant G as DirectReorderGesture
    participant TC as TableController
    participant LR as LocalReorderController
    participant CV as ContentView

    TV->>G: mouseDown, drag(to:)
    G-->>TV: .beginReorder
    TV->>TC: directReorderWillBegin(row:at:)
    TC->>LR: directSessionWillBegin
    LR-->>TC: session started, cells hidden, floating view shown

    loop every mousemove
        TV->>TC: directReorderMoved(to:)
        TC->>LR: directSessionMoved(to:)
        LR->>TC: localReorderResolvePlan
        TC->>CV: resolveWorkspaceReorderPlan
        CV-->>LR: plan
        LR->>TC: localReorderApplyOrder (MovePlanner steps)
        TC->>TV: table.moveRow (animated)
    end

    alt mouseUp in corridor
        TV->>TC: directReorderEnded(at:)
        LR->>TC: localReorderCommit(plan:)
        TC->>CV: commitWorkspaceReorderPlan
        LR->>LR: settleFloatingView, finish
    else Escape or outside corridor
        TV->>TC: directReorderCancelled
        LR->>TC: localReorderApplyOrder(originalOrder)
        LR->>LR: cancelAndRestore, finish
    else exits corridor, system drag handoff
        G-->>TV: .handoffToSystemDrag
        TC->>LR: directSessionHandedOffToSystemDrag
        TV->>TV: beginSystemDragHandoff
    end
Loading

Reviews (7): Last reviewed commit: "Stabilize sidebar reorder XCU fixture" | Re-trigger Greptile

…ed indent

- Codex P1: refresh the overlay's drop targets after a preview permutation
  moves rows, so the next draggingUpdated resolves against current row ids
  and frames instead of sticking to (and committing) the previous slot.
- Codex P2: derive the floating row's destination-group indent from the
  plan's reorder action (explicitGroupId) instead of the indicator render
  scope; root-lane group-boundary plans render with a group scope while
  committing a top-level move.
- CodeRabbit/Greptile: replace the greedy per-index firstIndex/remove/insert
  loop in localReorderApplyOrder (quadratic per drag update, one moveRow per
  displaced row) with SidebarWorkspaceReorderMovePlanner, a pure CmuxFoundation
  planner that emits one moveRow per dragged-block row and refuses any order
  that is not an id-preserving block permutation. Covered by package tests
  including full slot sweeps for single rows and group blocks.
- Greptile/CodeRabbit i18n: route the floating-row "+N" badge through
  String(localized:defaultValue:).

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 14d46d9698

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +266 to +268
guard let plan = delegate.localReorderResolvePlan(point: point, targets: targets) else {
// Dead zone: keep the last valid preview rather than snapping.
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Restore the preview when the resolver returns a no-op

Fresh evidence after the target-refresh fix: this nil path still preserves the last preview/lastPlan, but localReorderResolvePlan also returns nil for valid no-op slots because SidebarDropPlanner suppresses no-op indicators. For example, drag A below C, then move the pointer back to the original slot above B and release; the resolver returns nil, this guard keeps the A-below-C preview, and handlePerformDrop commits that stale plan instead of restoring/doing nothing.

Useful? React with 👍 / 👎.

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

Caution

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

⚠️ Outside diff range comments (2)
Sources/Sidebar/AppKitList/SidebarWorkspaceTableLocalReorderSession.swift (2)

325-375: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Duplicate target-rect computation in settleFloatingViewIntoSlot and cancelAndRestore.

Both methods independently recompute blockRowIndexes/targetRect from current.blockRowIds with the same rows.indices.filter { ... } + reduce(CGRect.null) { ... } pattern. Extracting a shared helper avoids future drift between the two paths.

♻️ Proposed refactor
+    private func targetRect(
+        for blockRowIds: [SidebarWorkspaceRenderItemID],
+        in container: SidebarWorkspaceTableContainerView,
+        rows: [SidebarWorkspaceTableRowConfiguration]
+    ) -> CGRect? {
+        let table = container.tableView
+        let overlay = container.reorderDropView
+        let blockRowIndexes = rows.indices.filter { blockRowIds.contains(rows[$0].id) }
+        let rect = blockRowIndexes.reduce(CGRect.null) { partial, row in
+            partial.union(table.convert(table.rect(ofRow: row), to: overlay))
+        }
+        return rect.isNull ? nil : rect
+    }
+
     private func settleFloatingViewIntoSlot() {
         guard let delegate,
               let container = delegate.localReorderContainerView,
               let current = session else {
             finish(flushDeferredApply: true)
             return
         }
-        let rows = delegate.localReorderRows
-        let table = container.tableView
-        let overlay = container.reorderDropView
-        let blockRowIndexes = rows.indices.filter { current.blockRowIds.contains(rows[$0].id) }
-        let targetRect = blockRowIndexes.reduce(CGRect.null) { partial, row in
-            partial.union(table.convert(table.rect(ofRow: row), to: overlay))
-        }
-        guard let floating = current.floatingView, !targetRect.isNull else {
+        guard let floating = current.floatingView,
+              let rect = targetRect(for: current.blockRowIds, in: container, rows: delegate.localReorderRows) else {
             finish(flushDeferredApply: true)
             return
         }
-        animateEndOfSession(floating: floating, to: targetRect)
+        animateEndOfSession(floating: floating, to: rect)
     }

Apply the same simplification to cancelAndRestore.

🤖 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/Sidebar/AppKitList/SidebarWorkspaceTableLocalReorderSession.swift`
around lines 325 - 375, Extract the repeated block-row target rectangle
calculation from settleFloatingViewIntoSlot and cancelAndRestore into a shared
helper on the reorder session type. Have the helper use the current blockRowIds,
localReorderRows, table view, and reorderDropView to return the computed CGRect,
then replace both inline blockRowIndexes/reduce implementations while preserving
the existing null-rectangle handling.

129-137: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restore the drag image on exit

hideDragImage(_:) sets NSDraggingItem.imageComponentsProvider for the whole drag session, so draggingExited() needs to clear or reapply it. Right now the system drag image stays hidden after the pointer leaves this sidebar, which breaks cross-window drags.

🤖 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/Sidebar/AppKitList/SidebarWorkspaceTableLocalReorderSession.swift`
around lines 129 - 137, Update draggingExited(_:) to restore the drag image
after leaving the sidebar by clearing or reapplying the NSDraggingItem
imageComponentsProvider configured by hideDragImage(_:). Keep the existing
session and phase handling intact, and ensure setInsideSidebar(false) still runs
for valid active drags.
🤖 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.

Outside diff comments:
In `@Sources/Sidebar/AppKitList/SidebarWorkspaceTableLocalReorderSession.swift`:
- Around line 325-375: Extract the repeated block-row target rectangle
calculation from settleFloatingViewIntoSlot and cancelAndRestore into a shared
helper on the reorder session type. Have the helper use the current blockRowIds,
localReorderRows, table view, and reorderDropView to return the computed CGRect,
then replace both inline blockRowIndexes/reduce implementations while preserving
the existing null-rectangle handling.
- Around line 129-137: Update draggingExited(_:) to restore the drag image after
leaving the sidebar by clearing or reapplying the NSDraggingItem
imageComponentsProvider configured by hideDragImage(_:). Keep the existing
session and phase handling intact, and ensure setInsideSidebar(false) still runs
for valid active drags.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 84ade6b3-0fa0-47b0-8d6c-2077d188b3f0

📥 Commits

Reviewing files that changed from the base of the PR and between 86cd5cf and 14d46d9.

📒 Files selected for processing (6)
  • Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/SidebarDrop/SidebarWorkspaceReorderMovePlan.swift
  • Packages/macOS/CmuxFoundation/Sources/CmuxFoundation/SidebarDrop/SidebarWorkspaceReorderPreviewPermutation.swift
  • Packages/macOS/CmuxFoundation/Tests/CmuxFoundationTests/SidebarWorkspaceReorderMovePlanTests.swift
  • Packages/macOS/CmuxFoundation/Tests/CmuxFoundationTests/SidebarWorkspaceReorderPreviewPermutationTests.swift
  • Sources/Sidebar/AppKitList/SidebarWorkspaceTableController.swift
  • Sources/Sidebar/AppKitList/SidebarWorkspaceTableLocalReorderSession.swift

…h indent preview

- A nil resolver plan (the no-op zone over the dragged block's original
  slot) now previews the original order and clears the pending plan, so a
  drag can be returned exactly where it started; dropping there restores
  instead of committing the last previewed slot.
- Plain presses commit the workspace switch at mouseDown (Arc parity):
  pressing a row to reorder it also activates it. Modifier presses and
  presses inside a multi-selection keep click-time semantics, and the
  optimistic press paint now survives the drag instead of rolling back.
- The floating snapshot resizes for group indent: content slides right and
  clips to member width when joining a group, un-indents when a member row
  previews a top-level slot, and commits keep the indent so the snapshot
  lands exactly where the member cell renders.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

…agged block

The planner suppresses no-op plans, and with drop targets refreshed to
preview geometry the pointer's resting state is the dragged block's own
frame — so every stationary moment resolved nil, restored the original
order, moved a neighbor under the pointer, and re-previewed, oscillating
rows under the cursor. Nil now keeps the current preview while the
pointer is inside the block's preview frame (that gap IS the block), and
only restores the original order when the pointer has crossed into the
no-op dead zone outside it, which is what makes drag-back-to-origin land
exactly.

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

Caution

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

⚠️ Outside diff range comments (1)
Sources/Sidebar/AppKitList/SidebarWorkspaceTableLocalReorderSession.swift (1)

552-574: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the missing locale entries for sidebar.reorder.floating.moreRows. Resources/Localizable.xcstrings has the key, but only en and ja are filled; the other catalog locales are still missing, so the reorder badge isn’t fully localized.

🤖 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/Sidebar/AppKitList/SidebarWorkspaceTableLocalReorderSession.swift`
around lines 552 - 574, Add translations for the existing
sidebar.reorder.floating.moreRows key in Resources/Localizable.xcstrings for
every catalog locale currently missing a localized value, preserving the
existing en and ja entries and the +count badge format.

Source: Path instructions

🤖 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.

Outside diff comments:
In `@Sources/Sidebar/AppKitList/SidebarWorkspaceTableLocalReorderSession.swift`:
- Around line 552-574: Add translations for the existing
sidebar.reorder.floating.moreRows key in Resources/Localizable.xcstrings for
every catalog locale currently missing a localized value, preserving the
existing en and ja entries and the +count badge format.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c19bb86f-0e6b-4a43-8884-a7a9b7199b15

📥 Commits

Reviewing files that changed from the base of the PR and between ed66997 and 709b718.

📒 Files selected for processing (3)
  • Sources/Sidebar/AppKitList/SidebarWorkspaceTableController.swift
  • Sources/Sidebar/AppKitList/SidebarWorkspaceTableLocalReorderSession.swift
  • Sources/Sidebar/AppKitList/SidebarWorkspaceTableViewImpl.swift

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@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 `@Sources/Sidebar/AppKitList/SidebarWorkspaceTableLocalReorderSession.swift`:
- Around line 339-344: Update the block-rectangle calculation in the
drag/reorder method containing blockRect to avoid nested scans: build a Set from
current.blockRowIds for O(1) membership checks, then iterate rows once while
accumulating matching row rectangles. Preserve the existing table.convert
conversion and return false when no matching rows are found, and exit the scan
early once all block row IDs have been located.
🪄 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: 2b0c2205-4dfc-4ba1-9902-d8a3c4b45d86

📥 Commits

Reviewing files that changed from the base of the PR and between 709b718 and dd85801.

📒 Files selected for processing (1)
  • Sources/Sidebar/AppKitList/SidebarWorkspaceTableLocalReorderSession.swift

Comment on lines +339 to +344
let blockRect = rows.indices
.filter { current.blockRowIds.contains(rows[$0].id) }
.reduce(CGRect.null) { partial, row in
partial.union(table.convert(table.rect(ofRow: row), to: overlay))
}
guard !blockRect.isNull else { return false }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Avoid nested array scans on drag update hot paths.

rows.indices.filter { current.blockRowIds.contains(...) } performs a full scan over scalable collections (up to ~1000 rows), and calling contains on an array introduces a nested scan per item.

As per path instructions, avoid repeated full scans over scalable collections in production drag/reorder code paths, and don't call contains/filter/map/sorted across the same backing array for each item. Use a Set for O(1) membership lookups and a single-pass loop with an early exit to bound the algorithmic complexity.

⚡️ Proposed fix using a Set and early exit
-        let blockRect = rows.indices
-            .filter { current.blockRowIds.contains(rows[$0].id) }
-            .reduce(CGRect.null) { partial, row in
-                partial.union(table.convert(table.rect(ofRow: row), to: overlay))
-            }
-        guard !blockRect.isNull else { return false }
+        let blockIds = Set(current.blockRowIds)
+        var blockRect = CGRect.null
+        var foundCount = 0
+        for i in rows.indices {
+            if blockIds.contains(rows[i].id) {
+                blockRect = blockRect.union(table.convert(table.rect(ofRow: i), to: overlay))
+                foundCount += 1
+                if foundCount == blockIds.count { break }
+            }
+        }
+        guard !blockRect.isNull else { return false }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let blockRect = rows.indices
.filter { current.blockRowIds.contains(rows[$0].id) }
.reduce(CGRect.null) { partial, row in
partial.union(table.convert(table.rect(ofRow: row), to: overlay))
}
guard !blockRect.isNull else { return false }
let blockIds = Set(current.blockRowIds)
var blockRect = CGRect.null
var foundCount = 0
for i in rows.indices {
if blockIds.contains(rows[i].id) {
blockRect = blockRect.union(table.convert(table.rect(ofRow: i), to: overlay))
foundCount += 1
if foundCount == blockIds.count { break }
}
}
guard !blockRect.isNull else { return false }
🤖 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/Sidebar/AppKitList/SidebarWorkspaceTableLocalReorderSession.swift`
around lines 339 - 344, Update the block-rectangle calculation in the
drag/reorder method containing blockRect to avoid nested scans: build a Set from
current.blockRowIds for O(1) membership checks, then iterate rows once while
accumulating matching row rectangles. Preserve the existing table.convert
conversion and return false when no matching rows are found, and exit the scan
early once all block row IDs have been located.

Source: Path instructions

…ag slop past the sidebar

- Slot decisions now key off the floating block's vertical center instead
  of the raw cursor: a swap fires when the dragged row's middle crosses a
  neighbor's midpoint, wherever inside the row it was grabbed.
- Group/root-ambiguous boundary slots (a group's last-member edge) resolve
  toward where the drag already previews instead of the pointer-lane
  heuristic: a drag inside the group stays through the boundary band, a
  top-level drag stays out, and stickiness releases a full row past the
  group's last visible row so tail groups can still be exited downward.
  This makes the last slot of a group reachable without pixel precision.
- The reorder drop overlay expands around the sidebar during local drags
  (drag destination routing is frame-based), so a cursor drifting out of
  the narrow sidebar keeps the preview alive, hit-tests as if clamped to
  the row band, and dropping there still commits.
@cursor

cursor Bot commented Jul 21, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

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

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

…order

# Conflicts:
#	Sources/Sidebar/AppKitList/SidebarWorkspaceTableController.swift
#	Sources/Sidebar/AppKitList/SidebarWorkspaceTableViewImpl.swift
#	cmux.xcodeproj/project.pbxproj
@cursor

cursor Bot commented Jul 22, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

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

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

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7984b9c45c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +400 to +402
if Set(nextRowIds) == Set(currentIds), nextRowIds.count == currentIds.count {
pendingApply = deferred
return true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Degrade on same-ID reorders during local drags

When a same-ID structural apply arrives while a local drag is active, this Set-only check classifies it as content-only and defers it. The model order has already changed, but the table keeps resolving the preview through the previous actions/pickup order until the drag ends, so a socket/notification/other reorder with the same row IDs can make the final drop commit a stale target index over the new order; compare the incoming order against the active preview/expected order and degrade on mismatches instead of deferring every same-set apply.

AGENTS.md reference: AGENTS.md:L148-L150

Useful? React with 👍 / 👎.

@cursor

cursor Bot commented Jul 22, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

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

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

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

2 participants