diff --git a/src/renderer/src/components/sidebar/WorktreeCard.pr-display.test.tsx b/src/renderer/src/components/sidebar/WorktreeCard.pr-display.test.tsx
index f3bc4768bc2..92282083b26 100644
--- a/src/renderer/src/components/sidebar/WorktreeCard.pr-display.test.tsx
+++ b/src/renderer/src/components/sidebar/WorktreeCard.pr-display.test.tsx
@@ -668,4 +668,173 @@ describe('WorktreeCard linked PR display', () => {
expect(markup).not.toContain('Branch')
expect(markup).not.toContain('lucide-git-branch')
})
+
+ it('keeps the detailed right-side PR badge during a transient hosted-review miss', async () => {
+ settings = { compactWorktreeCards: false, experimentalNewWorktreeCardStyle: false }
+ worktreeCardProperties = ['pr']
+ hostedReviewCache = {
+ 'local::repo-1::feature/local-branch': {
+ data: null,
+ fetchedAt: 100
+ }
+ }
+ prCache = {
+ 'repo-1::feature/local-branch': {
+ data: makePRInfo({
+ number: 6340,
+ title: 'Remove split terminal from onboarding checklist',
+ state: 'open',
+ checksStatus: 'success'
+ }),
+ fetchedAt: 200
+ }
+ }
+ const { default: WorktreeCard } = await import('./WorktreeCard')
+
+ const markup = renderWorktreeCardMarkup(
+
+ )
+
+ expect(markup).toContain('Linked PR #6340')
+ expect(markup).not.toContain('Linked PR #456')
+ })
+
+ it('keeps the detailed PR badge when a transient miss still has an older review hint', async () => {
+ settings = { compactWorktreeCards: false, experimentalNewWorktreeCardStyle: false }
+ worktreeCardProperties = ['pr']
+ hostedReviewCache = {
+ 'local::repo-1::feature/local-branch': {
+ data: null,
+ fetchedAt: 100,
+ linkedReviewHintKey: 'github:999'
+ }
+ }
+ prCache = {
+ 'repo-1::feature/local-branch': {
+ data: makePRInfo({
+ number: 6340,
+ title: 'Remove split terminal from onboarding checklist',
+ state: 'open',
+ checksStatus: 'success'
+ }),
+ fetchedAt: 200
+ }
+ }
+ const { default: WorktreeCard } = await import('./WorktreeCard')
+
+ const markup = renderWorktreeCardMarkup(
+
+ )
+
+ expect(markup).toContain('Linked PR #6340')
+ expect(markup).not.toContain('Linked PR #456')
+ })
+
+ it('keeps durable non-GitHub linked review metadata ahead of branch PR cache', async () => {
+ settings = { compactWorktreeCards: false, experimentalNewWorktreeCardStyle: false }
+ worktreeCardProperties = ['pr']
+ hostedReviewCache = {
+ 'local::repo-1::feature/local-branch': {
+ data: null,
+ fetchedAt: 100
+ }
+ }
+ prCache = {
+ 'repo-1::feature/local-branch': {
+ data: makePRInfo({
+ number: 6340,
+ title: 'Remove split terminal from onboarding checklist',
+ state: 'open',
+ checksStatus: 'success'
+ }),
+ fetchedAt: 200
+ }
+ }
+ const { default: WorktreeCard } = await import('./WorktreeCard')
+
+ const markup = renderWorktreeCardMarkup(
+
+ )
+
+ expect(markup).toContain('Linked MR #77')
+ expect(markup).not.toContain('Linked PR #6340')
+ })
+
+ it('does not resurrect an older PR cache entry after a newer hosted-review miss', async () => {
+ settings = { compactWorktreeCards: false, experimentalNewWorktreeCardStyle: false }
+ worktreeCardProperties = ['pr']
+ hostedReviewCache = {
+ 'local::repo-1::feature/local-branch': {
+ data: null,
+ fetchedAt: 200
+ }
+ }
+ prCache = {
+ 'repo-1::feature/local-branch': {
+ data: makePRInfo({
+ number: 6340,
+ title: 'Remove split terminal from onboarding checklist',
+ state: 'open',
+ checksStatus: 'success'
+ }),
+ fetchedAt: 100
+ }
+ }
+ const { default: WorktreeCard } = await import('./WorktreeCard')
+
+ const markup = renderWorktreeCardMarkup(
+
+ )
+
+ expect(markup).not.toContain('Linked PR #6340')
+ })
+
+ it('does not resurrect PR cache on the same millisecond as a hosted-review miss', async () => {
+ settings = { compactWorktreeCards: false, experimentalNewWorktreeCardStyle: false }
+ worktreeCardProperties = ['pr']
+ hostedReviewCache = {
+ 'local::repo-1::feature/local-branch': {
+ data: null,
+ fetchedAt: 200
+ }
+ }
+ prCache = {
+ 'repo-1::feature/local-branch': {
+ data: makePRInfo({
+ number: 6340,
+ title: 'Remove split terminal from onboarding checklist',
+ state: 'open',
+ checksStatus: 'success'
+ }),
+ fetchedAt: 200
+ }
+ }
+ const { default: WorktreeCard } = await import('./WorktreeCard')
+
+ const markup = renderWorktreeCardMarkup(
+
+ )
+
+ expect(markup).not.toContain('Linked PR #6340')
+ })
})
diff --git a/src/renderer/src/components/sidebar/WorktreeCard.tsx b/src/renderer/src/components/sidebar/WorktreeCard.tsx
index 7945c35a149..4d88afd55dd 100644
--- a/src/renderer/src/components/sidebar/WorktreeCard.tsx
+++ b/src/renderer/src/components/sidebar/WorktreeCard.tsx
@@ -441,26 +441,48 @@ 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 linkedGitHubPR = worktree.linkedPR ?? null
const linkedGitLabMR = worktree.linkedGitLabMR ?? null
const linkedBitbucketPR = worktree.linkedBitbucketPR ?? null
const linkedAzureDevOpsPR = worktree.linkedAzureDevOpsPR ?? null
const linkedGiteaPR = worktree.linkedGiteaPR ?? null
+ const hasNonGitHubLinkedReview =
+ linkedGitLabMR !== null ||
+ linkedBitbucketPR !== null ||
+ linkedAzureDevOpsPR !== null ||
+ linkedGiteaPR !== null
+ const hasLinkedReview =
+ linkedGitHubPR !== null ||
+ linkedGitLabMR !== null ||
+ linkedBitbucketPR !== null ||
+ linkedAzureDevOpsPR !== null ||
+ linkedGiteaPR !== null
+ // Why: ChecksPanel can discover a branch PR before hosted-review metadata
+ // warms, and transient older hosted-review misses can race with that cache.
+ // Newer hosted-review misses still win so stale PR cache cannot resurrect.
+ const cachedBranchPR = prCacheEntry?.data
+ const cachedBranchPRFetchedAt = prCacheEntry?.fetchedAt
+ const useCachedBranchReview =
+ cachedBranchPR !== undefined &&
+ cachedBranchPR !== null &&
+ !hasNonGitHubLinkedReview &&
+ (hostedReview === undefined ||
+ (hostedReview === null &&
+ cachedBranchPRFetchedAt !== undefined &&
+ cachedBranchPRFetchedAt > (hostedReviewEntry?.fetchedAt ?? 0)))
+ const cachedBranchReview = useCachedBranchReview
+ ? hostedReviewInfoFromGitHubPRInfo(cachedBranchPR)
+ : hostedReview
const prDisplay = getWorktreeCardPrDisplay(
cachedBranchReview,
- worktree.linkedPR,
+ linkedGitHubPR,
linkedGitLabMR,
linkedBitbucketPR,
linkedAzureDevOpsPR,
linkedGiteaPR,
{
- reviewHintKey: hostedReviewEntry?.linkedReviewHintKey ?? (prCacheEntry?.data ? '' : undefined)
+ reviewHintKey:
+ useCachedBranchReview && !hasLinkedReview ? '' : hostedReviewEntry?.linkedReviewHintKey
}
)
const issue: IssueInfo | null | undefined = worktree.linkedIssue