fix: clear removed web session tab tracking (#4099)

This commit is contained in:
Neil 2026-05-31 04:15:34 -07:00 committed by GitHub
parent 30b93c6bf7
commit 4615f2e169
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 143 additions and 0 deletions

View File

@ -6,6 +6,7 @@ import { makePaneKey } from '../../../shared/stable-pane-id'
import type { BrowserPage, BrowserWorkspace, Tab, TerminalTab } from '../../../shared/types'
import type { OpenFile } from '../store/slices/editor'
import {
_getWebSessionTabsTrackingCountsForTest,
applyFreshWebSessionTabsSnapshot,
applyWebSessionTabsSnapshot,
applyWebSessionTabsSnapshots,
@ -92,6 +93,121 @@ describe('applyWebSessionTabsSnapshot', () => {
expect(applyFreshWebSessionTabsSnapshot(afterNewer, newer, ENV, NOW)).toBe(afterNewer)
})
it('clears web session tracking maps when the host removes a worktree snapshot', () => {
const workspace: BrowserWorkspace = {
id: 'local-browser-workspace',
worktreeId: WT,
activePageId: 'local-browser-page',
pageIds: ['local-browser-page'],
url: 'https://example.com/',
title: 'Example Domain',
loading: false,
faviconUrl: null,
canGoBack: false,
canGoForward: false,
loadError: null,
createdAt: NOW - 10
}
const page: BrowserPage = {
id: 'local-browser-page',
workspaceId: workspace.id,
worktreeId: WT,
url: 'https://example.com/',
title: 'Example Domain',
loading: false,
faviconUrl: null,
canGoBack: false,
canGoForward: false,
loadError: null,
createdAt: NOW - 10
}
const unifiedTab: Tab = {
id: 'local-browser-unified',
entityId: workspace.id,
groupId: 'host-group-1',
worktreeId: WT,
contentType: 'browser',
label: 'New Tab',
customLabel: null,
color: null,
sortOrder: 0,
createdAt: NOW - 10,
isPreview: false,
isPinned: false
}
const patch = applyFreshWebSessionTabsSnapshot(
makeState({
browserTabsByWorktree: { [WT]: [workspace] },
browserPagesByWorkspace: { [workspace.id]: [page] },
remoteBrowserPageHandlesByPageId: {
[page.id]: { environmentId: ENV, remotePageId: 'host-browser-page' }
},
unifiedTabsByWorktree: { [WT]: [unifiedTab] },
groupsByWorktree: {
[WT]: [
{
id: 'host-group-1',
worktreeId: WT,
activeTabId: unifiedTab.id,
tabOrder: [unifiedTab.id],
recentTabIds: [unifiedTab.id]
}
]
}
}),
makeSnapshot(
[
{
type: 'browser',
id: 'host-browser-unified',
title: 'Example Domain',
browserWorkspaceId: 'host-browser-workspace',
browserPageId: 'host-browser-page',
url: 'https://example.com/',
loading: false,
canGoBack: false,
canGoForward: false,
isActive: true
}
],
{ activeTabId: 'host-browser-unified', activeTabType: 'browser' }
),
ENV,
NOW
) as Partial<WebSessionTabsSyncState>
const afterHostSnapshot = {
...makeState(),
...patch
} as WebSessionTabsSyncState
expect(_getWebSessionTabsTrackingCountsForTest()).toEqual({
freshness: 1,
hostMappings: 1
})
applyFreshWebSessionTabsSnapshot(
afterHostSnapshot,
{
...makeSnapshot([], {
publicationEpoch: 'removed-epoch',
snapshotVersion: 0,
activeGroupId: null,
activeTabId: null,
activeTabType: null
}),
removed: true
} as RuntimeMobileSessionTabsResult,
ENV,
NOW + 1
)
expect(_getWebSessionTabsTrackingCountsForTest()).toEqual({
freshness: 0,
hostMappings: 0
})
})
it('hydrates ready host terminal surfaces as remote runtime terminal tabs', () => {
const patch = applyWebSessionTabsSnapshot(
makeState(),

View File

@ -134,6 +134,13 @@ export function shouldApplyWebSessionTabsSnapshot(
environmentId: string
): boolean {
const key = sessionTabsFreshnessKey(environmentId, snapshot.worktree)
if ((snapshot as { removed?: unknown }).removed === true) {
// Why: removed worktrees can stop publishing snapshots, so their
// freshness/mapping entries need explicit cleanup instead of waiting for
// a later replacement snapshot that may never arrive.
clearWebSessionTabsTrackingForWorktree(environmentId, snapshot.worktree)
return true
}
const current = latestSessionTabsSnapshotByWorktree.get(key)
if (
current &&
@ -154,6 +161,26 @@ export function resetWebSessionTabsSnapshotFreshnessForTests(): void {
hostSessionTabIdByLocalKey.clear()
}
export function _getWebSessionTabsTrackingCountsForTest(): {
freshness: number
hostMappings: number
} {
return {
freshness: latestSessionTabsSnapshotByWorktree.size,
hostMappings: hostSessionTabIdByLocalKey.size
}
}
function clearWebSessionTabsTrackingForWorktree(environmentId: string, worktreeId: string): void {
latestSessionTabsSnapshotByWorktree.delete(sessionTabsFreshnessKey(environmentId, worktreeId))
const keyPrefix = `${environmentId}:${worktreeId}:`
for (const key of hostSessionTabIdByLocalKey.keys()) {
if (key.startsWith(keyPrefix)) {
hostSessionTabIdByLocalKey.delete(key)
}
}
}
function hostSessionTabMappingKey(args: {
environmentId: string
worktreeId: string