fix(tab-group): make split-right/split-down move the source tab instead of duplicating (#794)

Split actions from the tab context menu used copyUnifiedTabToGroup for editors
and spawned a fresh browser tab, so splitting produced two copies of the same
file/URL rather than moving the active tab into the new pane. Delegate to
dropUnifiedTab to share the drag-to-split move path; keep the new-terminal
behavior for single-tab terminal groups since moving the only tab would
collapse the split.
This commit is contained in:
Jinjing
2026-04-17 23:22:47 -07:00
committed by GitHub
parent c20a44a9a4
commit f2eb4f4866
@@ -68,7 +68,6 @@ export function useTabGroupWorkspaceModel({
const closeUnifiedTab = useAppStore((state) => state.closeUnifiedTab)
const closeOtherTabs = useAppStore((state) => state.closeOtherTabs)
const closeTabsToRight = useAppStore((state) => state.closeTabsToRight)
const createEmptySplitGroup = useAppStore((state) => state.createEmptySplitGroup)
const closeEmptyGroup = useAppStore((state) => state.closeEmptyGroup)
const createTab = useAppStore((state) => state.createTab)
const closeTab = useAppStore((state) => state.closeTab)
@@ -81,7 +80,8 @@ export function useTabGroupWorkspaceModel({
const closeBrowserTab = useAppStore((state) => state.closeBrowserTab)
const setActiveBrowserTab = useAppStore((state) => state.setActiveBrowserTab)
const setActiveWorktree = useAppStore((state) => state.setActiveWorktree)
const copyUnifiedTabToGroup = useAppStore((state) => state.copyUnifiedTabToGroup)
const dropUnifiedTab = useAppStore((state) => state.dropUnifiedTab)
const createEmptySplitGroup = useAppStore((state) => state.createEmptySplitGroup)
const setTabCustomTitle = useAppStore((state) => state.setTabCustomTitle)
const setTabColor = useAppStore((state) => state.setTabColor)
const consumeSuppressedPtyExit = useAppStore((state) => state.consumeSuppressedPtyExit)
@@ -296,59 +296,45 @@ export function useTabGroupWorkspaceModel({
) ?? activeTab
focusGroup(worktreeId, groupId)
const newGroupId = createEmptySplitGroup(worktreeId, groupId, direction)
if (!newGroupId || !sourceTab) {
if (!sourceTab) {
return
}
// Why: tab context-menu split actions belong to the visible tab that opened
// the menu. Keeping that decision inside the workspace model prevents the
// view layer from re-implementing "which tab is the source?" rules.
if (sourceTab.contentType === 'terminal') {
// Why: for terminals specifically, splitting a single-tab group should
// still produce a useful split — spawn a fresh terminal in the new pane
// and leave the existing one behind. Moving the only tab would collapse
// the split immediately (see the same-group guard in dropUnifiedTab),
// giving the user nothing; a new terminal preserves the old shortcut
// flow without duplicating a persistent tab like editors/browsers would.
if (sourceTab.contentType === 'terminal' && groupTabs.length <= 1) {
const newGroupId = createEmptySplitGroup(worktreeId, groupId, direction)
if (!newGroupId) {
return
}
const terminal = createTab(worktreeId, newGroupId)
setActiveTab(terminal.id)
setActiveTabType('terminal')
return
}
if (sourceTab.contentType === 'browser') {
const browserTab = worktreeState.browserTabs.find(
(candidate) => candidate.id === sourceTab.entityId
)
if (!browserTab) {
return
}
createBrowserTab(browserTab.worktreeId, browserTab.url, {
title: browserTab.title,
sessionProfileId: browserTab.sessionProfileId
})
return
}
copyUnifiedTabToGroup(sourceTab.id, newGroupId, {
entityId: sourceTab.entityId,
label: sourceTab.label,
customLabel: sourceTab.customLabel,
color: sourceTab.color,
isPinned: sourceTab.isPinned
})
setActiveFile(sourceTab.entityId)
setActiveTabType('editor')
// Why: split actions MOVE the source tab into the new pane rather than
// leaving a duplicate in the origin. Delegating to dropUnifiedTab reuses
// the same split+move path as drag-to-split so keyboard/menu splits and
// drag splits stay behaviorally identical, including collapsing the
// origin group if its last tab is the one we just moved.
dropUnifiedTab(sourceTab.id, { groupId, splitDirection: direction })
},
[
activeTab,
copyUnifiedTabToGroup,
createBrowserTab,
createEmptySplitGroup,
createTab,
dropUnifiedTab,
focusGroup,
groupId,
groupTabs,
setActiveFile,
setActiveTab,
setActiveTabType,
worktreeId,
worktreeState.browserTabs
worktreeId
]
)