Files
orca/src/shared/workspace-last-active.ts
T
Kelvin Amoaba 1ed47daf1f fix(workspace-cleanup): show merged and unchanged workspaces instead of flagging landed commits as unpushed (#41408)
* feat(workspace-cleanup): mark merged and unchanged workspaces from git evidence

Squash-merged branches whose remote was deleted were flagged as unpushed
and needed a forced delete. The scan now reads what HEAD holds beyond the
workspace's base (ancestor, Git's squash proof, or a cached merged PR) and
reports merged/unchanged reasons; landed commits no longer count as at risk.
A gone upstream no longer hides unpushed commits. Renames idle-clean to
inactive and drops the dead tier, selectedByDefault and volatile-local-context.

* fix(workspace-cleanup): read HEAD once, skip a reused branch as its own base

Merge evidence now compares the cached PR and the base against the HEAD it
reads, not the listing's possibly stale one; a workspace opened on an
existing branch no longer counts that branch as its base; the proof
wrapper settles the WSL route before building its caches; a visit keeps
merged/unchanged reasons; landed rows stop sorting as unpushed.

* fix(workspace-cleanup): qualify base refs and trust the branch proof only for HEAD

Base refs resolve through the worktree base-ref qualifier so a tag cannot
shadow them and only the branch's own family is skipped, including the
repo default; SHA-256 pinned commits are not bases; the squash proof runs
only while the branch ref is the HEAD just read; expanded details stop
counting landed commits as unpushed.

* test(workspace-cleanup): build the repo fixture without a cast

* fix(workspace-cleanup): never take a tag as a workspace's base
2026-11-12 08:14:14 +00:00

40 lines
1.5 KiB
TypeScript

import { ORCA_SIDEBAR_INPUTS_IS_MAIN } from './sidebar-inputs-switch'
import {
getWorkspaceCleanupInactivityReasons,
type WorkspaceCleanupCandidate
} from './workspace-cleanup/workspace-cleanup'
/** With the switch on, visits count because terminal start/exit no longer advance Recent; plain-shell work must not appear idle. */
export function getWorkspaceLastActiveAt(
lastActivityAt: number,
lastVisitedAt: number | undefined
): number {
if (!ORCA_SIDEBAR_INPUTS_IS_MAIN) {
return lastActivityAt
}
const finite = (value: number | undefined): number =>
value !== undefined && Number.isFinite(value) ? value : 0
return Math.max(finite(lastActivityAt), finite(lastVisitedAt))
}
/** A cleanup candidate seen with the client's visit; a newer visit can only drop idle reasons. */
export function withCleanupVisitEvidence(
candidate: WorkspaceCleanupCandidate,
lastVisitedAt: number | undefined,
now: number
): WorkspaceCleanupCandidate {
const lastActiveAt = getWorkspaceLastActiveAt(candidate.lastActivityAt, lastVisitedAt)
if (lastActiveAt <= candidate.lastActivityAt) {
return candidate
}
const idleReasons = getWorkspaceCleanupInactivityReasons(
{ isArchived: candidate.reasons.includes('archived'), lastActivityAt: lastActiveAt },
now
)
// Merged/unchanged come from Git, not age, so a visit keeps them.
const reasons = candidate.reasons.filter(
(reason) => (reason !== 'archived' && reason !== 'inactive') || idleReasons.includes(reason)
)
return { ...candidate, lastActivityAt: lastActiveAt, reasons }
}