Skip to content

feat: improve index caching#2827

Merged
Kitenite merged 12 commits intomainfrom
feat/cache-index
Sep 11, 2025
Merged

feat: improve index caching#2827
Kitenite merged 12 commits intomainfrom
feat/cache-index

Conversation

@Kitenite
Copy link
Copy Markdown
Contributor

@Kitenite Kitenite commented Sep 11, 2025

Description

Related Issues

Type of Change

  • Bug fix
  • New feature
  • Documentation update
  • Release
  • Refactor
  • Other (please describe):

Testing

Screenshots (if applicable)

Additional Notes


Important

Improve index caching by scoping it per project/branch and adding cache clearing, with updates to initialization and tests.

  • Behavior:
    • Caching is now scoped per project and branch in file-cache.ts and unified-cache.ts.
    • Added ability to clear cache for specific project/branch in file-cache.ts.
  • Initialization:
    • Updated EditorEngine in engine.ts to require project and branch context for file synchronization and caching.
  • Tests:
    • Updated file-sync.test.ts and index.test.ts to test new caching behavior and ensure correctness.
  • Misc:
    • Removed unused useEffect in page-selector.tsx and index.tsx to improve performance.

This description was created by Ellipsis for 888436d. You can customize this summary. It will automatically update as commits are pushed.


Summary by CodeRabbit

  • New Features

    • Caching is now scoped per project and branch and persisted, preventing cross-project conflicts and preserving cached files/directories.
    • File processing is more cache-first and runs in smaller parallel batches to reduce UI blocking and improve responsiveness.
  • Chores

    • Page scanning no longer runs automatically on mount; page scans are now opt-in/reactive and only run when the Pages tab is active.
    • Font loading now runs only when the Brand tab is active.

@vercel
Copy link
Copy Markdown

vercel bot commented Sep 11, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
web Ready Ready Preview Comment Sep 11, 2025 11:35pm
1 Skipped Deployment
Project Deployment Preview Comments Updated (UTC)
docs Skipped Skipped Sep 11, 2025 11:35pm

@supabase
Copy link
Copy Markdown

supabase bot commented Sep 11, 2025

This pull request has been ignored for the connected project wowaemfasoptxrdjhilu because there are no changes detected in apps/backend/supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Sep 11, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Caution

Review failed

The pull request is closed.

Walkthrough

Project- and branch-scoped caching and initialization were added across the editor: several managers (FileCacheManager, FileSyncManager, TemplateNodeManager) now take projectId/branchId to produce per-branch cache names; pages and font managers gained opt-in/init/reactive behavior; indexing/file-processing moved to cache-first and parallel batch handling; minor test and import fixes applied.

Changes

Cohort / File(s) Summary
Per-branch file/directory cache
apps/web/client/src/components/store/editor/cache/file-cache.ts
Constructor now requires projectId, branchId; cache names are ${projectId}-${branchId}-sandbox-files and ${projectId}-${branchId}-sandbox-directories; directory cache made persistent; otherwise config unchanged.
Unified cache minor formatting
apps/web/client/src/components/store/editor/cache/unified-cache.ts
Import order and whitespace/formating changes only; no API/behavior changes.
File sync — cache-aware and contexted
apps/web/client/src/components/store/editor/sandbox/file-sync.ts, apps/web/client/test/sandbox/file-sync.test.ts
FileSyncManager constructor now takes (projectId, branchId) and initializes FileCacheManager with them; tests updated to pass IDs. FileSync processing switched to cache-first for non-images (see sandbox changes).
Sandbox manager wiring & indexing refactor
apps/web/client/src/components/store/editor/sandbox/index.ts
SandboxManager constructs FileSyncManager(projectId, branchId); removed timer-based logging; processFilesInBatches now uses smaller parallel batches (default 10), Promise.all for batch concurrency, yields between batches, and uses cache-first file processing for non-image files.
Template nodes per-project cache
apps/web/client/src/components/store/editor/template-nodes/index.ts, apps/web/client/test/template-nodes/index.test.ts
TemplateNodeManager gains projectId constructor parameter and uses a per-project cache name template-nodes-${projectId}; tests updated to pass projectId.
Editor engine: templateNodes init moved to constructor
apps/web/client/src/components/store/editor/engine.ts
templateNodes declared without inline initializer and assigned in constructor as new TemplateNodeManager(this, projectId); added type-only Branch import.
Pages manager: opt-in reactive init
apps/web/client/src/components/store/editor/pages/index.ts
Added public init() that registers a MobX reaction on editorEngine.activeSandbox.routerConfig to call scanPages() when left panel tab is PAGES.
UI effects removed (page scan)
apps/web/client/src/app/project/[id]/_components/canvas/frame/top-bar/page-selector.tsx, apps/web/client/src/app/project/[id]/_components/left-panel/page-tab/index.tsx
Removed mount/useEffect that auto-triggered scanPages() (React useEffect import removed); scanning moved to opt-in PagesManager.init().
Font manager guard
apps/web/client/src/components/store/editor/font/index.ts
Added guard in FontManager.init() to return early unless left panel tab is BRAND, avoiding unnecessary font loading when not active.
Codesandbox provider cleanup
packages/code-provider/src/providers/codesandbox/utils/utils.ts
Removed a stray console.log('reading image file', filePath); no behavior changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor UI as UI
  participant Sandbox as SandboxManager
  participant Sync as FileSyncManager
  participant Cache as FileCacheManager
  note right of Sandbox #DDEBF7: Initialization with projectId, branchId
  UI->>Sandbox: new SandboxManager(projectId, branchId)
  Sandbox->>Sync: new FileSyncManager(projectId, branchId)
  Sync->>Cache: new FileCacheManager(projectId, branchId)
  Cache-->>Sync: initialized (per-branch cache names)
  Sync-->>Sandbox: ready
