From 3b0ef79cdf8add5dfdd3ede3697cc51b0f777bea Mon Sep 17 00:00:00 2001 From: LinYushen Date: Wed, 4 Feb 2026 14:51:38 +0800 Subject: [PATCH] fix: resolve AI response not rendering after long idle/sleep (#129) * fix: resolve AI response not rendering after long idle/sleep - Add backgroundThrottling: false to prevent Chromium from throttling the renderer process when the window is in the background - Add powerMonitor.on('resume') to send APP_FOCUS event after system wake, triggering DB sync to recover any missed IPC messages - Re-sync session updates from DB on app focus to capture messages lost during sleep/wake or background throttling - Guard async DB sync with currentSessionIdRef check to prevent race condition when user switches sessions during the async call Co-Authored-By: Claude Opus 4.5 * fix: dedupe focus resync logs --------- Co-authored-by: Claude Opus 4.5 --- src/main/index.ts | 12 +++++-- src/renderer/src/hooks/useAppSubscriptions.ts | 35 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 46689c6c9..28edaee98 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -8,7 +8,7 @@ import log from './logger' log.info('Multica starting...') -import { app, shell, BrowserWindow, ipcMain } from 'electron' +import { app, shell, BrowserWindow, ipcMain, powerMonitor } from 'electron' import { join } from 'path' import { electronApp, optimizer, is } from '@electron-toolkit/utils' import icon from '../../resources/icon.png?asset' @@ -42,7 +42,8 @@ function createWindow(): BrowserWindow { preload: join(__dirname, '../preload/index.js'), contextIsolation: true, nodeIntegration: false, - sandbox: true + sandbox: true, + backgroundThrottling: false } }) @@ -150,6 +151,13 @@ app.whenReady().then(async () => { } }) + // Notify renderer when system resumes from sleep (reuse APP_FOCUS to trigger DB sync) + powerMonitor.on('resume', () => { + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send(IPC_CHANNELS.APP_FOCUS) + } + }) + // Initialize auto-updater // Set FORCE_DEV_UPDATE=true to test updates in dev mode const forceDevUpdate = process.env.FORCE_DEV_UPDATE === 'true' diff --git a/src/renderer/src/hooks/useAppSubscriptions.ts b/src/renderer/src/hooks/useAppSubscriptions.ts index fef3e70db..5fcb15d36 100644 --- a/src/renderer/src/hooks/useAppSubscriptions.ts +++ b/src/renderer/src/hooks/useAppSubscriptions.ts @@ -14,6 +14,7 @@ import { useFileChangeStore } from '../stores/fileChangeStore' import { useCommandStore } from '../stores/commandStore' import { useAgentStore } from '../stores/agentStore' import { toast } from 'sonner' +import { mergeSessionUpdates } from '../utils/sessionUpdates' function isGitHeadPath(filePath: string): boolean { const normalizedPath = filePath.replace(/\\/g, '/') @@ -59,6 +60,8 @@ export function useAppSubscriptions(callbacks: SubscriptionCallbacks): void { // Debounce timer for git branch refresh const gitBranchRefreshTimerRef = useRef | null>(null) + const focusSyncInFlightRef = useRef(false) + const lastFocusSyncAtRef = useRef(0) // Get current session ID for stable reference in effects const currentSessionId = currentSession?.id @@ -232,6 +235,38 @@ export function useAppSubscriptions(callbacks: SubscriptionCallbacks): void { if (currentSession) { await Promise.all([agentRefresh, callbacks.validateCurrentSessionDirectory()]) + + // Re-sync session updates from DB to capture any IPC messages that were + // lost during system sleep/wake or Chromium background throttling + try { + const now = Date.now() + if (focusSyncInFlightRef.current || now - lastFocusSyncAtRef.current < 500) { + if (import.meta.env.DEV) { + console.log( + '[useAppSubscriptions] Skip focus sync (deduped)', + 'inFlight:', + focusSyncInFlightRef.current, + 'ageMs:', + now - lastFocusSyncAtRef.current + ) + } + return + } + focusSyncInFlightRef.current = true + lastFocusSyncAtRef.current = now + const sessionId = currentSession.id + if (import.meta.env.DEV) { + console.log('[useAppSubscriptions] Focus sync start', sessionId) + } + const freshData = await window.electronAPI.getSession(sessionId) + if (freshData?.updates && currentSessionIdRef.current === sessionId) { + setSessionUpdates((prev) => mergeSessionUpdates(prev, freshData.updates)) + } + } catch (err) { + console.error('[useAppSubscriptions] Failed to refresh session updates on focus:', err) + } finally { + focusSyncInFlightRef.current = false + } } else { await agentRefresh }