fix(desktop): call checkUpdates() in startUpdatePoller so version pill auto-populates

startUpdatePoller() only called checkBackendUpdates() — never checkUpdates().
The statusbar version pill reads $updateStatus (set by checkUpdates()), so the
commit-behind counter stayed null after restart. It only appeared when the user
manually clicked the pill, which triggered checkUpdates() via openUpdateOverlayFor.

Added void checkUpdates() in three places alongside the existing
checkBackendUpdates() calls:
- On startup in startUpdatePoller()
- In the 30-minute setInterval callback
- In the onFocus handler

checkUpdates() uses the Electron IPC bridge (local git check), not the gateway,
so no mode gating is needed. The existing $updateChecking atom guard prevents
double-fire on overlap.

Fixes #53079
This commit is contained in:
David Metcalfe 2026-06-26 08:05:42 -07:00 committed by brooklyn!
parent 63354edfd7
commit 9738870489
2 changed files with 76 additions and 1 deletions

View file

@ -51,7 +51,10 @@ const {
applyUpdates,
$updateApply,
$updateOverlayOpen,
resetUpdateApplyState
resetUpdateApplyState,
startUpdatePoller,
stopUpdatePoller,
$updateStatus
} = await import('./updates')
const { setConnection } = await import('./session')
@ -454,3 +457,72 @@ describe('applyBackendUpdate recovery', () => {
expect($backendUpdateApply.get().stage).toBe('error')
})
})
describe('startUpdatePoller', () => {
const checkMock = vi.fn()
const onProgressMock = vi.fn()
const listeners: Record<string, Function> = {}
beforeEach(() => {
storage.clear()
checkMock.mockReset()
onProgressMock.mockReset()
Object.keys(listeners).forEach(k => delete listeners[k])
checkMock.mockResolvedValue({
supported: true,
behind: 5,
targetSha: 'sha-abc',
fetchedAt: 0
})
$updateStatus.set(null)
;(globalThis as unknown as { window: unknown }).window = {
hermesDesktop: { updates: { check: checkMock, onProgress: onProgressMock } },
addEventListener: vi.fn((event: string, handler: Function) => {
listeners[event] = handler
}),
removeEventListener: vi.fn()
}
vi.useFakeTimers()
stopUpdatePoller()
})
afterEach(() => {
stopUpdatePoller()
delete (globalThis as unknown as { window?: unknown }).window
vi.useRealTimers()
})
it('calls checkUpdates() on startup so the version pill populates immediately', async () => {
startUpdatePoller()
// checkUpdates() is async — flush microtasks without advancing the 30-min interval.
await vi.advanceTimersByTimeAsync(0)
expect(checkMock).toHaveBeenCalled()
expect($updateStatus.get()?.behind).toBe(5)
})
it('calls checkUpdates() on each interval tick', async () => {
startUpdatePoller()
await vi.advanceTimersByTimeAsync(0)
checkMock.mockClear()
await vi.advanceTimersByTimeAsync(30 * 60 * 1000)
expect(checkMock).toHaveBeenCalled()
})
it('calls checkUpdates() when the window regains focus', async () => {
startUpdatePoller()
await vi.advanceTimersByTimeAsync(0)
checkMock.mockClear()
// Invoke the registered focus handler directly (the mock window doesn't
// propagate DOM events, so call the stored listener).
listeners['focus']?.()
await vi.advanceTimersByTimeAsync(0)
expect(checkMock).toHaveBeenCalled()
})
})

View file

@ -611,6 +611,7 @@ export function startUpdatePoller(): void {
}
pollerStarted = true
void checkUpdates()
void checkBackendUpdates()
void refreshDesktopVersion()
bridge.onProgress(ingestProgress)
@ -633,6 +634,7 @@ export function startUpdatePoller(): void {
window.addEventListener('focus', onFocus)
backgroundTimer = setInterval(
() => {
void checkUpdates()
void checkBackendUpdates()
},
30 * 60 * 1000
@ -660,6 +662,7 @@ function onFocus() {
}
lastFocusAt = now
void checkUpdates()
void checkBackendUpdates()
void refreshDesktopVersion()
}