Loading
sequenceDiagram
  autonumber
  participant Processor as BatchProcessor
  participant Cache as FileCacheManager
  participant Remote as Remote FS
  Note over Processor,Cache: For each file (non-image)
  Processor->>Cache: readCache(filePath)
  alt cached and valid JSX
    Cache-->>Processor: cached JSX
    Processor->>Processor: process cached JSX
  else no valid cache
    Processor->>Remote: readOrFetch(filePath) (readRemoteFile fallback)
    alt remote is JSX
      Remote-->>Processor: JSX content
      Processor->>Cache: optionally cache result
      Processor->>Processor: process JSX
    else not JSX
      Remote-->>Processor: non-JSX
      Processor-->>Processor: skip processing
    end
  end
  Note over Processor: Batches run with Promise.all, small yield between batches
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • feat: improve index caching #2827 — Updates same cache and sandbox initialization flow to be project/branch-scoped; likely touches identical constructors/naming.
  • feat: add branching #2763 — Changes to editor sandbox/cache/template-node initialization to be branch-aware; directly related to TemplateNodeManager and FileSync/FileCache updates.

Poem

I tuck my carrots by branch and by name,
Labels of project and branch keep them tame.
I peek in the cache before fetching the hay,
Hop in small batches, promise-all through the day.
A tidy burrow per branch — bytes safe and merry.


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf7e38a and 888436d.

📒 Files selected for processing (12)
  • apps/web/client/src/app/project/[id]/_components/canvas/frame/top-bar/page-selector.tsx (1 hunks)
  • apps/web/client/src/app/project/[id]/_components/left-panel/page-tab/index.tsx (1 hunks)
  • apps/web/client/src/components/store/editor/cache/file-cache.ts (1 hunks)
  • apps/web/client/src/components/store/editor/cache/unified-cache.ts (7 hunks)
  • apps/web/client/src/components/store/editor/engine.ts (2 hunks)
  • apps/web/client/src/components/store/editor/font/index.ts (2 hunks)
  • apps/web/client/src/components/store/editor/pages/index.ts (2 hunks)
  • apps/web/client/src/components/store/editor/sandbox/index.ts (4 hunks)
  • apps/web/client/src/components/store/editor/template-nodes/index.ts (1 hunks)
  • apps/web/client/test/sandbox/file-sync.test.ts (6 hunks)
  • apps/web/client/test/template-nodes/index.test.ts (1 hunks)
  • packages/code-provider/src/providers/codesandbox/utils/utils.ts (0 hunks)
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/cache-index

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@vercel vercel bot temporarily deployed to Preview – docs September 11, 2025 21:15 Inactive
maxItems: 1000,
maxSizeBytes: 5 * 1024 * 1024,
ttlMs: 1000 * 60 * 60,
persistent: false,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In static 'clearPersistentForBranch', the directory cache is configured with 'persistent: false' yet 'clearPersistent()' is called. Verify if this is intentional.

maxItems: 1000,
maxSizeBytes: 5 * 1024 * 1024,
ttlMs: 1000 * 60 * 60,
persistent: false,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Configuration inconsistency bug: The directoryCache is created with persistent: false but then clearPersistent() is called on it. This is inconsistent - if the cache is not persistent, calling clearPersistent() is meaningless and could cause errors. This should be persistent: true to match the fileCache configuration and the intent of clearing persistent storage.

Suggested change
persistent: false,
persistent: true,

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/web/client/src/components/store/editor/cache/file-cache.ts (1)

219-253: Don’t init/clear a non-persistent directory cache in clearPersistentForBranch (dup of prior review).

directoryCache is created with persistent: false but clearPersistent() is invoked. Remove the directory cache from this method and scope docs to “file cache”.

Apply:

