Skip to content

Commit a3557cf

Browse files
liangweifengclaude
andcommitted
fix: overlay typewriter RAF idle loop + auto-updater safety
**OverlayPage typewriter infinite RAF loop (MEDIUM)**: - When typewriter caught up to target text (behind <= 0), it still called requestAnimationFrame(tick) every frame, spinning the CPU forever doing nothing. Fixed: return without re-scheduling. Added `source` to useEffect deps so RAF restarts when new text arrives. **OverlayPage clipboard empty catch**: - Clipboard write failure was silently swallowed. Added console.error for debuggability. **Auto-updater safety**: - Added sendToMain() helper with isDestroyed() check — prevents crash if mainWindow is destroyed while updater events fire. - Initial checkForUpdates catch: now logs error instead of swallowing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1edafbb commit a3557cf

2 files changed

Lines changed: 15 additions & 9 deletions

File tree

electron/auto-updater.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { autoUpdater } from 'electron-updater';
22
import { state } from './app-state';
33

4+
/** Safely send to mainWindow — avoids crash if window is destroyed */
5+
function sendToMain(channel: string, ...args: unknown[]) {
6+
const wc = state.mainWindow?.webContents;
7+
if (wc && !wc.isDestroyed()) wc.send(channel, ...args);
8+
}
9+
410
export function setupAutoUpdater() {
511
autoUpdater.autoDownload = false;
612
autoUpdater.autoInstallOnAppQuit = true;
@@ -14,31 +20,31 @@ export function setupAutoUpdater() {
1420
} else if (Array.isArray(info.releaseNotes)) {
1521
notes = info.releaseNotes.map((n: any) => n.note || n).join('\n');
1622
}
17-
state.mainWindow?.webContents.send('updater:update-available', { version: info.version, releaseNotes: notes });
23+
sendToMain('updater:update-available', { version: info.version, releaseNotes: notes });
1824
});
1925

2026
autoUpdater.on('update-not-available', () => {
2127
console.log('[Updater] no update available');
22-
state.mainWindow?.webContents.send('updater:update-not-available');
28+
sendToMain('updater:update-not-available');
2329
});
2430

2531
autoUpdater.on('download-progress', (progress) => {
26-
state.mainWindow?.webContents.send('updater:download-progress', {
32+
sendToMain('updater:download-progress', {
2733
percent: Math.round(progress.percent),
2834
});
2935
});
3036

3137
autoUpdater.on('update-downloaded', () => {
3238
console.log('[Updater] update downloaded');
33-
state.mainWindow?.webContents.send('updater:update-downloaded');
39+
sendToMain('updater:update-downloaded');
3440
});
3541

3642
autoUpdater.on('error', (err) => {
3743
console.error('[Updater] error:', err.message);
38-
state.mainWindow?.webContents.send('updater:error', err.message);
44+
sendToMain('updater:error', err.message);
3945
});
4046

4147
setTimeout(() => {
42-
autoUpdater.checkForUpdates().catch(() => {});
48+
autoUpdater.checkForUpdates().catch((e) => console.error('[Updater] initial check failed:', e.message));
4349
}, 3000);
4450
}

src/pages/OverlayPage.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function useTypewriter(source: string, active: boolean) {
6969
const tick = (now: number) => {
7070
const s = stateRef.current;
7171
const behind = s.target.length - s.pos;
72-
if (behind <= 0) { raf = requestAnimationFrame(tick); return; }
72+
if (behind <= 0) return; // caught up — RAF restarts via useEffect when source changes
7373
// Always 1 char at a time; shorten the interval when falling behind
7474
const interval = behind > 20 ? 10 : behind > 10 ? 18 : behind > 4 ? 25 : 35;
7575
if (now - s.lastTime >= interval) {
@@ -81,7 +81,7 @@ function useTypewriter(source: string, active: boolean) {
8181
};
8282
raf = requestAnimationFrame(tick);
8383
return () => cancelAnimationFrame(raf);
84-
}, [active]);
84+
}, [active, source]);
8585

8686
return display;
8787
}
@@ -174,7 +174,7 @@ export function OverlayPage() {
174174
setCopied(false);
175175
window.electronAPI?.hideOverlay();
176176
}, 1200);
177-
} catch {}
177+
} catch (e) { console.error('[Overlay] clipboard write failed:', e); }
178178
}, [rec.processedText]);
179179

180180
const handleDismiss = useCallback(() => window.electronAPI?.hideOverlay(), []);

0 commit comments

Comments
 (0)