diff --git a/src/renderer/src/components/tab-group/TabGroupDndContext.tsx b/src/renderer/src/components/tab-group/TabGroupDndContext.tsx index 9dc15330..277333eb 100644 --- a/src/renderer/src/components/tab-group/TabGroupDndContext.tsx +++ b/src/renderer/src/components/tab-group/TabGroupDndContext.tsx @@ -54,16 +54,29 @@ import { const EMPTY_GROUPS: TabGroup[] = [] const EMPTY_TABS: Tab[] = [] -// Why: moving a live terminal tab between groups remounts the pane, and the -// non-daemon PTY reattach path relies on a brittle rAF-retry + width-jitter -// sequence that can leave the destination pane blank under load. Until the -// terminal daemon flips on by default (which gives snapshot-based reattach), -// gate cross-group terminal moves so users never see a half-working drag. -// Same-group reorder is unaffected — it doesn't tear down the pane. Browser -// and editor tabs have no PTY lifecycle and always drag freely. Pane-body -// split drops onto another group are treated like cross-group moves because -// they also remount the terminal pane. Center-zone drops don't create a new -// group, so they reduce to the tab-bar path and the same rules apply. +// Why: moving a live terminal tab between groups remounts the pane, which +// in turn requires reattaching the PTY to a fresh xterm instance. The +// non-daemon renderer-side reattach path is not reliable enough to ship +// (it can leave the destination pane blank), and we do not want users to +// see a half-working drag. The terminal daemon provides snapshot-based +// reattach that IS reliable, so we gate terminal cross-group drag on +// `settings.experimentalTerminalDaemon`: +// +// - daemon ON → cross-group terminal drag works like browser/editor +// - daemon OFF → cross-group terminal drag is refused silently (no drop +// indicator / overlay; same-group reorder still works) +// +// Browser and editor tabs have no PTY lifecycle and always drag freely, +// regardless of this flag. Same-group terminal reorder is also unaffected +// because the pane never remounts in that case. +// +// Pane-body split drops that land in a non-center zone create a new group +// and remount the pane, so they're treated as cross-group moves even when +// the target group matches the source. Center-zone drops do not create a +// new group and reduce to the tab-bar insertion path. +// +// When the daemon flips on by default, this gate becomes a no-op for most +// users and can be deleted as a follow-up cleanup. function isTerminalCrossGroupDropBlocked( drag: TabDragData, targetGroupId: string,