Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/renderer/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { KeyboardSettingsProvider } from './contexts/KeyboardSettingsContext';
import { ToastAction } from './components/ui/toast';
import { Toaster } from './components/ui/toaster';
import { useToast } from './hooks/use-toast';
import { useAutoPrRefresh } from './hooks/useAutoPrRefresh';
import { useGithubAuth } from './hooks/useGithubAuth';
import { useTheme } from './hooks/useTheme';
import useUpdateNotifier from './hooks/useUpdateNotifier';
Expand Down Expand Up @@ -151,6 +152,9 @@ const AppContent: React.FC = () => {
// Show toast on update availability and kick off a background check
useUpdateNotifier({ checkOnMount: true, onOpenSettings: () => setShowSettings(true) });

// Auto-refresh PR status on window focus and periodic polling for active task
useAutoPrRefresh(activeTask?.path);

const defaultPanelLayout = React.useMemo(() => {
const stored = loadPanelSizes(PANEL_LAYOUT_STORAGE_KEY, DEFAULT_PANEL_LAYOUT);
const [storedLeft = DEFAULT_PANEL_LAYOUT[0], , storedRight = DEFAULT_PANEL_LAYOUT[2]] =
Expand Down
74 changes: 74 additions & 0 deletions src/renderer/hooks/useAutoPrRefresh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useEffect, useRef } from 'react';
import { refreshPrStatus, refreshAllSubscribedPrStatus } from '../lib/prStatusStore';

const POLLING_INTERVAL_MS = 30000; // 30 seconds
const COOLDOWN_MS = 5000; // 5 second debounce for rapid focus/visibility events

/**
* Auto-refreshes PR status via:
* 1. Window focus - refreshes all subscribed tasks (debounced)
* 2. Polling - refreshes active task every 30s (pauses when hidden)
*/
export function useAutoPrRefresh(activeTaskPath: string | undefined): void {
const lastFocusRefresh = useRef(0);
const lastVisibilityRefresh = useRef(0);

// Window focus refresh (all subscribed tasks, debounced)
useEffect(() => {
const handleFocus = () => {
const now = Date.now();
if (now - lastFocusRefresh.current < COOLDOWN_MS) return;
lastFocusRefresh.current = now;
refreshAllSubscribedPrStatus().catch(() => {});
};

window.addEventListener('focus', handleFocus);
return () => window.removeEventListener('focus', handleFocus);
}, []);

// Polling for active task (pauses when window hidden)
useEffect(() => {
if (!activeTaskPath) return;

let intervalId: ReturnType<typeof setInterval> | null = null;

const startPolling = () => {
if (intervalId) return;
intervalId = setInterval(() => {
refreshPrStatus(activeTaskPath).catch(() => {});
}, POLLING_INTERVAL_MS);
};

const stopPolling = () => {
if (intervalId) {
clearInterval(intervalId);
intervalId = null;
}
};

const handleVisibilityChange = () => {
if (document.hidden) {
stopPolling();
} else {
const now = Date.now();
if (now - lastVisibilityRefresh.current >= COOLDOWN_MS) {
lastVisibilityRefresh.current = now;
refreshPrStatus(activeTaskPath).catch(() => {});
}
startPolling();
}
};

// Start polling if visible
if (!document.hidden) {
startPolling();
}

document.addEventListener('visibilitychange', handleVisibilityChange);

return () => {
stopPolling();
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, [activeTaskPath]);
}
10 changes: 10 additions & 0 deletions src/renderer/lib/prStatusStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ export async function refreshPrStatus(taskPath: string): Promise<PrStatus | null
}
}

/**
* Refresh PR status for all currently subscribed task paths.
* Used on window focus to update all visible PR buttons.
*/
export async function refreshAllSubscribedPrStatus(): Promise<void> {
const paths = Array.from(listeners.keys());
await Promise.all(paths.map(refreshPrStatus));
}

export function subscribeToPrStatus(taskPath: string, listener: Listener): () => void {
const set = listeners.get(taskPath) || new Set<Listener>();
set.add(listener);
Expand All @@ -70,6 +79,7 @@ export function subscribeToPrStatus(taskPath: string, listener: Listener): () =>
taskListeners.delete(listener);
if (taskListeners.size === 0) {
listeners.delete(taskPath);
cache.delete(taskPath); // Clear cache when no subscribers
}
}
};
Expand Down