From 9e7d3b58a145663a93cb374c039aaf9f41df5e5c Mon Sep 17 00:00:00 2001 From: proxy Date: Sat, 21 Feb 2026 15:46:25 -0500 Subject: [PATCH] fix: resolve perf regression in transcript loading and session search Two performance regressions introduced in recent PRs: 1. readAgentConfigs blocked transcript rendering (PR #50) The agent config IPC call was awaited on the critical path of fetchSessionDetail, preventing any transcript data from rendering until the filesystem read completed. On macOS this was especially noticeable due to security checks on first directory access. Fixed by making the call fire-and-forget: the transcript renders immediately and subagent color badges update asynchronously. Also set the project cache key optimistically before the async call to prevent duplicate in-flight requests on rapid navigation. 2. SessionSearcher stat()-called every session file on each search (PR #53) LocalFileSystemProvider.readdir() did not populate the optional mtimeMs field on FsDirent entries. The new SearchTextCache-based SessionSearcher fell back to an individual fsProvider.stat() call per session file when mtimeMs was missing, adding N extra filesystem round-trips on every search in local mode. Fixed by statting all entries concurrently inside readdir(), so mtimeMs is always populated and the stat fallback is never triggered. Co-Authored-By: Claude Sonnet 4.6 --- .../infrastructure/LocalFileSystemProvider.ts | 24 +++++++++++++++---- .../store/slices/sessionDetailSlice.ts | 21 +++++++++------- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/main/services/infrastructure/LocalFileSystemProvider.ts b/src/main/services/infrastructure/LocalFileSystemProvider.ts index 9d7ee499..ceb5a3fb 100644 --- a/src/main/services/infrastructure/LocalFileSystemProvider.ts +++ b/src/main/services/infrastructure/LocalFileSystemProvider.ts @@ -43,11 +43,25 @@ export class LocalFileSystemProvider implements FileSystemProvider { async readdir(dirPath: string): Promise { const entries = await fs.promises.readdir(dirPath, { withFileTypes: true }); - return entries.map((entry) => ({ - name: entry.name, - isFile: () => entry.isFile(), - isDirectory: () => entry.isDirectory(), - })); + // Stat all entries concurrently to populate mtimeMs, used by SessionSearcher's + // mtime-based cache invalidation. Failures are silently ignored (mtimeMs stays undefined). + return Promise.all( + entries.map(async (entry) => { + let mtimeMs: number | undefined; + try { + const stat = await fs.promises.stat(`${dirPath}/${entry.name}`); + mtimeMs = stat.mtimeMs; + } catch { + // ignore + } + return { + name: entry.name, + mtimeMs, + isFile: () => entry.isFile(), + isDirectory: () => entry.isDirectory(), + }; + }) + ); } createReadStream(filePath: string, opts?: ReadStreamOptions): fs.ReadStream { diff --git a/src/renderer/store/slices/sessionDetailSlice.ts b/src/renderer/store/slices/sessionDetailSlice.ts index 7eb2dd8c..9a158f49 100644 --- a/src/renderer/store/slices/sessionDetailSlice.ts +++ b/src/renderer/store/slices/sessionDetailSlice.ts @@ -197,16 +197,19 @@ export const createSessionDetailSlice: StateCreator | null = null; let contextStats: Map | null = null; let phaseInfo: ContextPhaseInfo | null = null; - // Fetch agent configs from .claude/agents/ (only when project changes) + // Fetch agent configs from .claude/agents/ (only when project changes). + // Fire-and-forget: don't block transcript rendering — color badges update async. if (connectionMode !== 'ssh' && projectRoot && projectRoot !== agentConfigsCachedForProject) { - try { - const configs = await api.readAgentConfigs(projectRoot); - if (requestGeneration !== sessionDetailFetchGeneration) return; - agentConfigsCachedForProject = projectRoot; - set({ agentConfigs: configs }); - } catch (err) { - logger.error('Failed to read agent configs:', err); - } + agentConfigsCachedForProject = projectRoot; // Optimistic set to prevent duplicate fetches + api + .readAgentConfigs(projectRoot) + .then((configs) => { + set({ agentConfigs: configs }); + }) + .catch((err) => { + logger.error('Failed to read agent configs:', err); + agentConfigsCachedForProject = ''; // Reset so it retries next time + }); } if (connectionMode !== 'ssh' && conversation?.items) {