diff --git a/src/main/ipc/workspace-cleanup-activity.ts b/src/main/ipc/workspace-cleanup-activity.ts index 4b0cffa42a8..a6236566af1 100644 --- a/src/main/ipc/workspace-cleanup-activity.ts +++ b/src/main/ipc/workspace-cleanup-activity.ts @@ -7,6 +7,14 @@ import { toWindowsWslPath } from '../wsl' type StatPath = (targetPath: string) => Promise<{ mtimeMs: number }> type ReadTextFile = (targetPath: string) => Promise +export function getPersistedWorkspaceCleanupActivityAt( + worktree: Pick +): number { + const persistedActivityAt = Number.isFinite(worktree.lastActivityAt) ? worktree.lastActivityAt : 0 + const createdAt = Number.isFinite(worktree.createdAt) ? (worktree.createdAt ?? 0) : 0 + return Math.max(persistedActivityAt, createdAt) +} + export async function resolveWorkspaceCleanupActivityWorktree( repo: Repo, worktree: Worktree, @@ -35,10 +43,9 @@ async function resolveWorkspaceCleanupActivityAt( statPath: StatPath, readTextFile: ReadTextFile ): Promise { - const persistedActivityAt = Number.isFinite(worktree.lastActivityAt) ? worktree.lastActivityAt : 0 - const createdAt = Number.isFinite(worktree.createdAt) ? (worktree.createdAt ?? 0) : 0 + const persistedActivityAt = getPersistedWorkspaceCleanupActivityAt(worktree) if (repo.connectionId) { - return Math.max(persistedActivityAt, createdAt) + return persistedActivityAt } const filesystemActivityAt = await getNewestLocalWorktreeStatMtime( @@ -46,7 +53,7 @@ async function resolveWorkspaceCleanupActivityAt( statPath, readTextFile ) - return Math.max(persistedActivityAt, createdAt, filesystemActivityAt) + return Math.max(persistedActivityAt, filesystemActivityAt) } async function getNewestLocalWorktreeStatMtime( diff --git a/src/main/ipc/workspace-cleanup-scan.ts b/src/main/ipc/workspace-cleanup-scan.ts index 2dc0b0e11a4..bd621c9a3cf 100644 --- a/src/main/ipc/workspace-cleanup-scan.ts +++ b/src/main/ipc/workspace-cleanup-scan.ts @@ -13,7 +13,10 @@ import type { WorkspaceCleanupScanProgress, WorkspaceCleanupScanResult } from '../../shared/workspace-cleanup' -import { resolveWorkspaceCleanupActivityWorktree } from './workspace-cleanup-activity' +import { + getPersistedWorkspaceCleanupActivityAt, + resolveWorkspaceCleanupActivityWorktree +} from './workspace-cleanup-activity' import { buildWorkspaceCleanupCandidate, buildWorkspaceCleanupCandidateFromError, @@ -201,14 +204,6 @@ function shouldResolveBroadWorkspaceCleanupActivity( ) } -function getPersistedWorkspaceCleanupActivityAt( - worktree: Pick -): number { - const lastActivityAt = Number.isFinite(worktree.lastActivityAt) ? worktree.lastActivityAt : 0 - const createdAt = Number.isFinite(worktree.createdAt) ? (worktree.createdAt ?? 0) : 0 - return Math.max(lastActivityAt, createdAt) -} - async function listCleanupGitWorktrees( repo: Repo, repoIsFolder: boolean diff --git a/src/renderer/src/components/workspace-cleanup/workspace-cleanup-background-removal.test.ts b/src/renderer/src/components/workspace-cleanup/workspace-cleanup-background-removal.test.ts index c072961666b..51b99b9e9ff 100644 --- a/src/renderer/src/components/workspace-cleanup/workspace-cleanup-background-removal.test.ts +++ b/src/renderer/src/components/workspace-cleanup/workspace-cleanup-background-removal.test.ts @@ -28,6 +28,26 @@ describe('startWorkspaceCleanupBackgroundRemoval', () => { vi.useRealTimers() }) + it('reports an empty result without starting removal when there are no candidates', async () => { + const removeCandidates = vi.fn() + const onProgress = vi.fn() + const onResult = vi.fn() + + startWorkspaceCleanupBackgroundRemoval({ + candidates: [], + removeCandidates, + onProgress, + onResult + }) + await settleBackgroundRemoval() + + expect(removeCandidates).not.toHaveBeenCalled() + expect(onProgress).not.toHaveBeenCalled() + expect(onResult).toHaveBeenCalledWith({ removedIds: [], failures: [] }) + expect(toast.success).not.toHaveBeenCalled() + expect(toast.error).not.toHaveBeenCalled() + }) + it('reports deletion progress while the slow removal promise is pending', async () => { let resolveRemoval: ( result: Awaited> diff --git a/src/renderer/src/components/workspace-cleanup/workspace-cleanup-background-removal.ts b/src/renderer/src/components/workspace-cleanup/workspace-cleanup-background-removal.ts index daa7ee0d3af..198c2c5c484 100644 --- a/src/renderer/src/components/workspace-cleanup/workspace-cleanup-background-removal.ts +++ b/src/renderer/src/components/workspace-cleanup/workspace-cleanup-background-removal.ts @@ -37,6 +37,11 @@ export function startWorkspaceCleanupBackgroundRemoval({ removalTimeoutMs = DEFAULT_WORKSPACE_CLEANUP_REMOVAL_TIMEOUT_MS }: WorkspaceCleanupBackgroundRemovalArgs): void { if (candidates.length === 0) { + try { + onResult?.({ removedIds: [], failures: [] }) + } catch (callbackError) { + console.error('Workspace cleanup result callback failed', callbackError) + } return } diff --git a/src/renderer/src/components/workspace-cleanup/workspace-cleanup-removal-candidates.ts b/src/renderer/src/components/workspace-cleanup/workspace-cleanup-removal-candidates.ts index 5c97d780f31..36c70257929 100644 --- a/src/renderer/src/components/workspace-cleanup/workspace-cleanup-removal-candidates.ts +++ b/src/renderer/src/components/workspace-cleanup/workspace-cleanup-removal-candidates.ts @@ -1,12 +1,11 @@ import type { WorkspaceCleanupCandidate } from '../../../../shared/workspace-cleanup' +import type { WorktreeDeleteState } from '@/store/slices/worktrees' -type DeleteState = { - isDeleting?: boolean -} +type DeletionFlagState = Pick export function filterWorkspaceCleanupRemovalCandidates( candidates: readonly WorkspaceCleanupCandidate[], - deleteStateByWorktreeId: Record + deleteStateByWorktreeId: Record ): WorkspaceCleanupCandidate[] { return candidates.filter( (candidate) => deleteStateByWorktreeId[candidate.worktreeId]?.isDeleting !== true