Harden mobile session tab sync

Harden mobile session-tab snapshot reconciliation and terminal creation. Rejects stale mobile snapshots, tombstones locally closed tabs until the publisher catches up, rolls back half-created terminal tabs when their surface never appears, and scopes terminal-create idempotency by worktree.
This commit is contained in:
JinHyeok Jeong
2026-06-22 13:31:10 -07:00
committed by GitHub
parent 423c2befe1
commit e253d46e72
7 changed files with 510 additions and 7 deletions
+244
View File
@@ -13887,6 +13887,250 @@ describe('OrcaRuntimeService', () => {
expect(result.tab).toMatchObject({ parentTabId: 'tab-renderer', isActive: false })
})
it('dedupes concurrent mobile terminal creates that share a clientMutationId', async () => {
const runtime = new OrcaRuntimeService(store)
runtime.setNotifier({
focusTerminal: vi.fn(),
worktreesChanged: vi.fn(),
reposChanged: vi.fn(),
activateWorktree: vi.fn(),
createTerminal: vi.fn(),
revealTerminalSession: vi.fn(),
splitTerminal: vi.fn(),
renameTerminal: vi.fn(),
closeTerminal: vi.fn(),
closeSessionTab: vi.fn(),
sleepWorktree: vi.fn(),
terminalFitOverrideChanged: vi.fn(),
terminalDriverChanged: vi.fn()
})
const send = vi.fn((_channel: string, payload: { requestId: string }) => {
runtime.syncWindowGraph(1, {
tabs: [],
leaves: [],
mobileSessionTabs: [
{
worktree: TEST_WORKTREE_ID,
publicationEpoch: 'epoch-1',
snapshotVersion: 1,
activeGroupId: 'group-1',
activeTabId: null,
activeTabType: null,
tabs: [
{
type: 'terminal',
id: 'tab-renderer::pane:1',
parentTabId: 'tab-renderer',
leafId: 'pane:1',
title: 'Terminal',
isActive: false
}
]
}
]
})
ipcMain.emit(
'terminal:tabCreateReply',
{},
{ requestId: payload.requestId, tabId: 'tab-renderer', title: 'Terminal' }
)
})
runtime.attachWindow(1)
runtime.syncWindowGraph(1, { tabs: [], leaves: [] })
electronMocks.BrowserWindow.fromId.mockReturnValue({
isDestroyed: () => false,
webContents: { send }
})
const [first, second] = await Promise.all([
runtime.createMobileSessionTerminal(`id:${TEST_WORKTREE_ID}`, {
activate: false,
clientMutationId: 'mutation-1'
}),
runtime.createMobileSessionTerminal(`id:${TEST_WORKTREE_ID}`, {
activate: false,
clientMutationId: 'mutation-1'
})
])
const createRequests = send.mock.calls.filter(
([channel]) => channel === 'terminal:requestTabCreate'
)
expect(createRequests).toHaveLength(1)
expect(second).toBe(first)
expect(first.tab).toMatchObject({ parentTabId: 'tab-renderer' })
})
it('does not dedupe mobile terminal creates across worktrees with the same clientMutationId', async () => {
const otherWorktreeId = `${TEST_REPO_ID}::/tmp/worktree-b`
vi.mocked(listWorktrees).mockResolvedValue([
...MOCK_GIT_WORKTREES,
{
path: '/tmp/worktree-b',
head: 'def',
branch: 'feature/bar',
isBare: false,
isMainWorktree: false
}
])
const runtimeStore = {
...store,
getAllWorktreeMeta: () => ({
[TEST_WORKTREE_ID]: store.getAllWorktreeMeta()[TEST_WORKTREE_ID],
[otherWorktreeId]: makeWorktreeMeta({ displayName: 'other' })
})
}
const runtime = new OrcaRuntimeService(runtimeStore)
runtime.setNotifier({
focusTerminal: vi.fn(),
worktreesChanged: vi.fn(),
reposChanged: vi.fn(),
activateWorktree: vi.fn(),
createTerminal: vi.fn(),
revealTerminalSession: vi.fn(),
splitTerminal: vi.fn(),
renameTerminal: vi.fn(),
closeTerminal: vi.fn(),
closeSessionTab: vi.fn(),
sleepWorktree: vi.fn(),
terminalFitOverrideChanged: vi.fn(),
terminalDriverChanged: vi.fn()
})
const send = vi.fn((_channel: string, payload: { requestId: string; worktreeId: string }) => {
const parentTabId =
payload.worktreeId === TEST_WORKTREE_ID ? 'tab-renderer-a' : 'tab-renderer-b'
ipcMain.emit(
'terminal:tabCreateReply',
{},
{ requestId: payload.requestId, tabId: parentTabId, title: 'Terminal' }
)
})
runtime.attachWindow(1)
runtime.syncWindowGraph(1, { tabs: [], leaves: [] })
electronMocks.BrowserWindow.fromId.mockReturnValue({
isDestroyed: () => false,
webContents: { send }
})
const firstCreate = runtime.createMobileSessionTerminal(`id:${TEST_WORKTREE_ID}`, {
activate: false,
clientMutationId: 'mutation-1'
})
const secondCreate = runtime.createMobileSessionTerminal(`id:${otherWorktreeId}`, {
activate: false,
clientMutationId: 'mutation-1'
})
await vi.waitFor(() => {
const createRequests = send.mock.calls.filter(
([channel]) => channel === 'terminal:requestTabCreate'
)
expect(createRequests).toHaveLength(2)
})
runtime.syncWindowGraph(1, {
tabs: [],
leaves: [],
mobileSessionTabs: [
{
worktree: TEST_WORKTREE_ID,
publicationEpoch: 'epoch-a',
snapshotVersion: 1,
activeGroupId: 'group-1',
activeTabId: null,
activeTabType: null,
tabs: [
{
type: 'terminal',
id: 'tab-renderer-a::pane:1',
parentTabId: 'tab-renderer-a',
leafId: 'pane:1',
title: 'Terminal',
isActive: false
}
]
},
{
worktree: otherWorktreeId,
publicationEpoch: 'epoch-b',
snapshotVersion: 1,
activeGroupId: 'group-1',
activeTabId: null,
activeTabType: null,
tabs: [
{
type: 'terminal',
id: 'tab-renderer-b::pane:1',
parentTabId: 'tab-renderer-b',
leafId: 'pane:1',
title: 'Terminal',
isActive: false
}
]
}
]
})
const [first, second] = await Promise.all([firstCreate, secondCreate])
const createRequests = send.mock.calls.filter(
([channel]) => channel === 'terminal:requestTabCreate'
)
expect(createRequests).toHaveLength(2)
expect(first.tab).toMatchObject({ parentTabId: 'tab-renderer-a' })
expect(second.tab).toMatchObject({ parentTabId: 'tab-renderer-b' })
})
it('rolls back a half-created terminal whose surface never publishes', async () => {
vi.useFakeTimers()
try {
const closeTerminal = vi.fn()
const runtime = new OrcaRuntimeService(store)
runtime.setNotifier({
focusTerminal: vi.fn(),
worktreesChanged: vi.fn(),
reposChanged: vi.fn(),
activateWorktree: vi.fn(),
createTerminal: vi.fn(),
revealTerminalSession: vi.fn(),
splitTerminal: vi.fn(),
renameTerminal: vi.fn(),
closeTerminal,
closeSessionTab: vi.fn(),
sleepWorktree: vi.fn(),
terminalFitOverrideChanged: vi.fn(),
terminalDriverChanged: vi.fn()
})
// Why: reply with a tabId but never sync a matching surface graph, so
// waitForMobileTerminalSurface times out and the rollback path runs.
const send = vi.fn((_channel: string, payload: { requestId: string }) => {
ipcMain.emit(
'terminal:tabCreateReply',
{},
{ requestId: payload.requestId, tabId: 'tab-ghost', title: 'Terminal' }
)
})
runtime.attachWindow(1)
runtime.syncWindowGraph(1, { tabs: [], leaves: [] })
electronMocks.BrowserWindow.fromId.mockReturnValue({
isDestroyed: () => false,
webContents: { send }
})
const pending = runtime.createMobileSessionTerminal(`id:${TEST_WORKTREE_ID}`, {
activate: false
})
const settled = pending.then(
() => ({ ok: true as const }),
(error: Error) => ({ ok: false as const, error })
)
await vi.advanceTimersByTimeAsync(11_000)
const outcome = await settled
expect(outcome.ok).toBe(false)
expect(closeTerminal).toHaveBeenCalledWith('tab-ghost')
} finally {
vi.useRealTimers()
}
})
it('reports browser tab creation as unsupported for a windowless host with no offscreen backend', async () => {
const runtime = new OrcaRuntimeService(store)
runtime.syncWindowGraph(0, { tabs: [], leaves: [] })