Fix split pane shortcuts targeting hidden worktrees (#676)

This commit is contained in:
Brennan Benson 2026-04-15 14:16:39 -07:00 committed by GitHub
parent d22a466ece
commit aad7d04997
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 73 additions and 2 deletions

View File

@ -896,6 +896,7 @@ function Terminal(): React.JSX.Element | null {
layout={layout}
worktreeId={worktree.id}
focusedGroupId={activeGroupIdByWorktree[worktree.id]}
isWorktreeActive={isVisible}
/>
</div>
)

View File

@ -0,0 +1,59 @@
import { describe, expect, it, vi } from 'vitest'
const setTabGroupSplitRatioMock = vi.fn()
const useAppStoreMock = vi.fn(
(selector: (state: { setTabGroupSplitRatio: () => void }) => unknown) =>
selector({ setTabGroupSplitRatio: setTabGroupSplitRatioMock })
)
vi.mock('../../store', () => ({
useAppStore: (selector: (state: { setTabGroupSplitRatio: () => void }) => unknown) =>
useAppStoreMock(selector)
}))
vi.mock('./TabGroupPanel', () => ({
default: (props: unknown) => ({ __mock: 'TabGroupPanel', props })
}))
import TabGroupSplitLayout from './TabGroupSplitLayout'
describe('TabGroupSplitLayout', () => {
function getLeafPanelProps(isWorktreeActive: boolean) {
const element = TabGroupSplitLayout({
layout: { type: 'leaf', groupId: 'group-1' },
worktreeId: 'wt-1',
focusedGroupId: 'group-1',
isWorktreeActive
})
const splitNodeElement = element.props.children
const tabGroupPanelElement = splitNodeElement.type(splitNodeElement.props)
return tabGroupPanelElement.props as {
groupId: string
worktreeId: string
isFocused: boolean
hasSplitGroups: boolean
}
}
it('does not mark an offscreen worktree group as focused', () => {
expect(getLeafPanelProps(false)).toEqual(
expect.objectContaining({
groupId: 'group-1',
worktreeId: 'wt-1',
isFocused: false,
hasSplitGroups: false
})
)
})
it('keeps the visible worktree focused group active', () => {
expect(getLeafPanelProps(true)).toEqual(
expect.objectContaining({
groupId: 'group-1',
worktreeId: 'wt-1',
isFocused: true,
hasSplitGroups: false
})
)
})
})

View File

@ -84,12 +84,14 @@ function SplitNode({
nodePath,
worktreeId,
focusedGroupId,
isWorktreeActive,
hasSplitGroups
}: {
node: TabGroupLayoutNode
nodePath: string
worktreeId: string
focusedGroupId?: string
isWorktreeActive: boolean
hasSplitGroups: boolean
}): React.JSX.Element {
const setTabGroupSplitRatio = useAppStore((state) => state.setTabGroupSplitRatio)
@ -99,7 +101,11 @@ function SplitNode({
<TabGroupPanel
groupId={node.groupId}
worktreeId={worktreeId}
isFocused={node.groupId === focusedGroupId}
// Why: hidden worktrees stay mounted so their PTYs and split layouts
// survive worktree switches, but only the visible worktree may own the
// global terminal shortcuts. If an offscreen group's pane stays
// "focused", Cmd/Ctrl+W and split shortcuts can hit the wrong worktree.
isFocused={isWorktreeActive && node.groupId === focusedGroupId}
hasSplitGroups={hasSplitGroups}
/>
)
@ -119,6 +125,7 @@ function SplitNode({
nodePath={nodePath.length > 0 ? `${nodePath}.first` : 'first'}
worktreeId={worktreeId}
focusedGroupId={focusedGroupId}
isWorktreeActive={isWorktreeActive}
hasSplitGroups={hasSplitGroups}
/>
</div>
@ -132,6 +139,7 @@ function SplitNode({
nodePath={nodePath.length > 0 ? `${nodePath}.second` : 'second'}
worktreeId={worktreeId}
focusedGroupId={focusedGroupId}
isWorktreeActive={isWorktreeActive}
hasSplitGroups={hasSplitGroups}
/>
</div>
@ -142,11 +150,13 @@ function SplitNode({
export default function TabGroupSplitLayout({
layout,
worktreeId,
focusedGroupId
focusedGroupId,
isWorktreeActive
}: {
layout: TabGroupLayoutNode
worktreeId: string
focusedGroupId?: string
isWorktreeActive: boolean
}): React.JSX.Element {
return (
<div className="flex flex-1 min-w-0 min-h-0 overflow-hidden">
@ -155,6 +165,7 @@ export default function TabGroupSplitLayout({
nodePath=""
worktreeId={worktreeId}
focusedGroupId={focusedGroupId}
isWorktreeActive={isWorktreeActive}
hasSplitGroups={layout.type === 'split'}
/>
</div>