mirror of
https://github.com/stablyai/orca.git
synced 2026-09-21 16:02:20 +00:00
fix(terminal): let a layout that carries the leaf beat the spawn-time tab hint
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
// identity, and the renderer delivers PTY data to one handler slot per pty id — so one pane
|
||||
// starves and both fight over the grid size. That is STA-7961.
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { collectLeafIdsInOrder } from '@/components/terminal-pane/terminal-layout-leaf-ids'
|
||||
import {
|
||||
createHarnessStoreState,
|
||||
createStoreWithOwnerFiledElsewhere,
|
||||
@@ -98,4 +99,53 @@ describe('terminal reveal must not mint a tab that reuses a bound leaf id (STA-7
|
||||
expect(storeState.createTab).not.toHaveBeenCalled()
|
||||
expect(tabsBindingSharedLeaf(storeState)).toEqual(['tab-a'])
|
||||
})
|
||||
|
||||
it('adopts the tab whose layout carries the leaf over the tab the pty was minted against', async () => {
|
||||
// The hint is a spawn-time tab id, not a binding. tab-b already holds the leaf, so reusing
|
||||
// the hinted tab-a writes a single pane over its split and orphans leaf-a2's pty.
|
||||
const storeState: HarnessStoreState = createHarnessStoreState({
|
||||
tabsByWorktree: {
|
||||
[WORKTREE_ID]: [
|
||||
{ id: 'tab-a', ptyId: 'pty-a1', title: 'Terminal 1' },
|
||||
{ id: 'tab-b', ptyId: null, title: 'Terminal 2' }
|
||||
]
|
||||
},
|
||||
ptyIdsByTabId: {},
|
||||
terminalLayoutsByTabId: {
|
||||
'tab-a': {
|
||||
root: {
|
||||
type: 'split',
|
||||
direction: 'horizontal',
|
||||
first: { type: 'leaf', leafId: 'leaf-a1' },
|
||||
second: { type: 'leaf', leafId: 'leaf-a2' }
|
||||
},
|
||||
ptyIdsByLeafId: { 'leaf-a1': 'pty-a1', 'leaf-a2': 'pty-a2' }
|
||||
},
|
||||
// Bound to a dead pty, so the revealed pty is still owned by nobody.
|
||||
'tab-b': {
|
||||
root: { type: 'leaf', leafId: SHARED_LEAF_ID },
|
||||
ptyIdsByLeafId: { [SHARED_LEAF_ID]: 'pty-stale' }
|
||||
}
|
||||
}
|
||||
})
|
||||
const harness = await loadIpcEventsHarness(storeState)
|
||||
harness.useIpcEvents()
|
||||
|
||||
harness.createTerminal({
|
||||
requestId: 'reveal',
|
||||
worktreeId: WORKTREE_ID,
|
||||
ptyId: SHARED_PTY_ID,
|
||||
tabId: 'tab-a',
|
||||
leafId: SHARED_LEAF_ID,
|
||||
presentation: 'focused',
|
||||
title: 'OpenCode'
|
||||
})
|
||||
|
||||
expect(harness.replyTerminalCreate.mock.calls[0]?.[0]).toMatchObject({ tabId: 'tab-b' })
|
||||
expect(storeState.createTab).not.toHaveBeenCalled()
|
||||
expect(tabsBindingSharedLeaf(storeState)).toEqual(['tab-b'])
|
||||
const hintedLayout = storeState.terminalLayoutsByTabId['tab-a']!
|
||||
expect(collectLeafIdsInOrder(hintedLayout.root ?? null)).toEqual(['leaf-a1', 'leaf-a2'])
|
||||
expect(hintedLayout.ptyIdsByLeafId).toEqual({ 'leaf-a1': 'pty-a1', 'leaf-a2': 'pty-a2' })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -53,11 +53,6 @@ export function resolveTerminalRevealTabAdoption(
|
||||
if (ownership.kind === 'owned') {
|
||||
return ownership.owner.tabId
|
||||
}
|
||||
// Why: nothing holds the PTY yet, so the tab it was minted against is the only thing left that
|
||||
// keeps paneKey hook attribution intact (#10486), and a split names its parent by that id.
|
||||
if (ownership.kind === 'none' && hintTabId !== undefined) {
|
||||
return hintTabId
|
||||
}
|
||||
// STA-7961: the PTY is unowned here, but the leaf id may already be someone's pane.
|
||||
const leafOwnerTabId = request.leafId
|
||||
? findTerminalTabIdBindingLeafId(state, request.leafId)
|
||||
@@ -65,6 +60,13 @@ export function resolveTerminalRevealTabAdoption(
|
||||
if (leafOwnerTabId !== null) {
|
||||
return leafOwnerTabId
|
||||
}
|
||||
// Why: nothing holds the PTY yet, so the tab it was minted against is the only thing left that
|
||||
// keeps paneKey hook attribution intact (#10486), and a split names its parent by that id. It
|
||||
// ranks below the leaf owner because a layout already carrying the leaf is that pane's home,
|
||||
// and reusing the hinted tab instead writes a single pane over it and orphans the rest.
|
||||
if (ownership.kind === 'none' && hintTabId !== undefined) {
|
||||
return hintTabId
|
||||
}
|
||||
// Why mint even when claimants exist: no layout carries this leaf id, so the bridge would
|
||||
// replace the adopted tab's whole layout with a single pane and orphan its other panes' PTYs.
|
||||
const claim =
|
||||
|
||||
Reference in New Issue
Block a user