fix(layout): tab reconcile and terminal reconnect stop writing tab structure (dark, layout-owner build only)

Main's frames draw the tab bar and rows: reconcile still returns its counts but writes nothing,
and reconnect still advertises restored terminals but leaves the rows to main.
This commit is contained in:
Jinwoo-H
2026-10-18 03:06:23 -04:00
parent 6bc1e5556f
commit ef8eb625ca
3 changed files with 81 additions and 2 deletions
@@ -0,0 +1,69 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { createTabsSliceMockApi } from '../tabs-slice-test-harness'
import { createTestStore, makeTab } from '../store-test-helpers'
vi.mock('sonner', () => ({ toast: { info: vi.fn(), success: vi.fn(), error: vi.fn() } }))
vi.mock('../../../../../shared/layout-owner-switch', () => ({ ORCA_LAYOUT_OWNER_IS_MAIN: true }))
createTabsSliceMockApi()
const WT = 'repo1::/tmp/feature'
// Main draws the tab bar from its frames; these shared paths must not write structure of their own.
describe('shared tab paths in a layout-owner build', () => {
let store: ReturnType<typeof createTestStore>
beforeEach(() => {
store = createTestStore()
})
it('reconcile counts what would render and leaves the tab bar to main', () => {
const entry = {
id: 'stale-terminal',
entityId: 'stale-terminal',
groupId: 'g-1',
worktreeId: WT,
contentType: 'terminal' as const,
label: 'Terminal 1',
customLabel: null,
color: null,
sortOrder: 0,
createdAt: 1
}
store.setState({
unifiedTabsByWorktree: { [WT]: [entry] },
groupsByWorktree: {
[WT]: [{ id: 'g-1', worktreeId: WT, activeTabId: entry.id, tabOrder: [entry.id] }]
},
activeGroupIdByWorktree: { [WT]: 'g-1' },
tabsByWorktree: { [WT]: [] }
})
const before = store.getState()
expect(store.getState().reconcileWorktreeTabModel(WT)).toEqual({
renderableTabCount: 0,
activeRenderableTabId: null
})
store.getState().reconcileWorktreeTabModels([WT])
expect(store.getState().unifiedTabsByWorktree).toBe(before.unifiedTabsByWorktree)
expect(store.getState().groupsByWorktree).toBe(before.groupsByWorktree)
})
it('reconnect advertises restored terminals without rewriting their rows', async () => {
const rows = { [WT]: [makeTab({ id: 'tab-1', worktreeId: WT, ptyId: null })] }
store.setState({
tabsByWorktree: rows,
terminalLayoutsByTabId: {},
pendingReconnectWorktreeIds: [WT],
pendingReconnectTabByWorktree: { [WT]: ['tab-1'] },
pendingReconnectPtyIdByTabId: { 'tab-1': 'pty-1' }
})
await store.getState().reconnectPersistedTerminals()
expect(store.getState().ptyIdsByTabId['tab-1']).toEqual(['pty-1'])
expect(store.getState().tabsByWorktree).toBe(rows)
expect(store.getState().workspaceSessionReady).toBe(true)
})
})
@@ -10,6 +10,7 @@ import { buildHydratedTabState } from '../tabs-hydration'
import { projectWorktreeTabModelReconciliation } from './tabs-reconciliation'
import { createWorktreeTabModelReconciliationBatch } from './tabs-reconciliation-batch'
import type { AppState } from '../../types'
import { ORCA_LAYOUT_OWNER_IS_MAIN } from '../../../../../shared/layout-owner-switch'
function replaceWorkspaceRecordKeys<T>(
current: Record<string, T>,
@@ -59,6 +60,10 @@ export function createTabsSessionActions(
if (worktreeIds.length === 0) {
return
}
// Switch point: main's frames draw the tab bar, so reconcile writes no structure of its own.
if (ORCA_LAYOUT_OWNER_IS_MAIN) {
return
}
const patch = projectWorktreeTabModelReconciliations(get(), worktreeIds)
if (Object.keys(patch).length > 0) {
set(patch)
@@ -67,7 +72,8 @@ export function createTabsSessionActions(
reconcileWorktreeTabModel: (worktreeId) => {
const reconciliation = projectWorktreeTabModelReconciliation(get(), worktreeId)
if (Object.keys(reconciliation.patch).length > 0) {
// Switch point: the counts only; main's frames draw the tab bar.
if (!ORCA_LAYOUT_OWNER_IS_MAIN && Object.keys(reconciliation.patch).length > 0) {
set(reconciliation.patch)
}
return {
@@ -2,6 +2,7 @@ import type { TerminalTab } from '../../../../shared/terminal-tab-types'
import { buildByIdIndex, buildWorktreeByIdIndex } from '../slices/worktree-by-id-index'
import { getRepoIdFromWorktreeId } from '../../../../shared/worktree/id'
import type { TerminalSlice, TerminalStoreGet, TerminalStoreSet } from './terminal-state'
import { ORCA_LAYOUT_OWNER_IS_MAIN } from '../../../../shared/layout-owner-switch'
export function createWorkspaceTerminalReconnectActions(
set: TerminalStoreSet,
@@ -117,7 +118,10 @@ export function createWorkspaceTerminalReconnectActions(
return
}
set({
...(reconnectedTabsByWorktree ? { tabsByWorktree: reconnectedTabsByWorktree } : {}),
// Switch point: main's frames draw the rows, with main's own bindings.
...(reconnectedTabsByWorktree && !ORCA_LAYOUT_OWNER_IS_MAIN
? { tabsByWorktree: reconnectedTabsByWorktree }
: {}),
...(reconnectedPtyIdsByTabId ? { ptyIdsByTabId: reconnectedPtyIdsByTabId } : {}),
workspaceSessionReady: true,
pendingReconnectWorktreeIds: [],