diff --git a/src/renderer/src/components/status-bar/ResourceUsageStatusSegment.tsx b/src/renderer/src/components/status-bar/ResourceUsageStatusSegment.tsx index 853ba4ae8..36308d163 100644 --- a/src/renderer/src/components/status-bar/ResourceUsageStatusSegment.tsx +++ b/src/renderer/src/components/status-bar/ResourceUsageStatusSegment.tsx @@ -1388,7 +1388,7 @@ export function ResourceUsageStatusSegment({ className="relative inline-flex w-full items-center justify-center rounded-md border border-border/70 px-2.5 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-accent/60" > - Delete inactive workspaces ({oldWorkspaceCount}) + Review inactive workspaces ({oldWorkspaceCount}) = { type CleanupView = WorkspaceCleanupTier | 'hidden' +const SCAN_LOADING_STEPS = [ + { + title: 'Finding inactive workspaces', + detail: 'Listing old worktrees before any cleanup decision is made.' + }, + { + title: 'Checking git safety', + detail: 'Looking for changed files and commits that only exist locally.' + }, + { + title: 'Protecting active work', + detail: 'Skipping workspaces with open tabs, terminals, live agents, or unavailable remotes.' + } +] as const + const BLOCKER_LABELS: Record = { 'main-worktree': 'Main workspace', 'folder-repo': 'Folder project', @@ -187,6 +202,7 @@ export default function WorkspaceCleanupDialog(): React.JSX.Element { const [removing, setRemoving] = useState(false) const [rowFailures, setRowFailures] = useState>({}) const [repoSelection, setRepoSelection] = useState>(() => new Set()) + const [loadingStepIndex, setLoadingStepIndex] = useState(0) const eligibleRepos = useMemo(() => repos.filter((repo) => isGitRepoKind(repo)), [repos]) const eligibleRepoIds = useMemo(() => eligibleRepos.map((repo) => repo.id), [eligibleRepos]) @@ -209,6 +225,19 @@ export default function WorkspaceCleanupDialog(): React.JSX.Element { setRepoSelection(new Set(eligibleRepoIds)) }, [eligibleRepoIds, open]) + useEffect(() => { + if (!open || !loading) { + setLoadingStepIndex(0) + return + } + + const timer = window.setInterval(() => { + setLoadingStepIndex((index) => (index + 1) % SCAN_LOADING_STEPS.length) + }, 2600) + + return () => window.clearInterval(timer) + }, [loading, open]) + const candidates = useMemo(() => scan?.candidates ?? [], [scan?.candidates]) const effectiveRepoSelection = useMemo>(() => { if (repoSelection.size > 0 || eligibleRepoIds.length === 0) { @@ -285,8 +314,11 @@ export default function WorkspaceCleanupDialog(): React.JSX.Element { [repoNameById, selectedScanErrors] ) const readyCount = groups.ready.length + const protectedCount = groups.protected.length + const inactiveCount = filteredCandidates.length const hasAnyCandidates = candidates.length > 0 const initialLoading = loading && !scan + const loadingStep = SCAN_LOADING_STEPS[loadingStepIndex] const activeRows = activeView === 'hidden' ? hiddenCandidates : groups[activeView] const activeQueueableRows = useMemo( () => activeRows.filter(canQueueWorkspaceCleanupCandidate), @@ -476,9 +508,12 @@ export default function WorkspaceCleanupDialog(): React.JSX.Element { {initialLoading ? ( -
- - Checking inactive workspaces +
+ +
+
{loadingStep.title}
+
{loadingStep.detail}
+
) : hasAnyCandidates ? (
@@ -486,12 +521,16 @@ export default function WorkspaceCleanupDialog(): React.JSX.Element {
{selectedCount} selected
+ {inactiveCount} inactive {readyCount > 0 ? ( {readyCount} safe to remove ) : null} {groups.review.length > 0 ? ( {groups.review.length} need review ) : null} + {protectedCount > 0 ? ( + {protectedCount} not suggested + ) : null}
{eligibleRepos.length > 1 ? (