diff --git a/src/renderer/src/store/slices/repos.test.ts b/src/renderer/src/store/slices/repos.test.ts index aa88907062b..0db2f3e9576 100644 --- a/src/renderer/src/store/slices/repos.test.ts +++ b/src/renderer/src/store/slices/repos.test.ts @@ -668,6 +668,26 @@ describe('repo slice runtime routing', () => { expect(runtimeEnvironmentCall).not.toHaveBeenCalled() }) + it('drops persisted visit timestamps for removed unhydrated SSH repos', async () => { + const store = createTestStore() + const sshWorktreeId = `${sshRepo.id}::/home/orca/wt` + const localWorktreeId = `${localRepo.id}::/local/wt` + store.setState({ + repos: [sshRepo, localRepo], + activeRepoId: sshRepo.id, + lastVisitedAtByWorktreeId: { + [sshWorktreeId]: 100, + [localWorktreeId]: 200 + } + }) + + await store.getState().removeProject(sshRepo.id) + + expect(store.getState().repos).toEqual([localRepo]) + expect(store.getState().lastVisitedAtByWorktreeId).toEqual({ [localWorktreeId]: 200 }) + expect(reposRemove).toHaveBeenCalledWith({ repoId: sshRepo.id }) + }) + it('evicts GitHub caches for removed repos using repo id and legacy path keys', async () => { const store = createTestStore() store.setState({ diff --git a/src/renderer/src/store/slices/repos.ts b/src/renderer/src/store/slices/repos.ts index 6fc4c263d2a..0d4b27b9e7b 100644 --- a/src/renderer/src/store/slices/repos.ts +++ b/src/renderer/src/store/slices/repos.ts @@ -43,8 +43,8 @@ import { sanitizeRepoIcon } from '../../../../shared/repo-icon' import { normalizeRepoBadgeColor } from '../../../../shared/repo-badge-color' import { getProjectGroupSubtreeIds } from '../../../../shared/project-groups' import { isPathInsideOrEqual } from '../../../../shared/cross-platform-path' +import { getRepoIdFromWorktreeId } from '../../../../shared/worktree-id' import { selectProjectGroupRemovalTargets } from './project-group-removal-targets' -import { getRepoIdFromWorktreeId } from './worktree-helpers' import { reconcileFetchedRepos } from './repo-identity-reconcile' import { splitRepoReorderByHost } from './repo-reorder-host-split' import { @@ -2325,10 +2325,6 @@ export const createRepoSlice: StateCreator = (set, const activeFileCleared = s.activeFileId ? s.openFiles.some((f) => f.id === s.activeFileId && worktreeIdSet.has(f.worktreeId)) : false - // Why: pruneLastVisitedTimestamps defers entries for repos missing - // from worktreesByRepo (treats them as not-yet-hydrated SSH repos). - // Drop this repo's timestamps explicitly so they cannot survive prune - // forever after the repo is removed. const nextRepos = s.repos.filter((r) => !repoMatchesHostIdentity(r, projectId, ownerHostId)) // Why: when no sibling host still owns this repo id, drop every persisted // timestamp for the repo's worktrees — including unhydrated SSH/remote ones @@ -2338,7 +2334,10 @@ export const createRepoSlice: StateCreator = (set, const repoIdFullyRemoved = !nextRepos.some((r) => r.id === projectId) let nextLastVisitedAtByWorktreeId = s.lastVisitedAtByWorktreeId for (const id of Object.keys(s.lastVisitedAtByWorktreeId)) { - if (worktreeIdSet.has(id) || (repoIdFullyRemoved && getRepoIdFromWorktreeId(id) === projectId)) { + if ( + worktreeIdSet.has(id) || + (repoIdFullyRemoved && getRepoIdFromWorktreeId(id) === projectId) + ) { if (nextLastVisitedAtByWorktreeId === s.lastVisitedAtByWorktreeId) { nextLastVisitedAtByWorktreeId = { ...s.lastVisitedAtByWorktreeId } }