Harden worktree identity migration coverage

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-06-23 23:49:29 -07:00
co-authored by Orca
parent 25801d2ef5
commit 4a484e856b
2 changed files with 29 additions and 10 deletions
@@ -133,6 +133,7 @@ function createTestStore() {
expandedDirs: {},
gitStatusByWorktree: {},
gitStatusHeadByWorktree: {},
gitStatusHugeByWorktree: {},
gitIgnoredPathsByWorktree: {},
gitConflictOperationByWorktree: {},
trackedConflictPathsByWorktree: {},
@@ -144,6 +145,8 @@ function createTestStore() {
activeBrowserTabIdByWorktree: {},
browserTabsByWorktree: {},
recentlyClosedBrowserTabsByWorktree: {},
recentlyClosedEditorTabsByWorktree: {},
remoteStatusesByWorktree: {},
activeTabTypeByWorktree: {},
rightSidebarTab: 'explorer' as const,
rightSidebarTabByWorktree: {},
@@ -5003,6 +5006,11 @@ describe('migrateWorktreeIdentity', () => {
const OLD = 'repo1::/ws/cunner'
const NEW = 'repo1::/ws/worktree-creation-spinner'
const CANONICAL = makeWorktreeKey({ hostId: 'local', repoId: 'repo1', path: '/ws/cunner' })
const WORKTREE_ID_VALUE_MIGRATED_KEYS = new Set([
'browserPagesByWorkspace',
'recentlyClosedBrowserPagesByWorkspace',
'sleepingAgentSessionsByPaneKey'
])
function makeMigrationMapValue(
key: (typeof WORKTREE_ID_KEYED_MAP_KEYS)[number],
@@ -5026,11 +5034,24 @@ describe('migrateWorktreeIdentity', () => {
return [{ id: 'unified1', worktreeId }]
case 'groupsByWorktree':
return [{ id: 'group1', worktreeId }]
case 'recentlyClosedEditorTabsByWorktree':
return [{ id: 'file1', worktreeId }]
default:
return { marker: key }
}
}
it('keeps the worktree-id keyed map list in sync with store fields', () => {
const declared = new Set<keyof AppState>(WORKTREE_ID_KEYED_MAP_KEYS)
const state = createTestStore().getState()
const uncovered = Object.keys(state)
.filter((key) => key.includes('ByWorktree'))
.filter((key) => !declared.has(key as keyof AppState))
.filter((key) => !WORKTREE_ID_VALUE_MIGRATED_KEYS.has(key))
expect(uncovered).toEqual([])
})
it('re-keys worktree-scoped maps, pointers, the Set, and openFiles old->new', () => {
const store = createTestStore()
store.setState({
@@ -5052,6 +5073,7 @@ describe('migrateWorktreeIdentity', () => {
groupsByWorktree: { [OLD]: [{ id: 'group1', worktreeId: OLD }] },
gitStatusByWorktree: { [OLD]: [{ path: 'a.ts' }] },
gitStatusHeadByWorktree: { [OLD]: 'head-old' },
gitStatusHugeByWorktree: { [OLD]: { limit: 10_000 } },
gitBranchCompareRequestStatusHeadByWorktree: { [OLD]: 'head-old' },
lastVisitedAtByWorktreeId: { [OLD]: 123 },
defaultTerminalTabsAppliedByWorktreeId: { [OLD]: true },
@@ -5095,12 +5117,12 @@ describe('migrateWorktreeIdentity', () => {
expect(s.groupsByWorktree[NEW]?.[0]?.worktreeId).toBe(NEW)
expect(s.gitStatusByWorktree[NEW]).toEqual([{ path: 'a.ts' }])
expect(s.gitStatusHeadByWorktree[NEW]).toBe('head-old')
expect(s.gitStatusHugeByWorktree[NEW]).toEqual({ limit: 10_000 })
expect(s.gitBranchCompareRequestStatusHeadByWorktree[NEW]).toBe('head-old')
expect(s.rightSidebarExplorerViewByWorktree[OLD]).toBeUndefined()
expect(s.rightSidebarExplorerViewByWorktree[NEW]).toBe('search')
expect(s.lastVisitedAtByWorktreeId[NEW]).toBe(123)
expect(s.defaultTerminalTabsAppliedByWorktreeId[NEW]).toBe(true)
// The two maps absent from the purge list are still re-keyed.
expect(s.recentlyClosedEditorTabsByWorktree[NEW]).toEqual([{ id: 'f1', worktreeId: NEW }])
expect(s.remoteStatusesByWorktree[NEW]).toEqual({ ahead: 1 })
expect(s.everActivatedWorktreeIds.has(NEW)).toBe(true)
+6 -9
View File
@@ -1272,6 +1272,7 @@ export const WORKTREE_ID_KEYED_MAP_KEYS = [
'activeGroupIdByWorktree',
'gitStatusByWorktree',
'gitStatusHeadByWorktree',
'gitStatusHugeByWorktree',
'gitIgnoredPathsByWorktree',
'gitConflictOperationByWorktree',
'trackedConflictPathsByWorktree',
@@ -1279,6 +1280,8 @@ export const WORKTREE_ID_KEYED_MAP_KEYS = [
'gitBranchCompareSummaryByWorktree',
'gitBranchCompareRequestKeyByWorktree',
'gitBranchCompareRequestStatusHeadByWorktree',
'recentlyClosedEditorTabsByWorktree',
'remoteStatusesByWorktree',
'showDotfilesByWorktree',
'expandedDirs',
'lastVisitedAtByWorktreeId',
@@ -1371,7 +1374,9 @@ function buildWorktreeRenameState(
pages: snapshot.pages.map(withNewWorktreeId)
})),
unifiedTabsByWorktree: (tabs: { worktreeId: string }[]) => tabs.map(withNewWorktreeId),
groupsByWorktree: (groups: { worktreeId: string }[]) => groups.map(withNewWorktreeId)
groupsByWorktree: (groups: { worktreeId: string }[]) => groups.map(withNewWorktreeId),
recentlyClosedEditorTabsByWorktree: (files: { worktreeId: string }[]) =>
files.map(withNewWorktreeId)
}
for (const key of WORKTREE_ID_KEYED_MAP_KEYS) {
renameKey(key, renameValueByKey[key] as ((value: unknown) => unknown) | undefined)
@@ -1398,14 +1403,6 @@ function buildWorktreeRenameState(
oldWorkspaceKey,
newWorkspaceKey
)
// Re-key these on rename so a renamed worktree keeps its editor-undo + push/pull
// state. (Both removal paths — buildWorktreePurgeState and the single
// removeWorktree reducer — now also purge them on removal.)
renameKey('recentlyClosedEditorTabsByWorktree', (files: { worktreeId: string }[]) =>
files.map(withNewWorktreeId)
)
renameKey('remoteStatusesByWorktree')
const openFiles = s.openFiles?.some((f) => f.worktreeId === oldWorktreeId)
? s.openFiles.map((f) =>
f.worktreeId === oldWorktreeId ? { ...f, worktreeId: newWorktreeId } : f