fix: keep floating tabs local with active runtime

Keep the synthetic floating workspace local while a remote runtime is active, including terminal/browser creation, activation, close, and remote snapshot handling.

Maintainer follow-ups:
- require worktreeId for runtime-session terminal create payloads
- add renderer-backed terminal create reply sender regression coverage
- merge current main and keep the WSL readDir breadcrumb test aligned with main's Windows-only handler coverage
This commit is contained in:
Wolfie
2026-06-30 12:48:26 -07:00
committed by GitHub
parent 01070198fa
commit 62ab2d470e
22 changed files with 535 additions and 268 deletions
@@ -603,4 +603,43 @@ describe('attachMainWindowServices', () => {
sendMock.mock.invocationCallOrder[0]
)
})
it('accepts terminal reveal replies only from the main window renderer', async () => {
const sendMock = vi.fn()
const mainWindow = createMainWindow({ send: sendMock })
const runtime = createRuntime()
attachMainWindowServices(mainWindow as never, createStore(), runtime as never)
const notifier = runtime.setNotifier.mock.calls[0][0] as {
revealTerminalSession: (
worktreeId: string,
opts: { ptyId: string; title?: string; activate?: boolean }
) => Promise<{ tabId: string; title?: string }>
}
const revealPromise = notifier.revealTerminalSession('wt-1', {
ptyId: 'pty-1',
title: 'SSH tmux'
})
const sentPayload = sendMock.mock.calls.find(
([channel]) => channel === 'ui:createTerminal'
)?.[1]
const handler = onMock.mock.calls.find(
([channel]) => channel === 'terminal:tabCreateReply'
)?.[1]
handler?.(
{ sender: { send: vi.fn() } },
{ requestId: sentPayload.requestId, error: 'spoofed renderer reply' }
)
expect(removeListenerMock).not.toHaveBeenCalledWith('terminal:tabCreateReply', handler)
handler?.(
{ sender: mainWindow.webContents },
{ requestId: sentPayload.requestId, tabId: 'tab-1', title: 'SSH tmux' }
)
await expect(revealPromise).resolves.toEqual({ tabId: 'tab-1', title: 'SSH tmux' })
expect(removeListenerMock).toHaveBeenCalledWith('terminal:tabCreateReply', handler)
})
})
@@ -264,10 +264,12 @@ function registerRuntimeWindowLifecycle(
reject(new Error('Terminal reveal timed out'))
}, 10_000)
const handler = (
_event: Electron.IpcMainEvent,
event: Electron.IpcMainEvent,
reply: { requestId: string; tabId?: string; title?: string; error?: string }
): void => {
if (reply.requestId !== requestId) {
// Why: requestId is renderer-supplied; only the targeted main window
// may satisfy the reveal and provide the tab handle.
if (event.sender !== mainWindow.webContents || reply.requestId !== requestId) {
return
}
clearTimeout(timer)