Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -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.
27 changes: 20 additions & 7 deletions backend/src/store/localStore.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -114,17 +114,30 @@ export class LocalStore {
}

async listProjects(): Promise<ProjectManifest[]> {
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<ProjectManifest | null>[] = [];
for (const entry of entries) {
if (entry.isDirectory()) {
const manifestPath = join(this.projectsDir, entry.name, "manifest.json");
promises.push(readJsonFile<ProjectManifest>(manifestPath));
}
const manifest = await readJsonFile<ProjectManifest>(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;
}
Expand Down