Repository navigation
cmux-next: TabDragSession, glass ghost, one command per drop outcome - #15555
Conversation
Pure, tested logic for the App's TabDragSession: TabDragResolver maps drop-target proposals to one outcome (and rejects no-op or layout-breaking drops such as splitting a pane with its only tab), TabDragLifecycle guarantees every drag ends exactly once and restores the source tab unless its transaction settled, TabDragGhostMotion tracks the pointer with zero lag while springing every discontinuity, TabMoveIndex converts final indices to move-tab's pre-move insertion coordinates. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The drag session owns one borderless panel whose ghost follows the pointer across every window and outside them, folds into an inline tab over strips and unfolds into a Liquid Glass preview card (live terminal/browser snapshot) elsewhere. It asks every TabDropTargetProviding in every window (sidebar, strips, layout adapters) by screen point, commits one daemon command per outcome with a client transaction and optimistic patch, springs back on Escape, tears off into a new window under the pointer (or moves the window when the drag carries the whole workspace), and handles whole-group drags. A display link runs only while the ghost animates or columns auto-scroll (new LayoutRootView.autoscrollTabDrag). Fixes: same-pane move-tab sent final indices where the daemon expects pre-move coordinates (a one-slot move right was a no-op); the fallback split for left/top edges swapped with a pane that moving its last tab had closed (swap-pane: unknown pane/target). The swap now runs only when the target survives, and the resolver refuses a split of the source pane by its only tab. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository: manaflow-ai/cmux/.coderabbit.yaml Review profile: ASSERTIVE Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
Tab drag session for cmux-next (plans/cmux-next/REWRITE.md "Tab drag"): one drag, every destination.
What changes
TabDragSession(CmuxNextApp/Drag) replaces the first cut. One borderless non-activating panel carries the ghost across every window and outside them. Over a strip it folds into an inline tab, and the strip opens a gap. Everywhere else it unfolds into a Liquid Glass card with a live terminal or browser snapshot. The ghost follows the pointer with no lag. Every discontinuity springs: card to tab, slot change, spring back, drop.TabDropTargetProvidingin every window by screen point, in this order: sidebar, strips, layout. The sidebar and the layout get adapters (TabDropTargets.swift). A pane center maps to "join that pane's strip".WindowManagerpersists the window. A drag that carries the whole workspace moves its window instead. Escape, no target, or a rejected command springs the tab back.tab-groups-v1group moves.TabDragLifecyclemakes sure every drag ends exactly once. It callsrestoreDetachedTab/restoreDetachedGroupunless the transaction settled.LayoutRootView.autoscrollTabDragscrolls niri columns near the edges. This is a small additive API in CmuxNextLayout.Fixes
swap-pane: unknown pane/targeton daemons withouttab-drag-v1. Reproduced against a live cmux-tui: dragging a pane's only tab onto its own left edge moves the tab into the new pane, the source pane closes, and the swap targets a pane that no longer exists. The resolver now refuses that no-op drop. The fallback swaps only when the target pane still exists, and usessplit {tab}when the daemon honors it.move-tabsent the final index, but the daemon expects pre-move insertion coordinates. Moving a tab one slot right did nothing, and longer moves landed one slot short.TabMoveIndexconverts the index.Verification
swift build --build-testspasses.swift testpasses for the whole package. The newTabDragTestscover outcome resolution, cancel and restore invariants, transaction settle, ghost motion, index conversion, and tear-off geometry.check-no-godfiles.shpasses.tabdrag-v1at d2e6984 was launched locally with a clean environment andCMUX_NEXT_NO_ACTIVATE=1. Through the control socket,tab move-rightby one slot now moves the tab; before this fix it was a no-op. With 5 tabs and 2 panes, the app idles at 0.0% CPU and 117 MB RSS.Known limits
tab-drag-v1), a new split or column first spawns a PTY, moves the tab in, and then closes that PTY.Changelog
none
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by cubic
Replaces the first tab-drag cut with a full
TabDragSession: one ghost panel follows the pointer across every window and outside them, and every drop outcome commits exactly one daemon command.Drag session
tab-groups-v1moves, and releases outside every window tear off into a new window under the pointer, or move the source window when the drag carries the whole workspace.TabDragLifecycleguarantees every drag ends exactly once and restores the source tab unless the transaction settled.LayoutRootView.autoscrollTabDragscrolls niri columns near the edges; the display link runs only while the ghost animates or a screen auto-scrolls.Fixes
move-tabnow converts final indices to the daemon's pre-move insertion coordinates (a one-slot move right was a no-op, longer moves landed a slot short).unknown pane/target); the resolver also refuses splitting the source pane with its last tab.Written for commit d2e6984. Summary will update on new commits.