Use branch PR cache for sidebar status

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
brennanb2025
2026-06-25 11:11:38 -07:00
co-authored by Orca
parent 7dcdf6d781
commit ee71c3d33f
3 changed files with 85 additions and 12 deletions
@@ -2,7 +2,13 @@ import { renderToStaticMarkup } from 'react-dom/server'
import type { ReactNode } from 'react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { HostedReviewInfo } from '../../../../shared/hosted-review'
import type { GlobalSettings, Repo, Worktree, WorktreeCardProperty } from '../../../../shared/types'
import type {
GlobalSettings,
PRInfo,
Repo,
Worktree,
WorktreeCardProperty
} from '../../../../shared/types'
import { COMPACT_WORKTREE_CARD_PROPERTIES } from '../../../../shared/worktree-card-properties'
import type { WorkspacePortScanResult } from '../../../../shared/workspace-ports'
@@ -14,6 +20,7 @@ const updateWorktreeMeta = vi.fn()
let worktreeCardProperties: WorktreeCardProperty[] = ['status']
let hostedReviewCache: Record<string, unknown> = {}
let prCache: Record<string, unknown> = {}
let workspacePortScan: WorkspacePortScanResult | null = null
let settings: Partial<GlobalSettings> | null = null
@@ -29,6 +36,7 @@ vi.mock('@/store', () => ({
issueCache: {},
linearIssueCache: {},
openModal,
prCache,
projectGroups: [],
remoteBranchConflictByWorktreeId: {},
settings,
@@ -121,6 +129,19 @@ function makeHostedReview(overrides: Partial<HostedReviewInfo> = {}): HostedRevi
}
}
function makePRInfo(overrides: Partial<PRInfo> = {}): PRInfo {
return {
number: 456,
title: 'Fix stale GH PR',
state: 'open',
url: 'https://github.com/acme/orca/pull/456',
checksStatus: 'success',
updatedAt: '2026-05-17T00:00:00.000Z',
mergeable: 'MERGEABLE',
...overrides
}
}
function renderWorktreeCardMarkup(element: ReactNode): string {
return renderToStaticMarkup(<>{element}</>)
}
@@ -140,6 +161,7 @@ describe('WorktreeCard linked PR display', () => {
vi.clearAllMocks()
worktreeCardProperties = ['status']
hostedReviewCache = {}
prCache = {}
workspacePortScan = null
settings = null
})
@@ -616,4 +638,34 @@ describe('WorktreeCard linked PR display', () => {
expect(markup).not.toContain('Linked PR #456')
expect(markup).not.toContain('CI checks')
})
it('uses branch PR cache for the status slot before hosted-review metadata warms', async () => {
settings = { experimentalNewWorktreeCardStyle: true }
worktreeCardProperties = ['status']
prCache = {
'repo-1::feature/local-branch': {
data: makePRInfo({
number: 6340,
title: 'Remove split terminal from onboarding checklist',
state: 'merged',
checksStatus: 'success'
}),
fetchedAt: Date.now()
}
}
const { default: WorktreeCard } = await import('./WorktreeCard')
const markup = renderWorktreeCardMarkup(
<WorktreeCard
worktree={makeWorktree({ linkedPR: null })}
repo={makeRepo()}
isActive={false}
/>
)
expect(markup).toContain('PR: Merged')
expect(markup).toContain('text-purple-600/70')
expect(markup).not.toContain('Branch')
expect(markup).not.toContain('lucide-git-branch')
})
})
@@ -3,6 +3,7 @@ import React, { useEffect, useCallback, useState } from 'react'
import { useAppStore } from '@/store'
import { getHostedReviewCacheKey } from '@/store/slices/hosted-review'
import { issueCacheKey as getIssueCacheKey } from '@/store/slices/github'
import { getGitHubPRCacheKey } from '@/store/slices/github-cache-key'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Tooltip, TooltipTrigger, TooltipContent } from '@/components/ui/tooltip'
@@ -29,6 +30,7 @@ import { cn } from '@/lib/utils'
import { activateWorktreeFromSidebar } from '@/lib/sidebar-worktree-activation'
import { isFolderRepo } from '../../../../shared/repo-kind'
import type { HostedReviewInfo } from '../../../../shared/hosted-review'
import { hostedReviewInfoFromGitHubPRInfo } from '../../../../shared/hosted-review-github'
import type {
GitHubWorkItem,
Worktree,
@@ -398,6 +400,17 @@ const WorktreeCard = React.memo(function WorktreeCard({
repo.executionHostId
)
: ''
const prCacheKey =
repo && branch
? getGitHubPRCacheKey(
repo.path,
repo.id,
branch,
settings,
repo.connectionId,
repo.executionHostId
)
: ''
const issueCacheKey =
repo && worktree.linkedIssue
? getIssueCacheKey(
@@ -417,6 +430,7 @@ const WorktreeCard = React.memo(function WorktreeCard({
const hostedReviewEntry = useAppStore((s) =>
hostedReviewCacheKey ? s.hostedReviewCache[hostedReviewCacheKey] : undefined
)
const prCacheEntry = useAppStore((s) => (prCacheKey ? s.prCache[prCacheKey] : undefined))
const issueEntry = useAppStore((s) => (issueCacheKey ? s.issueCache[issueCacheKey] : undefined))
const linearIssueEntry = useAppStore((s) =>
linearIssueCacheKey ? s.linearIssueCache[linearIssueCacheKey] : undefined
@@ -427,19 +441,26 @@ const WorktreeCard = React.memo(function WorktreeCard({
const hostedReview: HostedReviewInfo | null | undefined =
hostedReviewEntry !== undefined ? hostedReviewEntry.data : undefined
// Why: ChecksPanel can discover a branch PR before hosted-review metadata
// warms. The sidebar status slot should not fall back to "branch" while
// the branch PR cache already knows the review state.
const cachedBranchReview =
hostedReview === undefined && prCacheEntry?.data
? hostedReviewInfoFromGitHubPRInfo(prCacheEntry.data)
: hostedReview
const linkedGitLabMR = worktree.linkedGitLabMR ?? null
const linkedBitbucketPR = worktree.linkedBitbucketPR ?? null
const linkedAzureDevOpsPR = worktree.linkedAzureDevOpsPR ?? null
const linkedGiteaPR = worktree.linkedGiteaPR ?? null
const prDisplay = getWorktreeCardPrDisplay(
hostedReview,
cachedBranchReview,
worktree.linkedPR,
linkedGitLabMR,
linkedBitbucketPR,
linkedAzureDevOpsPR,
linkedGiteaPR,
{
reviewHintKey: hostedReviewEntry?.linkedReviewHintKey
reviewHintKey: hostedReviewEntry?.linkedReviewHintKey ?? (prCacheEntry?.data ? '' : undefined)
}
)
const issue: IssueInfo | null | undefined = worktree.linkedIssue
@@ -65,15 +65,6 @@ function overlayNewCardUnreadStatus(
function getReviewStatusTooltip(review: WorktreeCardPrDisplay): string {
const label = getReviewLabel(review)
if (review.status === 'failure') {
return `${label} checks: Failed`
}
if (review.status === 'pending') {
return `${label} checks: Pending`
}
if (review.status === 'success') {
return `${label} checks: Passing`
}
if (review.state === 'merged') {
return `${label}: Merged`
}
@@ -83,6 +74,15 @@ function getReviewStatusTooltip(review: WorktreeCardPrDisplay): string {
if (review.state === 'draft') {
return `${label}: Draft`
}
if (review.status === 'failure') {
return `${label} checks: Failed`
}
if (review.status === 'pending') {
return `${label} checks: Pending`
}
if (review.status === 'success') {
return `${label} checks: Passing`
}
return `${label}: Open`
}