diff --git a/main.js b/main.js index 43111d87..0467b9d3 100644 --- a/main.js +++ b/main.js @@ -648,6 +648,8 @@ ipcMain.handle('get-projects', async (_event, showArchived) => { // Cache already populated: pick up folders changed while the app was // closed, or never indexed by an older build, so sessions/worktrees don't // silently go missing. Stat-gated, so it's cheap when nothing has changed. + // Synchronous (readdir/stat sweep) — completes before buildProjectsFromCache + // below; no await needed despite the await in the cold-start branch above. reconcileCacheFromFilesystem(); } diff --git a/session-cache.js b/session-cache.js index ccaef66a..e6f9f4ad 100644 --- a/session-cache.js +++ b/session-cache.js @@ -270,8 +270,19 @@ function refreshFolder(folder, opts = {}) { * app was closed, or that predates the build which first indexed it, is * otherwise never picked up, because the cold-start full scan * (populateCacheViaWorker) only runs when the cache is completely empty. + * + * Throttled: the renderer's loadProjects() fires get-projects twice per sidebar + * paint (showArchived false/true via Promise.all), which would run the sweep + * back-to-back. The second pass is idempotent but wasted work — and skipping it + * keeps a single metaMap snapshot per paint. Changes landing inside the + * throttle window are still picked up by the live watcher. */ +const RECONCILE_THROTTLE_MS = 1000; +let lastReconcileAt = 0; function reconcileCacheFromFilesystem() { + const now = Date.now(); + if (now - lastReconcileAt < RECONCILE_THROTTLE_MS) return; + lastReconcileAt = now; try { const metaMap = getAllFolderMeta(); const folders = fs.readdirSync(PROJECTS_DIR, { withFileTypes: true })