diff --git a/apps/web/src/components/LegacySidebar.tsx b/apps/web/src/components/LegacySidebar.tsx index cb3484e097f8f..e8993ddf1d449 100644 --- a/apps/web/src/components/LegacySidebar.tsx +++ b/apps/web/src/components/LegacySidebar.tsx @@ -104,6 +104,8 @@ import { useOrchestrationCommand, } from "../state/use-orchestration-command"; import { previewEnvironment } from "../state/preview"; +import { vcsEnvironment } from "../state/vcs"; +import { useEnvironmentQuery } from "../state/query"; import { readEnvironmentScope, useEnvironmentScope } from "../state/session"; import { legacyProjectCwdPreferenceKey, @@ -198,9 +200,11 @@ import { orderItemsByPreferredIds, shouldClearThreadSelectionOnMouseDown, sortProjectsForSidebar, + useRetainedValue, useSidebarRowSubscriptionLease, useThreadJumpHintVisibility, ThreadStatusPill, + withoutRemovedCheckout, } from "./Sidebar.logic"; import { sortThreads } from "../lib/threadSort"; import { SidebarChromeFooter, SidebarChromeHeader } from "./sidebar/SidebarChrome"; @@ -461,6 +465,19 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP thread.pullRequests, thread.branchPullRequest, ); + const gitStatus = useEnvironmentQuery( + leaseLiveStatus && thread.worktreePath !== null + ? vcsEnvironment.status({ + environmentId: thread.environmentId, + input: { cwd: thread.worktreePath, includeRemote: false }, + }) + : null, + ); + const visibleGitStatus = useRetainedValue( + JSON.stringify([thread.environmentId, thread.worktreePath]), + gitStatus.data, + ); + const checkoutThread = withoutRemovedCheckout(thread, visibleGitStatus); const pr = linkedPullRequestStatus?.pr ?? null; const supportsMultiplePullRequests = useSupportsMultiplePullRequests(thread.environmentId); // Hooks must precede this ref's plain uses for the compiler to cache it. @@ -779,7 +796,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP )} - + (key: string | null, value: T | null): T | nu return key !== null && retained?.key === key ? retained.value : null; } +// A known non-repository cwd means the stored checkout was removed; unknown status keeps it. +export function withoutRemovedCheckout< + T extends { branch: string | null; worktreePath: string | null }, +>(thread: T, gitStatus: { readonly isRepo: boolean } | null): T { + return gitStatus?.isRepo === false ? { ...thread, branch: null, worktreePath: null } : thread; +} + // Sortable owns drag transforms; replaying committed order would animate drops twice. export const animateSidebarLayoutChanges: AnimateLayoutChanges = (args) => args.isSorting ? defaultAnimateLayoutChanges(args) : false; diff --git a/apps/web/src/components/sidebar/SidebarThreadRow.tsx b/apps/web/src/components/sidebar/SidebarThreadRow.tsx index c2393f3fe9b7b..f6bb5d6fa9331 100644 --- a/apps/web/src/components/sidebar/SidebarThreadRow.tsx +++ b/apps/web/src/components/sidebar/SidebarThreadRow.tsx @@ -83,6 +83,7 @@ import { shouldRecedeSidebarThread, resolveWorkingStartedAt, useRetainedValue, + withoutRemovedCheckout, useSidebarRowSubscriptionLease, } from "../Sidebar.logic"; import { resolveLocalCheckoutBranchMismatch } from "../BranchToolbar.logic"; @@ -629,6 +630,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP JSON.stringify([thread.environmentId, gitCwd]), gitStatus.data, ); + const checkoutThread = withoutRemovedCheckout(thread, visibleGitStatus); const pr = linkedPullRequestStatus?.pr ?? null; const supportsMultiplePullRequests = useSupportsMultiplePullRequests(thread.environmentId); const currentLinkedPr = supportsMultiplePullRequests @@ -710,9 +712,9 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP const isWokeStatus = topStatus?.icon === "woke"; const branchMismatch = resolveLocalCheckoutBranchMismatch({ - effectiveEnvMode: thread.worktreePath === null ? "local" : "worktree", - activeWorktreePath: thread.worktreePath, - activeThreadBranch: thread.branch, + effectiveEnvMode: checkoutThread.worktreePath === null ? "local" : "worktree", + activeWorktreePath: checkoutThread.worktreePath, + activeThreadBranch: checkoutThread.branch, currentGitBranch: visibleGitStatus?.refName ?? null, }); const prStatus = prStatusIndicator(pr, linkedPullRequestStatus?.sourceControlProvider); @@ -730,11 +732,11 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP : thread.modelSelection.model; const isRemote = thread.environmentId !== props.currentEnvironmentId; - const showsScratchMachine = !thread.branch && props.scratchMachineLabel !== null; + const showsScratchMachine = !checkoutThread.branch && props.scratchMachineLabel !== null; const detailsTooltip = (
- {thread.branch ? ( + {checkoutThread.branch ? ( <> - + - + ) : showsScratchMachine ? ( @@ -1563,10 +1565,11 @@ export const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props JSON.stringify([thread.environmentId, gitCwd]), gitStatus.data, ); + const checkoutThread = withoutRemovedCheckout(thread, visibleGitStatus); const branchMismatch = resolveLocalCheckoutBranchMismatch({ - effectiveEnvMode: thread.worktreePath === null ? "local" : "worktree", - activeWorktreePath: thread.worktreePath, - activeThreadBranch: thread.branch, + effectiveEnvMode: checkoutThread.worktreePath === null ? "local" : "worktree", + activeWorktreePath: checkoutThread.worktreePath, + activeThreadBranch: checkoutThread.branch, currentGitBranch: visibleGitStatus?.refName ?? null, }); const modelInstanceId = thread.runtime?.providerInstanceId ?? thread.modelSelection.instanceId; @@ -1679,7 +1682,7 @@ export const SidebarSearchResultRow = memo(function SidebarSearchResultRow(props