fix(menubar): popover-open always force-recovers current key

Opening the popover now unconditionally restarts a dead timer and
clears stuck loading bookkeeping before force-fetching, so a user
looking at a stuck tab always recovers within one CLI round-trip.
This commit is contained in:
iamtoruk 2026-05-29 23:51:48 -07:00
parent ea64dca52e
commit 75edff64ec
2 changed files with 34 additions and 8 deletions

View file

@ -512,15 +512,23 @@ final class AppDelegate: NSObject, NSApplicationDelegate, NSPopoverDelegate {
}
private func refreshPayloadForPopoverOpen() {
guard store.needsInteractivePayloadRefresh else { return }
let shouldResetPipeline = store.shouldResetInteractiveRefreshPipeline
if shouldResetPipeline, let age = store.staleInteractivePayloadAgeSeconds {
NSLog("CodeBurn: popover opened with %ds stale payload cache - resetting refresh pipeline", age)
// A user viewing the popover is ground truth and must always recover.
// Unconditionally ensure the loop is alive, then clear the current
// key's stuck loading / in-flight / generation bookkeeping and force a
// fresh fetch even if the cache looks "not stale yet". This is the
// guaranteed one-round-trip recovery path.
if refreshTimer == nil {
startRefreshLoop(forceQuotaOnStart: false)
}
if store.shouldResetInteractiveRefreshPipeline,
let age = store.staleInteractivePayloadAgeSeconds {
NSLog("CodeBurn: popover opened with %ds stale payload cache - hard recovery", age)
}
Task { [weak self] in
guard let self else { return }
await self.store.recoverFromStuckLoading()
self.refreshStatusButton()
}
recoverRefreshPipelineAfterInterruption(
resetLoading: shouldResetPipeline,
reason: "popover open"
)
}
private func stopRefreshTimer() {

View file

@ -119,4 +119,22 @@ struct AppStoreRefreshRecoveryTests {
#expect(store.isInFlightForTesting(period: .today, provider: .all))
}
@Test("prepareStuckLoadingRecovery clears stale loading bookkeeping for the current key")
func popoverRecoveryClearsStuckLoading() {
let store = AppStore()
// Seed an orphaned in-flight entry older than the 60s watchdog so the
// stale-clear path runs, mimicking a fetch torn down across sleep/wake.
store.seedInFlightForTesting(
period: .today,
provider: .all,
insertedAt: Date().addingTimeInterval(-120)
)
#expect(store.isInFlightForTesting(period: .today, provider: .all))
let willFetch = store.prepareStuckLoadingRecovery()
#expect(willFetch)
#expect(!store.isInFlightForTesting(period: .today, provider: .all))
}
}