Skip to content

Commit 504ef2c

Browse files
Danny Bannisterclaude
andcommitted
IPC debouncing, resource cleanup, and native module rebuild scripts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0c70456 commit 504ef2c

4 files changed

Lines changed: 75 additions & 15 deletions

File tree

main/ipc-handlers.ts

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -503,23 +503,49 @@ export function setupIpcHandlers(): void {
503503
fileWatchers.set(resolvedPath, abortController);
504504

505505
// Use fs.watch if available, otherwise fallback to polling
506+
const IGNORED_DIRS = new Set([
507+
'node_modules', '.git', 'dist', 'build', '.next', '.cache',
508+
'.turbo', '.nuxt', '.output', '__pycache__', '.venv', 'venv',
509+
'.expo', '.parcel-cache', 'coverage', '.svelte-kit',
510+
]);
511+
506512
const { watch } = await import('node:fs');
513+
514+
// Debounce file change notifications to avoid IPC flooding
515+
let pendingChanges = new Map<string, string>();
516+
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
517+
518+
const flushChanges = () => {
519+
const changes = Array.from(pendingChanges.entries());
520+
pendingChanges = new Map();
521+
debounceTimer = null;
522+
BrowserWindow.getAllWindows().forEach(window => {
523+
for (const [fullPath, type] of changes) {
524+
window.webContents.send('file:change', { type, path: fullPath });
525+
}
526+
});
527+
};
528+
507529
const watcher = watch(resolvedPath, { recursive: true }, (eventType, filename) => {
508530
if (!filename) return;
509-
531+
532+
// Skip ignored directories
533+
const parts = filename.split(path.sep);
534+
if (parts.some(p => IGNORED_DIRS.has(p))) return;
535+
510536
const fullPath = path.join(resolvedPath, filename);
511-
512-
// Send to all windows
513-
BrowserWindow.getAllWindows().forEach(window => {
514-
window.webContents.send('file:change', {
515-
type: eventType === 'rename' ? 'unlink' : 'change',
516-
path: fullPath,
517-
});
518-
});
537+
const type = eventType === 'rename' ? 'unlink' : 'change';
538+
539+
pendingChanges.set(fullPath, type);
540+
if (!debounceTimer) {
541+
debounceTimer = setTimeout(flushChanges, 300);
542+
}
519543
});
520544

521545
// Store watcher reference
522546
abortController.signal.addEventListener('abort', () => {
547+
if (debounceTimer) clearTimeout(debounceTimer);
548+
pendingChanges.clear();
523549
watcher.close();
524550
});
525551

@@ -1253,12 +1279,22 @@ export function setupIpcHandlers(): void {
12531279

12541280
// --- Git IPC Handlers ---
12551281
console.log('[IPC:git] Registering git handlers...');
1282+
const GIT_MANAGER_CACHE_MAX = 10;
12561283
const gitManagerCache = new Map<string, GitManager>();
12571284
function getGitManager(cwd: string): GitManager {
12581285
let mgr = gitManagerCache.get(cwd);
1259-
if (!mgr) {
1260-
mgr = new GitManager(cwd);
1286+
if (mgr) {
1287+
// Move to end (most recently used)
1288+
gitManagerCache.delete(cwd);
12611289
gitManagerCache.set(cwd, mgr);
1290+
return mgr;
1291+
}
1292+
mgr = new GitManager(cwd);
1293+
gitManagerCache.set(cwd, mgr);
1294+
// Evict oldest entry if cache exceeds limit
1295+
if (gitManagerCache.size > GIT_MANAGER_CACHE_MAX) {
1296+
const oldest = gitManagerCache.keys().next().value;
1297+
if (oldest) gitManagerCache.delete(oldest);
12621298
}
12631299
return mgr;
12641300
}

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
},
1313
"scripts": {
1414
"build": "tsup",
15-
"dev": "tsx src/index.ts",
15+
"dev": "npm run rebuild:node && tsx src/index.ts",
16+
"rebuild:node": "npm rebuild better-sqlite3",
17+
"rebuild:electron": "npx electron-rebuild -m . -o better-sqlite3",
1618
"test": "vitest",
1719
"lint": "eslint src/",
1820
"typecheck": "tsc --noEmit",

renderer/stores/appStore.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,11 @@ export const useAppStore = create<AppState>((set, get) => ({
937937
}
938938
},
939939

940-
setIndexingPollInterval: (interval) => set({ indexingPollInterval: interval }),
940+
setIndexingPollInterval: (interval) => {
941+
const existing = get().indexingPollInterval;
942+
if (existing) clearInterval(existing);
943+
set({ indexingPollInterval: interval });
944+
},
941945

942946
// Multi-Conversation Actions
943947
createConversation: (options) => {
@@ -1823,6 +1827,12 @@ export const useAppStore = create<AppState>((set, get) => ({
18231827
projectPath: state.projectPath || dirPath,
18241828
}));
18251829

1830+
// Unwatch previous directory before watching new one
1831+
const prevPath = get().projectPath;
1832+
if (prevPath && prevPath !== dirPath) {
1833+
await window.electronAPI.file.unwatch(prevPath).catch(() => {});
1834+
}
1835+
18261836
// Start watching the directory
18271837
await window.electronAPI.file.watch(dirPath);
18281838

scripts/electron-dev.mjs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { spawn } from 'node:child_process';
1+
import { spawn, execSync } from 'node:child_process';
22
import { watchFile, unwatchFile } from 'node:fs';
33
import { access } from 'node:fs/promises';
44
import path from 'node:path';
@@ -85,7 +85,7 @@ async function restartElectron() {
8585
await waitForFile(electronEntry);
8686
await waitForRenderer(viteUrl);
8787
await stopElectron();
88-
electronProcess = spawnProcess(electronBinary, ['.'], 'electron', {
88+
electronProcess = spawnProcess(electronBinary, ['--max-old-space-size=8192', '.'], 'electron', {
8989
NODE_ENV: 'development',
9090
ELECTRON_RUN_AS_NODE: '',
9191
});
@@ -94,6 +94,18 @@ async function restartElectron() {
9494
}
9595
}
9696

97+
// Rebuild native modules for Electron before launching
98+
console.log('[electron-dev] Rebuilding native modules for Electron...');
99+
try {
100+
execSync('npx electron-rebuild -m . -o better-sqlite3', {
101+
cwd: projectRoot,
102+
stdio: 'inherit',
103+
});
104+
console.log('[electron-dev] Native modules rebuilt for Electron.');
105+
} catch (e) {
106+
console.error('[electron-dev] Warning: electron-rebuild failed:', e.message);
107+
}
108+
97109
const viteProcess = spawnProcess('npm', ['run', 'dev:vite'], 'vite', {
98110
NODE_ENV: 'development',
99111
});

0 commit comments

Comments
 (0)