Clarify inactive workspace cleanup review (#2533)
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
parent
cb15124cbb
commit
2e3627fd8e
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 ? (
|
||||
|
|
|
|||
Loading…
Reference in New Issue