-    /**
-     * Static method to clear persistent cache for a specific project/branch
-     * without creating an instance
-     */
+    /**
+     * Clear persistent FILE cache for a specific project/branch without creating an instance.
+     */
     static async clearPersistentForBranch(projectId: string, branchId: string): Promise<void> {
         try {
-            const fileCache = new UnifiedCacheManager({
+            const fileCache = new UnifiedCacheManager({
                 name: `${projectId}-${branchId}-sandbox-files`,
                 maxItems: 500,
                 maxSizeBytes: 50 * 1024 * 1024,
                 ttlMs: 1000 * 60 * 30,
                 persistent: true,
             });
-
-            const directoryCache = new UnifiedCacheManager({
-                name: `${projectId}-${branchId}-sandbox-directories`,
-                maxItems: 1000,
-                maxSizeBytes: 5 * 1024 * 1024,
-                ttlMs: 1000 * 60 * 60,
-                persistent: false,
-            });
 
             await Promise.all([
                 fileCache.init(),
-                directoryCache.init(),
             ]);
 
             await Promise.all([
                 fileCache.clearPersistent(),
-                directoryCache.clearPersistent(),
             ]);
         } catch (error) {
             console.error(`Error clearing persistent cache for project ${projectId}, branch ${branchId}:`, error);
         }
     }

Run to confirm UnifiedCacheManager behavior before changing:

#!/bin/bash
# Locate UnifiedCacheManager and inspect clearPersistent/persistent handling
fd -a 'unified-cache.*\.(ts|tsx|js)' | xargs -I{} sh -c 'echo "==> {}"; rg -n "class\s+UnifiedCacheManager|clearPersistent|persistent" -n {} -C3'
🧹 Nitpick comments (1)
apps/web/client/src/components/store/editor/cache/file-cache.ts (1)

10-26: Sanitize and centralize cache key construction to avoid invalid names and duplication.

IDs may contain spaces/slashes. Build a safe, reusable prefix once and use it for both caches.

Apply:

-    constructor(projectId: string, branchId: string) {
-        this.fileCache = new UnifiedCacheManager({
-            name: `${projectId}-${branchId}-sandbox-files`,
+    constructor(projectId: string, branchId: string) {
+        const prefix = FileCacheManager.keyPrefix(projectId, branchId);
+        this.fileCache = new UnifiedCacheManager({
+            name: `${prefix}-sandbox-files`,
             maxItems: 500,
             maxSizeBytes: 50 * 1024 * 1024, // 50MB
             ttlMs: 1000 * 60 * 30, // 30 minutes
             persistent: true,
         });
 
-        this.directoryCache = new UnifiedCacheManager({
-            name: `${projectId}-${branchId}-sandbox-directories`,
+        this.directoryCache = new UnifiedCacheManager({
+            name: `${prefix}-sandbox-directories`,
             maxItems: 1000,
             maxSizeBytes: 5 * 1024 * 1024, // 5MB
             ttlMs: 1000 * 60 * 60, // 1 hour
             persistent: false, // Directories are lightweight, no need to persist
         });
     }

Add (outside the range, in the class):

private static keyPrefix(projectId: string, branchId: string): string {
  const safe = (s: string) => encodeURIComponent(String(s).trim());
  return `${safe(projectId)}-${safe(branchId)}`;
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28e46d8 and bf7e38a.

📒 Files selected for processing (3)
  • apps/web/client/src/components/store/editor/cache/file-cache.ts (2 hunks)
  • apps/web/client/src/components/store/editor/sandbox/file-sync.ts (1 hunks)
  • apps/web/client/src/components/store/editor/sandbox/index.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
apps/web/client/src/components/store/editor/sandbox/index.ts (1)
apps/web/client/src/components/store/editor/sandbox/file-sync.ts (1)
  • FileSyncManager (6-101)
apps/web/client/src/components/store/editor/sandbox/file-sync.ts (1)
apps/web/client/src/components/store/editor/cache/file-cache.ts (1)
  • FileCacheManager (6-254)
🔇 Additional comments (1)
apps/web/client/src/components/store/editor/sandbox/file-sync.ts (1)

9-11: LGTM: per-project/branch cache is correctly injected.

Constructor change and pass-through look correct.

this.errorManager
);
this.fileSync = new FileSyncManager();
this.fileSync = new FileSyncManager(this.branch.projectId, this.branch.id);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Pass stable identifiers; audit call sites after signature change.

Looks good. Verify all constructors were updated and that branch.projectId/branch.id are immutable over the SandboxManager lifecycle.

To verify:


🏁 Script executed:

#!/bin/bash
# Find remaining old-API usages (zero-arg) of FileSyncManager
rg -nP "new\s+FileSyncManager\s*\(" -C2 | rg -nPv "new\s+FileSyncManager\s*\(\s*[^,]+\s*,\s*[^)]+\)"

# (Optional) consider an options object to prevent arg-order mistakes:
# new FileSyncManager({ projectId: branch.projectId, branchId: branch.id })

Length of output: 1158


Pass stable identifiers; update remaining FileSyncManager call sites.

Mixed constructor usages detected — update call sites or restore backward-compatible constructor.

  • apps/web/client/test/sandbox/file-sync.test.ts:42 — new FileSyncManager() (zero-arg) needs updating.
  • apps/web/client/test/sandbox/sandbox.test.ts:329 — new FileSyncManager({ ... }) uses options-object (keep or standardize).
  • apps/web/client/src/components/store/editor/sandbox/index.ts:57 — new FileSyncManager(this.branch.projectId, this.branch.id) — ensure branch.projectId and branch.id remain stable for the SandboxManager lifecycle (capture into consts if they can change).

Consider switching FileSyncManager to an options object to avoid arg-order mistakes (optional).

🤖 Prompt for AI Agents
In apps/web/client/src/components/store/editor/sandbox/index.ts around line 57,
the FileSyncManager is constructed with positional args new
FileSyncManager(this.branch.projectId, this.branch.id) while other call sites
use zero-arg or options-object forms; capture stable identifiers into consts
(e.g., const projectId = this.branch.projectId; const branchId = this.branch.id)
before creating the manager to ensure values don't change during SandboxManager
lifecycle, and then update all FileSyncManager usages to a single consistent
constructor shape (prefer an options object like new FileSyncManager({
projectId, branchId }) or update tests and call sites to pass two explicit args)
so all call sites (apps/web/client/test/sandbox/file-sync.test.ts:42,
apps/web/client/test/sandbox/sandbox.test.ts:329, and this file) are consistent.

@vercel vercel bot temporarily deployed to Preview – docs September 11, 2025 23:02 Inactive
Comment on lines +29 to +39
init() {
reaction(
() => this.editorEngine.activeSandbox.routerConfig,
() => {
if (this.editorEngine.state.leftPanelTab !== LeftPanelTabValue.PAGES) {
return;
}
this.scanPages();
},
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Async setup race condition bug: The init() method sets up a reaction but is never called, meaning the reaction will never be established. The reaction setup should either be moved to the constructor or init() must be called during PageManager initialization. Without this, the automatic page scanning on router config changes will never occur.

Suggested change
init() {
reaction(
() => this.editorEngine.activeSandbox.routerConfig,
() => {
if (this.editorEngine.state.leftPanelTab !== LeftPanelTabValue.PAGES) {
return;
}
this.scanPages();
},
);
}
constructor() {
makeObservable(this, {
pages: observable,
scanPages: action,
});
reaction(
() => this.editorEngine.activeSandbox.routerConfig,
() => {
if (this.editorEngine.state.leftPanelTab !== LeftPanelTabValue.PAGES) {
return;
}
this.scanPages();
},
);
}

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

@vercel vercel bot temporarily deployed to Preview – docs September 11, 2025 23:06 Inactive
@vercel vercel bot temporarily deployed to Preview – docs September 11, 2025 23:09 Inactive
@vercel vercel bot temporarily deployed to Preview – docs September 11, 2025 23:24 Inactive
Comment on lines +141 to 151
// Check cache first
const cachedFile = this.fileSync.readCache(filePath);
if (cachedFile && cachedFile.content !== null) {
if (this.isJsxFile(filePath)) {
await this.processFileForMapping(remoteFile);
await this.processFileForMapping(cachedFile);
}
} else {
const file = await this.fileSync.readOrFetch(filePath, this.readRemoteFile.bind(this));
if (file && this.isJsxFile(filePath)) {
await this.processFileForMapping(file);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic error in cache validation: The code checks if cachedFile.content !== null but doesn't validate if the cached content is actually valid or up-to-date. This could lead to using stale cached data when the file has been modified. The cache check should include content hash validation or timestamp comparison to ensure data freshness.

Suggested change
// Check cache first
const cachedFile = this.fileSync.readCache(filePath);
if (cachedFile && cachedFile.content !== null) {
if (this.isJsxFile(filePath)) {
await this.processFileForMapping(remoteFile);
await this.processFileForMapping(cachedFile);
}
} else {
const file = await this.fileSync.readOrFetch(filePath, this.readRemoteFile.bind(this));
if (file && this.isJsxFile(filePath)) {
await this.processFileForMapping(file);
}
// Check cache first
const cachedFile = this.fileSync.readCache(filePath);
if (cachedFile && cachedFile.content !== null && this.fileSync.isCacheValid(filePath)) {
if (this.isJsxFile(filePath)) {
await this.processFileForMapping(cachedFile);
}
} else {
const file = await this.fileSync.readOrFetch(filePath, this.readRemoteFile.bind(this));
if (file && this.isJsxFile(filePath)) {
await this.processFileForMapping(file);
}

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant