Clarify inactive workspace cleanup review (#2533)

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Brennan Benson 2026-05-21 12:07:35 -07:00 committed by GitHub
parent cb15124cbb
commit 2e3627fd8e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 43 additions and 4 deletions

View File

@ -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"
>
<span className="min-w-0 truncate px-4 text-center">
Delete inactive workspaces ({oldWorkspaceCount})
Review inactive workspaces ({oldWorkspaceCount})
</span>
<ChevronRight
className="absolute right-2.5 size-3.5 text-muted-foreground"

View File

@ -44,6 +44,21 @@ const TIER_LABELS: Record<WorkspaceCleanupTier, string> = {
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<WorkspaceCleanupBlocker, string> = {
'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<Record<string, string>>({})
const [repoSelection, setRepoSelection] = useState<ReadonlySet<string>>(() => 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<ReadonlySet<string>>(() => {
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 {
</DialogHeader>
{initialLoading ? (
<div className="flex items-center gap-2 border-b border-border bg-muted/25 px-5 py-3 text-xs text-muted-foreground">
<Loader2 className="size-3.5 animate-spin" />
Checking inactive workspaces
<div className="flex items-start gap-2 border-b border-border bg-muted/25 px-5 py-3">
<Loader2 className="mt-0.5 size-3.5 shrink-0 animate-spin text-muted-foreground" />
<div className="min-w-0">
<div className="text-xs font-medium text-foreground">{loadingStep.title}</div>
<div className="mt-0.5 text-xs text-muted-foreground">{loadingStep.detail}</div>
</div>
</div>
) : hasAnyCandidates ? (
<div className="flex flex-wrap items-center justify-between gap-3 border-b border-border bg-muted/25 px-4 py-2.5">
@ -486,12 +521,16 @@ export default function WorkspaceCleanupDialog(): React.JSX.Element {
<div className="min-w-0 text-sm font-medium text-foreground">
{selectedCount} selected
</div>
<StatusPill>{inactiveCount} inactive</StatusPill>
{readyCount > 0 ? (
<StatusPill tone="ready">{readyCount} safe to remove</StatusPill>
) : null}
{groups.review.length > 0 ? (
<StatusPill tone="review">{groups.review.length} need review</StatusPill>
) : null}
{protectedCount > 0 ? (
<StatusPill>{protectedCount} not suggested</StatusPill>
) : null}
</div>
<div className="flex min-w-0 flex-wrap items-center gap-2">
{eligibleRepos.length > 1 ? (