Prevent deleted workspace browser snapshot resurrection

This commit is contained in:
Jinwoo-H
2026-09-01 14:08:41 -04:00
parent 5150c52045
commit 0844d85efd
6 changed files with 50 additions and 2 deletions
@@ -52,6 +52,33 @@ describe('buildMobileSessionTabSnapshots', () => {
expect(restored.snapshotVersion).toBeGreaterThan(initial.snapshotVersion)
})
it('publishes a new instance identity when unchanged content is recreated', () => {
const worktree = {
id: 'wt-1',
instanceId: 'old-instance',
repoId: 'repo-1'
}
const base = makeState({
worktreesByRepo: { 'repo-1': [worktree] } as unknown as AppState['worktreesByRepo'],
tabsByWorktree: {
'wt-1': [{ id: 'term-1', title: 'Terminal 1' }]
} as unknown as AppState['tabsByWorktree']
})
const initial = buildMobileSessionTabSnapshots(base)[0]!
const recreated = {
...base,
worktreesByRepo: {
'repo-1': [{ ...worktree, instanceId: 'new-instance' }]
} as unknown as AppState['worktreesByRepo']
}
const next = buildMobileSessionTabSnapshots(recreated)[0]!
expect(initial.worktreeInstanceId).toBe('old-instance')
expect(next.worktreeInstanceId).toBe('new-instance')
expect(next.snapshotVersion).toBeGreaterThan(initial.snapshotVersion)
})
it('publishes browser and editor color + pin state from unified tabs', () => {
const fileId = '/repo/README.md'
const state = makeState({
@@ -157,6 +157,7 @@ export function canReuseMobileSessionSnapshot(
): boolean {
return (
previous.worktreeId === next.worktreeId &&
previous.worktreeInstanceId === next.worktreeInstanceId &&
previous.terminalTabs === next.terminalTabs &&
previous.browserWorkspaces === next.browserWorkspaces &&
previous.unifiedTabs === next.unifiedTabs &&
@@ -1,6 +1,7 @@
import type { AppState } from '@/store/types'
import { parsePaneKey, makePaneKey } from '../../../../shared/stable-pane-id'
import { nativeChatLaunchAgentForLeaf } from '../../components/native-chat/native-chat-leaf-routing'
import { getIndexedWorktreesById } from '@/store/worktree-repo-index'
import {
EMPTY_NARROWED_BY_KEY,
EMPTY_WORKTREE_BROWSER_WORKSPACES,
@@ -146,6 +147,9 @@ export function buildMobileSessionWorktreeInputs(
const activeTabId = state.activeTabId
return {
worktreeId,
worktreeInstanceId: getIndexedWorktreesById(state.worktreesByRepo, worktreeId).find(
(worktree) => worktree.instanceId !== undefined
)?.instanceId,
terminalTabs,
browserWorkspaces,
unifiedTabs: state.unifiedTabsByWorktree[worktreeId] ?? EMPTY_WORKTREE_UNIFIED_TABS,
@@ -208,16 +208,29 @@ export function buildMobileSessionTabSnapshots(
}
const candidateVersion = ++graphState.mobileSessionSnapshotVersion
if (cached && jsonContentEquals(cached.content, content)) {
const snapshot =
cached.snapshot.worktreeInstanceId === inputs.worktreeInstanceId
? cached.snapshot
: {
worktree: worktreeId,
...(inputs.worktreeInstanceId
? { worktreeInstanceId: inputs.worktreeInstanceId }
: {}),
publicationEpoch: mobilePublicationEpoch,
snapshotVersion: candidateVersion,
...content
}
graphState.mobileSessionSnapshotCacheByWorktree.set(worktreeId, {
inputs,
content,
snapshot: cached.snapshot
snapshot
})
snapshots.push(cached.snapshot)
snapshots.push(snapshot)
continue
}
const snapshot: RuntimeMobileSessionTabsSnapshot = {
worktree: worktreeId,
...(inputs.worktreeInstanceId ? { worktreeInstanceId: inputs.worktreeInstanceId } : {}),
publicationEpoch: mobilePublicationEpoch,
snapshotVersion: candidateVersion,
...content
@@ -107,6 +107,7 @@ export type MountedTerminalSurfaceCapture = {
*/
export type MobileSessionWorktreeInputs = {
worktreeId: string
worktreeInstanceId: string | undefined
terminalTabs: AppState['tabsByWorktree'][string]
browserWorkspaces: AppState['browserTabsByWorktree'][string]
unifiedTabs: AppState['unifiedTabsByWorktree'][string]
+2
View File
@@ -215,6 +215,8 @@ export const UNPUBLISHED_WORKTREE_PUBLICATION_EPOCH = 'none'
export type RuntimeMobileSessionTabsSnapshot = {
worktree: string
/** Immutable catalog identity used to fence snapshots across path reuse. */
worktreeInstanceId?: string
publicationEpoch: string
snapshotVersion: number
activeGroupId: string | null