diff --git a/src/main/github/pr-refresh-coordinator.test.ts b/src/main/github/pr-refresh-coordinator.test.ts index 61d166b621c..618b73b0ab0 100644 --- a/src/main/github/pr-refresh-coordinator.test.ts +++ b/src/main/github/pr-refresh-coordinator.test.ts @@ -126,6 +126,48 @@ describe('pr-refresh-coordinator', () => { expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(2) }) + it('lets a repeated active refresh pull forward an equal-priority visible follow-up', async () => { + const { enqueuePRRefresh } = await import('./pr-refresh-coordinator') + getPRForBranchOutcomeMock + .mockResolvedValueOnce({ + kind: 'found', + pr: makePR({ checksStatus: 'success' }), + fetchedAt: Date.now() + }) + .mockResolvedValueOnce({ + kind: 'found', + pr: makePR({ checksStatus: 'success', state: 'merged' }), + fetchedAt: Date.now() + }) + + const candidate = makeCandidate() + enqueuePRRefresh(candidate, 'active', 80, 1) + await vi.runOnlyPendingTimersAsync() + + enqueuePRRefresh( + { + ...candidate, + cachedFetchedAt: Date.now(), + cachedChecksStatus: 'success' + }, + 'active', + 80, + 1 + ) + await vi.runOnlyPendingTimersAsync() + + const inFlightEvents = sendMock.mock.calls + .map(([, event]) => event) + .filter((event) => event.status === 'in-flight') + const queuedEvents = sendMock.mock.calls + .map(([, event]) => event) + .filter((event) => event.status === 'queued') + + expect(inFlightEvents.map((event) => event.reason)).toEqual(['active', 'active']) + expect(queuedEvents).toHaveLength(0) + expect(getPRForBranchOutcomeMock).toHaveBeenCalledTimes(2) + }) + it('preserves coalesced aliases across visible follow-up refreshes', async () => { const { reportVisiblePRRefreshCandidates } = await import('./pr-refresh-coordinator') getPRForBranchOutcomeMock diff --git a/src/main/github/pr-refresh-coordinator.ts b/src/main/github/pr-refresh-coordinator.ts index cb5a7d1dc50..bd87f61fd51 100644 --- a/src/main/github/pr-refresh-coordinator.ts +++ b/src/main/github/pr-refresh-coordinator.ts @@ -144,7 +144,11 @@ function shouldBroadcastQueued(reason: GitHubPRRefreshReason, dueAt: number): bo if (isBudgetedBackground(reason)) { return false } - return dueAt - Date.now() <= 5_000 + const delay = dueAt - Date.now() + if (delay <= 0) { + return false + } + return delay <= 5_000 } function freshRetryAt(candidate: GitHubPRRefreshCandidate): number | null { @@ -412,7 +416,11 @@ export function enqueuePRRefresh( const dueAt = freshDueAt ?? Date.now() + (reason === 'post-push' ? POST_PUSH_DELAY_MS : 0) if (existing) { existing.aliases.set(alias.cacheKey, alias) - if (priority > existing.priority || isManual(reason)) { + const shouldPromoteExisting = + priority > existing.priority || + isManual(reason) || + (priority >= existing.priority && dueAt < existing.dueAt && bypassesFreshnessDelay(reason)) + if (shouldPromoteExisting) { existing.priority = priority existing.reason = reason existing.dueAt = Math.min(existing.dueAt, dueAt)