diff --git a/src/browser/hooks/useReviewRefreshController.test.ts b/src/browser/hooks/useReviewRefreshController.test.ts index ccbb42c5e4..003d273ae6 100644 --- a/src/browser/hooks/useReviewRefreshController.test.ts +++ b/src/browser/hooks/useReviewRefreshController.test.ts @@ -59,7 +59,9 @@ describe("useReviewRefreshController design", () => { test("visibility contract: hidden tab queues refresh for later", () => { // Contract: When document.hidden is true, refresh is queued. - // When visibilitychange fires and document.hidden becomes false, queued refresh executes. + // When visibilitychange fires or window.focus fires (and document is visible), + // queued refresh executes. Uses both events since visibilitychange alone is + // unreliable in Electron when app is behind other windows or on different desktop. // This prevents wasted git operations when user isn't looking. expect(true).toBe(true); }); diff --git a/src/browser/hooks/useReviewRefreshController.ts b/src/browser/hooks/useReviewRefreshController.ts index 106e279d35..0a37ffbb2b 100644 --- a/src/browser/hooks/useReviewRefreshController.ts +++ b/src/browser/hooks/useReviewRefreshController.ts @@ -238,17 +238,23 @@ export function useReviewRefreshController( // eslint-disable-next-line react-hooks/exhaustive-deps }, [api, workspaceId, isCreating]); - // Handle visibility changes - flush pending refresh when tab becomes visible + // Handle visibility/focus changes - flush pending refresh when user returns. + // Uses both visibilitychange (for browser tab hidden state) and window focus + // (for Electron app focus) since visibilitychange alone is unreliable in Electron + // when the app is behind other windows or on a different desktop/space. useEffect(() => { - const handleVisibilityChange = () => { + const handleReturn = () => { + // Only flush if document is actually visible if (!document.hidden) { flushPending("hidden"); } }; - document.addEventListener("visibilitychange", handleVisibilityChange); + document.addEventListener("visibilitychange", handleReturn); + window.addEventListener("focus", handleReturn); return () => { - document.removeEventListener("visibilitychange", handleVisibilityChange); + document.removeEventListener("visibilitychange", handleReturn); + window.removeEventListener("focus", handleReturn); }; // flushPending is stable (only uses refs internally) // eslint-disable-next-line react-hooks/exhaustive-deps