From 6f47695573467d6d62104ee18d0ea011ca77fdcf Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Mon, 31 Aug 2026 00:34:24 -0700 Subject: [PATCH] fix(status-bar): preserve workspace-space review and agent freshness --- ...use-workspace-space-decision-projection.ts | 14 +++++------ .../workspace-space-decision-details.ts | 20 ++++++++++++++-- .../workspace-space-presentation.test.ts | 24 +++++++++++++++++++ 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/renderer/src/components/status-bar/use-workspace-space-decision-projection.ts b/src/renderer/src/components/status-bar/use-workspace-space-decision-projection.ts index 18cc5b42e57..78519edde52 100644 --- a/src/renderer/src/components/status-bar/use-workspace-space-decision-projection.ts +++ b/src/renderer/src/components/status-bar/use-workspace-space-decision-projection.ts @@ -1,5 +1,5 @@ import { useCallback, useMemo } from 'react' -import { useNow } from '@/hooks/use-now' +import { getAgentStatusEpochNow } from '@/lib/agent-status-epoch-clock' import { getWorkspaceDecisionDetails, type WorkspaceDecisionDetails @@ -40,7 +40,6 @@ export function useWorkspaceSpaceDecisionProjection(bindings: WorkspaceSpaceMana } = bindings const sourceRows = useMemo(() => analysis?.worktrees ?? [], [analysis?.worktrees]) - const now = useNow(30_000) const worktreeIdCounts = useMemo(() => { const counts = new Map() for (const row of sourceRows) { @@ -49,9 +48,9 @@ export function useWorkspaceSpaceDecisionProjection(bindings: WorkspaceSpaceMana return counts }, [sourceRows]) const decisionDetailsByWorktreeId = useMemo(() => { - // Why: active-agent freshness is time-based. The epoch bumps when fresh - // hook entries cross the stale boundary so delete readiness recomputes. - void agentStatusEpoch + // Why: the epoch bumps when fresh hook entries cross the stale boundary so + // delete readiness recomputes with the same wall-clock sample as the store. + const agentStatusNow = getAgentStatusEpochNow(agentStatusEpoch) const details = new Map() for (const worktree of sourceRows) { details.set( @@ -78,7 +77,7 @@ export function useWorkspaceSpaceDecisionProjection(bindings: WorkspaceSpaceMana settings, activeWorktreeId, activeWorkspaceExecutionHostId, - now + now: agentStatusNow }) ) } @@ -106,8 +105,7 @@ export function useWorkspaceSpaceDecisionProjection(bindings: WorkspaceSpaceMana settings, sourceRows, tabsByWorktree, - worktreeMap, - now + worktreeMap ]) const getDeleteStateForWorktree = useCallback( (worktree: WorkspaceSpaceWorktree) => diff --git a/src/renderer/src/components/status-bar/workspace-space-decision-details.ts b/src/renderer/src/components/status-bar/workspace-space-decision-details.ts index ffab49cbe7c..2265d0e9a4a 100644 --- a/src/renderer/src/components/status-bar/workspace-space-decision-details.ts +++ b/src/renderer/src/components/status-bar/workspace-space-decision-details.ts @@ -3,9 +3,11 @@ import type { MigrationUnsupportedPtyEntry } from '../../../../shared/agent-status-types' import type { ExecutionHostId } from '../../../../shared/execution-host' +import type { HostedReviewProvider } from '../../../../shared/hosted-review' import type { Repo } from '../../../../shared/repo-types' import type { TerminalTab } from '../../../../shared/terminal-tab-types' import type { Worktree } from '../../../../shared/worktree/types' +import { isGitHubPRSuppressed } from '../../../../shared/worktree/github-pr-suppression' import type { WorkspaceSpaceWorktree } from '../../../../shared/workspace-space-types' import { branchDisplayName } from '../sidebar/WorktreeCardHelpers' import { findRepoForHost } from '../../store/slices/repo-host-identity' @@ -50,7 +52,15 @@ export type WorkspaceDecisionInputs = { remoteStatusesByWorktree: Record hostedReviewCache: Record< string, - { data?: { number: number; state: string; status: string; title: string } | null } + { + data?: { + number: number + state: string + status: string + title: string + provider?: HostedReviewProvider + } | null + } > issueCache: Record linearIssueCache: Record< @@ -129,7 +139,13 @@ export function getWorkspaceDecisionDetails( ownerExecutionHostId, repo !== null && repo !== undefined ) - const hostedReview = inputs.hostedReviewCache[reviewCacheKey]?.data + const cachedHostedReview = inputs.hostedReviewCache[reviewCacheKey]?.data + const hostedReview = + cachedHostedReview?.provider === 'github' && + workspaceRecord && + isGitHubPRSuppressed(workspaceRecord, cachedHostedReview.number) + ? null + : cachedHostedReview const linkedPR = workspaceRecord?.linkedPR ?? null const reviewLabel = hostedReview !== undefined && hostedReview !== null diff --git a/src/renderer/src/components/status-bar/workspace-space-presentation.test.ts b/src/renderer/src/components/status-bar/workspace-space-presentation.test.ts index e0d3a868fa7..666db63968f 100644 --- a/src/renderer/src/components/status-bar/workspace-space-presentation.test.ts +++ b/src/renderer/src/components/status-bar/workspace-space-presentation.test.ts @@ -428,6 +428,30 @@ describe('workspace space presentation helpers', () => { expect(details.issueLabel).toBe('#123 open: Local owner issue') }) + it('hides a GitHub review explicitly suppressed after unlinking', () => { + const details = getWorkspaceDecisionDetails( + row({ branch: 'refs/heads/feature/local' }), + decisionInputs({ + hostedReviewCache: { + 'local::repo::feature/local': { + data: { + number: 12, + state: 'open', + status: 'success', + title: 'Suppressed review', + provider: 'github' + } + } + }, + worktreeMap: new Map([ + ['wt', worktreeRecord({ branch: 'refs/heads/feature/local', suppressedGitHubPR: 12 })] + ]) + }) + ) + + expect(details.reviewLabel).toBeNull() + }) + it('counts migration-unsupported agent entries by worktree id', () => { const count = countWorkspaceSpaceActiveAgents({ worktreeId: 'wt',