From 9738870489d04ee168f87263d85922ae2a858d42 Mon Sep 17 00:00:00 2001 From: David Metcalfe <80915+DavidMetcalfe@users.noreply.github.com> Date: Fri, 26 Jun 2026 08:05:42 -0700 Subject: [PATCH] fix(desktop): call checkUpdates() in startUpdatePoller so version pill auto-populates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- apps/desktop/src/store/updates.test.ts | 74 +++++++++++++++++++++++++- apps/desktop/src/store/updates.ts | 3 ++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/store/updates.test.ts b/apps/desktop/src/store/updates.test.ts index 494d65319..5ecb9c52c 100644 --- a/apps/desktop/src/store/updates.test.ts +++ b/apps/desktop/src/store/updates.test.ts @@ -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 = {} + + 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() + }) +}) diff --git a/apps/desktop/src/store/updates.ts b/apps/desktop/src/store/updates.ts index eb70afcb3..5efe64771 100644 --- a/apps/desktop/src/store/updates.ts +++ b/apps/desktop/src/store/updates.ts @@ -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() }