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 <noreply@anthropic.com>

* fix: dedupe focus resync logs

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
LinYushen
2026-02-04 14:51:38 +08:00
committed by GitHub
parent bfad56fbf6
commit 3b0ef79cdf
2 changed files with 45 additions and 2 deletions

View File

@@ -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'

View File

@@ -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<ReturnType<typeof setTimeout> | 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
}