diff --git a/.gitignore b/.gitignore index 6ff2ddb..3c46b4c 100644 --- a/.gitignore +++ b/.gitignore @@ -125,3 +125,4 @@ walkthrough.md # Local planning and inspiration folders (keep tracked files as-is, ignore new ones) Plan/ prism_inspo_ui/ +temp_bench_projects/ diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..87b6da4 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2025-03-28 - [Performance Optimization in LocalStore listProjects] +**Learning:** Found an O(N) scaling bottleneck where `localStore.ts` used `listFilesRecursive` to find `manifest.json` across all files in a project workspace instead of directly reading immediate subdirectories. +**Action:** Avoid `listFilesRecursive` for well-known shallow paths. Use `readdir({ withFileTypes: true })` and read target files concurrently with `Promise.all` to significantly improve performance, especially on workspaces with many nested files. diff --git a/backend/src/store/localStore.ts b/backend/src/store/localStore.ts index 6aa075d..5440406 100644 --- a/backend/src/store/localStore.ts +++ b/backend/src/store/localStore.ts @@ -1,4 +1,4 @@ -import { readFile, stat, writeFile } from "node:fs/promises"; +import { readFile, readdir, stat, writeFile } from "node:fs/promises"; import { homedir } from "node:os"; import { dirname, join, relative } from "node:path"; import { URL } from "node:url"; @@ -114,17 +114,30 @@ export class LocalStore { } async listProjects(): Promise { - const entries = await listFilesRecursive(this.projectsDir); - const manifests: ProjectManifest[] = []; - for (const filePath of entries) { - if (!filePath.endsWith("manifest.json")) { - continue; + let entries; + try { + entries = await readdir(this.projectsDir, { withFileTypes: true }); + } catch { + return []; + } + + const promises: Promise[] = []; + for (const entry of entries) { + if (entry.isDirectory()) { + const manifestPath = join(this.projectsDir, entry.name, "manifest.json"); + promises.push(readJsonFile(manifestPath)); } - const manifest = await readJsonFile(filePath); + } + + const results = await Promise.all(promises); + const manifests: ProjectManifest[] = []; + // Prefer for...of over map/filter per performance guidelines + for (const manifest of results) { if (manifest) { manifests.push(manifest); } } + manifests.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)); return manifests; }