Skip to content

Conversation

@Kitenite
Copy link
Contributor

@Kitenite Kitenite commented Sep 15, 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

Improves file caching and synchronization in the editor, enhancing reliability and performance for image and JSX file handling.

  • Behavior:
    • Improved file update reliability in the editor after copy and rename actions in sandbox/index.ts.
    • Ensured live content reflects latest remote changes to prevent stale views in sandbox/index.ts.
  • Performance:
    • Limited JSX mapping to JSX files only in sandbox/index.ts.
    • Streamlined synchronization to keep editor state in sync with remote files more efficiently in image/index.ts.
  • Stability:
    • Reduced inconsistencies between editor and file system, leading to more predictable behavior during file operations in folder-provider.tsx and image/index.ts.

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


Summary by CodeRabbit

  • New Features

    • Image panel: tooltips for Create Folder, Upload, and a new Refresh Images action; Create Folder is disabled during ongoing operations.
  • Bug Fixes

    • More reliable editor updates after copy/rename and when remote files change to avoid stale content.
  • Performance

    • Reduced unnecessary mapping to only JSX files and guarded image scanning to run only when ready.
  • Stability

    • Improved synchronization between editor and image manager for consistent file and image listings.

@vercel
Copy link

vercel bot commented Sep 15, 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 15, 2025 6:35am
1 Skipped Deployment
Project Deployment Preview Comments Updated (UTC)
docs Skipped Skipped Sep 15, 2025 6:35am

@supabase
Copy link

supabase bot commented Sep 15, 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

coderabbitai bot commented Sep 15, 2025

Walkthrough

Unified sandbox file-change handling to always read remote content and update cache, limited JSX mapping to JSX text files; cache sync after copy. Added tooltipped image actions in the image folder UI. Image provider and image scanning now source and filter image paths from the image manager with guarded scanning and stricter IMAGE_FOLDER prefix checks.

Changes

Cohort / File(s) Summary of Changes
Sandbox editor event handling and cache sync
apps/web/client/src/components/store/editor/sandbox/index.ts
- handleFileChangedEvent now always reads the remote file and updates the cache; mapping is triggered only for text files that are JSX and whose remote content differs from cached content.
- copy now reads the destination remote file and updates cache after copy.
- rename left unchanged functionally; comment notes watcher handles cache updates.
Image tab — UI actions
apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/folder/index.tsx
- Replaced plain Create Folder with a tooltip-wrapped, disable-aware button; added tooltip-wrapped Refresh (reload) button calling editorEngine.image.scanImages(); wrapped Upload in a tooltip and adjusted Plus icon sizing (w-4 h-4). Buttons respect isAnyOperationLoading.
Image folder provider — image listing source
apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/providers/folder-provider.tsx
- getImagesInFolder now reads from editorEngine.image.imagePaths instead of editorEngine.activeSandbox.files and removes isImageFile check, using a direct-child path filter based on relative path separators; updated useCallback dependencies and added clarifying comment.
Image manager — scanning & indexing reaction
apps/web/client/src/components/store/editor/image/index.ts
- Imports adjusted to use LeftPanelTabValue and mark some items as types.
- init() reaction refactored to a memoized status (isIndexing / isIndexed), and scanning is triggered only when left panel tab is IMAGES and indexing finished.
- scanImages() adds a re-entrancy guard (_isScanning), reads from editorEngine.activeSandbox.files (not recursive listing), requires files to exist, and filters image paths to start with DefaultSettings.IMAGE_FOLDER in addition to being image files. Empty scans clear _imagePaths.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant FW as File Watcher
  participant SB as Sandbox
  participant RFS as Remote FS
  participant C as Cache
  participant MAP as Mapper (JSX)

  rect rgba(230,245,255,0.5)
    FW->>SB: fileChanged(path)
    SB->>RFS: read(path)
    alt read OK
      RFS-->>SB: remoteFile(content, type)
      SB->>C: update(path, content)
      opt if type == text AND isJsxFile(path) AND content != cache
        SB->>MAP: trigger mapping for path
      end
    else read failed
      SB-->>FW: log error and return
    end
  end
Loading
sequenceDiagram
  autonumber
  participant U as User
  participant SB as Sandbox
  participant RFS as Remote FS
  participant C as Cache

  rect rgba(240,255,240,0.5)
    U->>SB: copy(src, dest)
    SB->>RFS: copy(src -> dest)
    alt copy OK
      SB->>RFS: read(dest)
      RFS-->>SB: remoteFile(content)
      SB->>C: update(dest, content)
    else copy/read failed
      SB-->>U: error
    end
  end
Loading
sequenceDiagram
  autonumber
  participant UI as Left Panel (UI)
  participant IMG as ImageStore
  participant SB as Sandbox

  rect rgba(255,250,230,0.5)
    UI->>IMG: tabChanged -> IMAGES
    IMG->>IMG: check isIndexing / isIndexed
    alt indexed and not indexing
      IMG->>SB: read activeSandbox.files
      SB-->>IMG: files[]
      IMG->>IMG: filter paths starting with IMAGE_FOLDER & isImageFile
      IMG-->>UI: update imagePaths
    else not ready
      IMG-->>UI: do nothing
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I hopped through watchers, sniffed each byte,
Synced copies snugly in the moonlit night.
JSX maps only when the text has changed,
Images refreshed, and paths rearranged.
A rabbit’s small joy—cache tidy and bright. 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR includes the repository's template but most required sections are left with placeholder comments or empty (the "Description" header contains only a placeholder despite an autogenerated Ellipsis summary appearing later, "Related Issues" is empty, the "Type of Change" checkboxes are unset, and "Testing" is blank), so the description does not fully satisfy the project's template requirements. Because key template fields (explicit description, test instructions, and change type) are incomplete, the description is insufficient as-is for reviewers to verify scope and testing. Please populate the template's main sections: move or copy a concise human-written summary into the "Description" field (the Ellipsis text can be used but should be placed under "Description"), add any related issue references or state "none", check the appropriate "Type of Change" box(es) (e.g., Bug fix, Refactor), and provide a "Testing" section that lists manual steps or automated tests performed to verify cache, copy, and rename behavior; optionally include screenshots and any additional notes for reviewers.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "fix: stricter file caching" is concise and directly reflects the primary intent of the changeset — tightening and unifying file-caching and synchronization behavior across the sandbox and image handling code — making it clear to reviewers and for commit history. It uses the conventional "fix:" prefix and does not include extraneous details, so it summarizes the main change appropriately.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/cache

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.

Copy link

@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

🧹 Nitpick comments (1)
apps/web/client/src/components/store/editor/sandbox/index.ts (1)

731-731: Rename relies solely on watcher; add a fallback for stale-cache risk

If a provider fails to emit a rename event or the watcher is inactive, the cache won’t update. Add a guarded fallback.

-            // Note: Cache update handled by file watcher rename event
+            // Note: Cache update handled by file watcher rename event
+            // Fallback: if no watcher is active, eagerly update cache to avoid stale state
+            if (!this.fileWatcher) {
+                await this.fileSync.rename(normalizedOldPath, normalizedNewPath);
+                this.fileEventBus.publish({
+                    type: 'rename',
+                    paths: [normalizedOldPath, normalizedNewPath],
+                    timestamp: Date.now(),
+                });
+            }

Please confirm all deployed providers reliably emit rename events for renameFile operations. If not, the fallback above should be kept.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52cef0d and 8273d32.

📒 Files selected for processing (1)
  • apps/web/client/src/components/store/editor/sandbox/index.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Files:

  • apps/web/client/src/components/store/editor/sandbox/index.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • apps/web/client/src/components/store/editor/sandbox/index.ts
🔇 Additional comments (1)
apps/web/client/src/components/store/editor/sandbox/index.ts (1)

552-565: Avoid false-negative change detection when cache mutates in place

updateCache → FileCacheManager.setFile → UnifiedCacheManager.set; if that set mutates the cached object by reference, comparing after update can hide real changes. Capture previous text content before updating the cache and compare against it.

Apply this diff:

-        // Always read the remote file and update the cache, regardless of file type
-        const remoteFile = await this.readRemoteFile(normalizedPath);
-        if (!remoteFile) {
-            console.error(`File content for ${normalizedPath} not found in remote`);
-            return;
-        }
-
-        // Always update the cache with the fresh remote file content
-        this.fileSync.updateCache(remoteFile);
-
-        // For text files, also process for mapping if content has changed
-        if (remoteFile.type === 'text' && this.isJsxFile(normalizedPath)) {
-            if (remoteFile.content !== cachedFile?.content) {
-                await this.processFileForMapping(remoteFile);
-            }
-        }
+        // Always read the remote file; capture previous text content before updating cache
+        const prevTextContent = cachedFile?.type === 'text' ? cachedFile.content : undefined;
+        const remoteFile = await this.readRemoteFile(normalizedPath);
+        if (!remoteFile) {
+            console.error(`File content for ${normalizedPath} not found in remote`);
+            return;
+        }
+        const shouldProcess =
+            remoteFile.type === 'text' &&
+            this.isJsxFile(normalizedPath) &&
+            remoteFile.content !== prevTextContent;
+        // Update the cache with the fresh remote file content
+        this.fileSync.updateCache(remoteFile);
+        if (shouldProcess) {
+            await this.processFileForMapping(remoteFile);
+        }

Confirm whether UnifiedCacheManager.set mutates existing entries in-place; if it does, merge this change.

Comment on lines +659 to +664
// Read and cache the copied file
const copiedFile = await this.readRemoteFile(normalizedTargetPath);
if (copiedFile) {
this.fileSync.updateCache(copiedFile);
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Copying directories leaves cache stale; handle directory targets

After copyFiles, attempting to read a directory via readRemoteFile returns null and the directory cache isn’t updated. This desynchronizes the cache on directory copies (common when recursive is true).

Apply this diff:

-            // Read and cache the copied file
-            const copiedFile = await this.readRemoteFile(normalizedTargetPath);
-            if (copiedFile) {
-                this.fileSync.updateCache(copiedFile);
-            }
+            // Sync cache for the copy target (file or directory)
+            const stat = await this.session.provider.statFile({
+                args: { path: normalizedTargetPath },
+            });
+            if (stat?.type === 'directory') {
+                this.fileSync.updateDirectoryCache(normalizedTargetPath);
+                // Optional: list + seed child entries if provider doesn't emit add events for children.
+                // (Rely on watcher by default.)
+            } else {
+                const copiedFile = await this.readRemoteFile(normalizedTargetPath);
+                if (copiedFile) {
+                    this.fileSync.updateCache(copiedFile);
+                }
+            }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Read and cache the copied file
const copiedFile = await this.readRemoteFile(normalizedTargetPath);
if (copiedFile) {
this.fileSync.updateCache(copiedFile);
}
// Sync cache for the copy target (file or directory)
const stat = await this.session.provider.statFile({
args: { path: normalizedTargetPath },
});
if (stat?.type === 'directory') {
this.fileSync.updateDirectoryCache(normalizedTargetPath);
// Optional: list + seed child entries if provider doesn't emit add events for children.
// (Rely on watcher by default.)
} else {
const copiedFile = await this.readRemoteFile(normalizedTargetPath);
if (copiedFile) {
this.fileSync.updateCache(copiedFile);
}
}
🤖 Prompt for AI Agents
In apps/web/client/src/components/store/editor/sandbox/index.ts around lines 659
to 664, the code calls readRemoteFile after copyFiles and skips cache updates
when readRemoteFile returns null, which leaves the cache stale for directory
targets; change the logic so that if readRemoteFile returns null you detect
whether the normalizedTargetPath is a directory (e.g., via readRemoteDirectory
or a metadata/stat call), and when it is update the fileSync cache with a
directory entry (and for recursive copies, fetch and update the directory
contents recursively or refresh the parent directory listing) so the cache
reflects the newly copied directory structure.

@vercel vercel bot temporarily deployed to Preview – docs September 15, 2025 06:32 Inactive
Comment on lines +553 to +557
const remoteFile = await this.readRemoteFile(normalizedPath);
if (!remoteFile) {
console.error(`File content for ${normalizedPath} not found in remote`);
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Logic error: The code returns early if remoteFile is falsy, but the comment on line 552 states it should 'always update cache regardless of file type'. This creates inconsistent behavior where some file change events are silently ignored. The function should either handle the null case appropriately (perhaps by removing the file from cache) or the early return should be reconsidered to match the stated behavior.

Suggested change
const remoteFile = await this.readRemoteFile(normalizedPath);
if (!remoteFile) {
console.error(`File content for ${normalizedPath} not found in remote`);
return;
}
const remoteFile = await this.readRemoteFile(normalizedPath);
if (!remoteFile) {
console.error(`File content for ${normalizedPath} not found in remote`);
// Remove file from cache instead of returning early
this.removeFromCache(normalizedPath);
return;
}

Spotted by Diamond

Fix in Graphite


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

async (isIndexingFiles) => {
if (!isIndexingFiles) {
await this.scanImages();
() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

The reaction’s tracking function returns only isIndexing and isIndexed. If you intend to trigger a scan when the images tab is activated, consider including leftPanelTab in the tracked object.

@Kitenite Kitenite merged commit 0fb06c7 into main Sep 15, 2025
5 of 7 checks passed
@Kitenite Kitenite deleted the fix/cache branch September 15, 2025 06:34
Copy link

@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: 5

🧹 Nitpick comments (1)
apps/web/client/src/components/store/editor/image/index.ts (1)

200-204: Log level/message tweak for unavailable file list.

When files is undefined, it’s a transient state; prefer warn and clearer wording.

-            if (!files) {
-                console.error('No files found in image folder');
-                return;
-            }
+            if (!files) {
+                console.warn('Sandbox files list unavailable; skipping image scan');
+                return;
+            }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8273d32 and c60429f.

📒 Files selected for processing (3)
  • apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/folder/index.tsx (3 hunks)
  • apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/providers/folder-provider.tsx (1 hunks)
  • apps/web/client/src/components/store/editor/image/index.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
apps/web/client/src/app/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/app/**/*.tsx: Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries
Do not use process.env in client code; import env from @/env instead

Files:

  • apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/providers/folder-provider.tsx
  • apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/folder/index.tsx
apps/web/client/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.{ts,tsx}: Use path aliases @/* and ~/* for imports that map to apps/web/client/src/*
Avoid hardcoded user-facing text; use next-intl messages/hooks instead

Files:

  • apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/providers/folder-provider.tsx
  • apps/web/client/src/components/store/editor/image/index.ts
  • apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/folder/index.tsx
apps/web/client/src/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

apps/web/client/src/**/*.tsx: Create MobX store instances with useState(() => new Store()) for stable references across renders
Keep the active MobX store in a useRef and perform async cleanup with setTimeout(() => storeRef.current?.clear(), 0) to avoid route-change races
Avoid useMemo for creating MobX store instances
Avoid putting the MobX store instance in effect dependency arrays if it causes loops; split concerns by domain

Files:

  • apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/providers/folder-provider.tsx
  • apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/folder/index.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Do not use the any type unless necessary

Files:

  • apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/providers/folder-provider.tsx
  • apps/web/client/src/components/store/editor/image/index.ts
  • apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/folder/index.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-14T01:44:21.199Z
Learnt from: CR
PR: onlook-dev/onlook#0
File: AGENTS.md:0-0
Timestamp: 2025-09-14T01:44:21.199Z
Learning: Applies to apps/web/client/src/app/**/*.tsx : Default to Server Components; add 'use client' when using events, state/effects, browser APIs, or client‑only libraries

Applied to files:

  • apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/folder/index.tsx
🧬 Code graph analysis (2)
apps/web/client/src/components/store/editor/image/index.ts (3)
apps/web/client/src/components/store/editor/sandbox/index.ts (1)
  • files (319-321)
packages/constants/src/editor.ts (1)
  • DefaultSettings (38-69)
packages/utility/src/file.ts (1)
  • isImageFile (101-104)
apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/folder/index.tsx (2)
packages/ui/src/components/tooltip.tsx (4)
  • Tooltip (72-72)
  • TooltipTrigger (72-72)
  • TooltipPortal (72-72)
  • TooltipContent (72-72)
packages/ui/src/components/button.tsx (1)
  • Button (57-57)
🔇 Additional comments (4)
apps/web/client/src/components/store/editor/image/index.ts (2)

2-2: Good use of type-only imports.

Keeps runtime clean and improves perf/emit.


195-199: Re-entrancy guard is solid.

The try/finally ensures _isScanning resets on all paths.

apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/folder/index.tsx (2)

1-2: Correctly marked as a Client Component.

Required due to state, effects, and event handlers.


255-255: Icon sizing change looks consistent.

No issues spotted.

Comment on lines +211 to +228
<Tooltip>
<TooltipTrigger asChild>
<Button
variant={'default'}
size={'icon'}
className="p-2 w-fit h-fit text-foreground-primary border-border-primary hover:border-border-onlook bg-background-secondary hover:bg-background-onlook border"
onClick={() => handleCreateFolder(currentFolder)}
disabled={isAnyOperationLoading}
>
<Icons.DirectoryPlus className="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
<p>Create a folder</p>
</TooltipContent>
</TooltipPortal>
</Tooltip>
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Localize tooltip text and add an accessible label to the icon-only “Create folder” button.

Complies with next-intl/i18n guideline and improves a11y.

Apply this diff:

-                <Tooltip>
+                <Tooltip>
                     <TooltipTrigger asChild>
                         <Button
                             variant={'default'}
                             size={'icon'}
                             className="p-2 w-fit h-fit text-foreground-primary border-border-primary hover:border-border-onlook bg-background-secondary hover:bg-background-onlook border"
                             onClick={() => handleCreateFolder(currentFolder)}
                             disabled={isAnyOperationLoading}
+                            aria-label={t('images.createFolder')}
                         >
                             <Icons.DirectoryPlus className="h-4 w-4" />
                         </Button>
                     </TooltipTrigger>
                     <TooltipPortal>
-                        <TooltipContent>
-                            <p>Create a folder</p>
+                        <TooltipContent>
+                            <p>{t('images.createFolder')}</p>
                         </TooltipContent>
                     </TooltipPortal>
                 </Tooltip>

Add this outside the shown range:

import { useTranslations } from 'next-intl';
// inside Folder component:
const t = useTranslations();

If creation is intended only at the root, gate the button with currentFolder === rootDir as well.


🏁 Script executed:

#!/bin/bash
set -euo pipefail
file='apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/folder/index.tsx'
echo "LOOKUP: $file"
if [ -f "$file" ]; then
  echo "=== FILE FOUND ==="
  sed -n '1,320p' "$file"
else
  echo "=== FILE NOT FOUND AT EXACT PATH ==="
  echo "Searching for likely files..."
  rg -n --hidden --glob '!node_modules' "image-tab" -S || true
  rg -n --hidden --glob '!node_modules' "folder/index.tsx" -S || true
  rg -n --hidden --glob '!node_modules' "Create a folder" -S || true
  rg -n --hidden --glob '!node_modules' "TooltipContent" -S || true
  rg -n --hidden --glob '!node_modules' "useTranslations" -S || true
  rg -n --hidden --glob '!node_modules' "aria-label" -S || true
  rg -n --hidden --glob '!node_modules' "handleCreateFolder" -S || true
  rg -n --hidden --glob '!node_modules' "currentFolder" -S || true
fi

Length of output: 12963


Localize tooltip text and add accessible label to the icon-only "Create folder" button.

  • Add: import { useTranslations } from 'next-intl'; and inside Folder: const t = useTranslations();
  • Replace TooltipContent

    Create a folder

    with

    {t('images.createFolder')}

    and add aria-label={t('images.createFolder')} to the icon-only Create Folder Button.
  • Gate the header Create Folder button so it only renders when currentFolder === rootDir (or use the existing showCreateButton).
  • Also localize other visible strings ("Search images" placeholder, "Loading images...", "Refresh Images", "Upload an image") and add aria-labels to other icon-only buttons (Back/ArrowLeft, Refresh, Upload).
🤖 Prompt for AI Agents
In
apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/folder/index.tsx
around lines 211-228, tooltip text is hardcoded and the icon-only "Create
folder" button lacks an accessible label and conditional rendering; import
useTranslations from 'next-intl' and instantiate it (const t =
useTranslations()), replace the hardcoded TooltipContent <p>Create a folder</p>
with the localized key {t('images.createFolder')}, add
aria-label={t('images.createFolder')} to the icon-only Create Folder Button, and
gate that Create Folder button so it only renders when currentFolder === rootDir
or when showCreateButton is true; additionally, localize visible strings such as
the "Search images" placeholder, "Loading images...", "Refresh Images", and
"Upload an image" using t(...) and add aria-labels to other icon-only buttons
(Back/ArrowLeft, Refresh, Upload) to ensure accessibility.

Comment on lines +229 to +245
<Tooltip>
<TooltipTrigger asChild>
<Button
variant={'default'}
size={'icon'}
className="p-2 w-fit h-fit text-foreground-primary border-border-primary hover:border-border-onlook bg-background-secondary hover:bg-background-onlook border"
onClick={() => editorEngine.image.scanImages()}
>
<Icons.Reload className="w-4 h-4" />
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
<p>Refresh Images</p>
</TooltipContent>
</TooltipPortal>
</Tooltip>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Disable “Refresh Images” while scanning; localize text; add aria-label; avoid unhandled promise.

Prevents spam-clicks; aligns with i18n; clarifies intent.

Apply this diff:

                 <Tooltip>
                     <TooltipTrigger asChild>
                         <Button
                             variant={'default'}
                             size={'icon'}
                             className="p-2 w-fit h-fit text-foreground-primary border-border-primary hover:border-border-onlook bg-background-secondary hover:bg-background-onlook border"
-                            onClick={() => editorEngine.image.scanImages()}
+                            onClick={() => { void editorEngine.image.scanImages(); }}
+                            disabled={isAnyOperationLoading || editorEngine.image.isScanning}
+                            aria-label={t('images.refresh')}
                         >
                             <Icons.Reload className="w-4 h-4" />
                         </Button>
                     </TooltipTrigger>
                     <TooltipPortal>
                         <TooltipContent>
-                            <p>Refresh Images</p>
+                            <p>{t('images.refresh')}</p>
                         </TooltipContent>
                     </TooltipPortal>
                 </Tooltip>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Tooltip>
<TooltipTrigger asChild>
<Button
variant={'default'}
size={'icon'}
className="p-2 w-fit h-fit text-foreground-primary border-border-primary hover:border-border-onlook bg-background-secondary hover:bg-background-onlook border"
onClick={() => editorEngine.image.scanImages()}
>
<Icons.Reload className="w-4 h-4" />
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
<p>Refresh Images</p>
</TooltipContent>
</TooltipPortal>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant={'default'}
size={'icon'}
className="p-2 w-fit h-fit text-foreground-primary border-border-primary hover:border-border-onlook bg-background-secondary hover:bg-background-onlook border"
onClick={() => { void editorEngine.image.scanImages(); }}
disabled={isAnyOperationLoading || editorEngine.image.isScanning}
aria-label={t('images.refresh')}
>
<Icons.Reload className="w-4 h-4" />
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
<p>{t('images.refresh')}</p>
</TooltipContent>
</TooltipPortal>
</Tooltip>
🤖 Prompt for AI Agents
In
apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/folder/index.tsx
around lines 229-245, the Refresh Images button should be disabled while a scan
is in progress, use a localized tooltip/aria-label, and avoid an unhandled
promise from scanImages. Update the Button to set
disabled={editorEngine.image.isScanning} (or the appropriate scanning flag),
replace the hardcoded tooltip and aria-label with the i18n call (e.g.
t('refreshImages')) and add aria-label using that same localized string, and
change the onClick to an async handler that awaits
editorEngine.image.scanImages() inside try/catch (or attach .catch()) so errors
are handled. Ensure the tooltip content uses the localized string as well.

Comment on lines +391 to 404
// Use the image manager's scanned image paths instead of sandbox files
return editorEngine.image.imagePaths.filter(image => {
if (image.startsWith(folder.fullPath)) {
// Check if this is a direct child (not in a subdirectory)
const relativePath = image.slice(folder.fullPath.length);
// Remove leading slash if present
const cleanRelativePath = relativePath.startsWith('/') ? relativePath.slice(1) : relativePath;
// Only include if it's a direct child (no additional path separators)
if (!cleanRelativePath.includes('/')) {
return isImageFile(image);
}
return !cleanRelativePath.includes('/') && cleanRelativePath.length > 0;
}
return false;
});
}, [editorEngine.activeSandbox.files]);
}, [editorEngine.image.imagePaths]);

Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Fix direct-child check: require a path separator after the folder to prevent “publications” matching “public”.

Also fine to rely on image.imagePaths (already filtered to images).

Apply this diff:

-        return editorEngine.image.imagePaths.filter(image => {
-            if (image.startsWith(folder.fullPath)) {
-                // Check if this is a direct child (not in a subdirectory)
-                const relativePath = image.slice(folder.fullPath.length);
-                // Remove leading slash if present
-                const cleanRelativePath = relativePath.startsWith('/') ? relativePath.slice(1) : relativePath;
-                // Only include if it's a direct child (no additional path separators)
-                return !cleanRelativePath.includes('/') && cleanRelativePath.length > 0;
-            }
-            return false;
-        });
+        return editorEngine.image.imagePaths.filter(image => {
+            const prefix = `${folder.fullPath}/`;
+            if (!image.startsWith(prefix)) return false;
+            const cleanRelativePath = image.slice(prefix.length);
+            return !cleanRelativePath.includes('/') && cleanRelativePath.length > 0;
+        });

Please confirm activeSandbox emits image paths without a leading slash (e.g., "public/foo.png"). If it can include "/public/...", we should normalize before comparisons.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"

echo
echo "1) Find occurrences of 'imagePaths' and editorEngine usage"
rg -n --hidden -S "imagePaths" -g '!.git' -C3 || true
rg -n --hidden -S "\\beditorEngine\\b" -g '!.git' -C3 || true

echo
echo "2) Show the folder-provider file (if present)"
FILE="apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/providers/folder-provider.tsx"
if [ -f "$FILE" ]; then
  echo "File found: $FILE"
  echo "---- begin file (1..520) ----"
  sed -n '1,520p' "$FILE"
  echo "---- end file ----"
else
  echo "File not found at exact path: $FILE"
  echo "Searching for similarly named files..."
  rg -n --hidden -S "folder-provider\\.tsx|left-panel/image-tab/providers" -g '!.git' -C2 || true
fi

echo
echo "3) Search where imagePaths are populated / normalized"
rg -n --hidden -S "imagePaths\\s*[:=]" -g '!.git' -C3 || true
rg -n --hidden -S "setImagePaths|addImagePaths|push\\([^)]*image" -g '!.git' -C3 || true
rg -n --hidden -S "scanImages|scan.*image|listImages|list_images|list_images\\(|walkSync|readdirSync|glob\\(" -g '!.git' -C3 || true

echo
echo "4) Search for 'activeSandbox' and any code that emits sandbox paths"
rg -n --hidden -S "activeSandbox|sandbox" -g '!.git' -C3 || true

echo
echo "5) Search for '/public' or leading-slash path building / normalization"
rg -n --hidden -S "/public|\"/public|'\/public|^/public" -g '!.git' -C3 || true
rg -n --hidden -S "replace\\(\\s*['\"]/|^\\s*['\"]/|^\\s*/" -g '!.git' -C3 || true

echo
echo "Done."

Length of output: 15745


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"
echo

echo "1) Locate editor store / useEditorEngine"
rg -n --hidden -uu "useEditorEngine" -S -g '!.git' -C3 || true

echo
echo "2) List files under components/store (if present)"
ls -la apps/web/client/src/components/store || true
echo

echo "3) Show files in that directory (editor.*) if present"
for f in apps/web/client/src/components/store/editor* apps/web/client/src/components/store/*editor*; do
  if [ -f "$f" ]; then
    echo "---- FILE: $f ----"
    sed -n '1,240p' "$f"
    echo "---- END FILE: $f ----"
    echo
  fi
done

echo "4) Grep for imagePaths, activeSandbox, directories occurrences (fallback to grep)"
echo
grep -RIn --line-number "imagePaths" || true
echo
grep -RIn --line-number "activeSandbox" || true
echo
grep -RIn --line-number "directories" || true

echo
echo "Done."

Length of output: 28207


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Printing image store:"
echo "----------------------------------------"
sed -n '1,320p' apps/web/client/src/components/store/editor/image/index.ts || true
echo
echo "Printing sandbox store (to see how files/directories are listed):"
echo "----------------------------------------"
sed -n '1,520p' apps/web/client/src/components/store/editor/sandbox/index.ts || true
echo
echo "Search for IMAGE_FOLDER / DefaultSettings definitions:"
rg -n --hidden "IMAGE_FOLDER|DefaultSettings" || true
echo
echo "Show any files that mention IMAGE_FOLDER:"
rg -n --hidden --line-number "IMAGE_FOLDER" -S || true

Length of output: 129954


Require a trailing '/' when matching direct children (fix ambiguous prefix match)

  • Apply the suggested diff in apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/providers/folder-provider.tsx: match using const prefix = ${folder.fullPath}/ and slice by prefix.length so "publications" will not match "public".
  • Verified: DefaultSettings.IMAGE_FOLDER === 'public' and editorEngine.image.imagePaths are populated from activeSandbox.files as "public/..." (no leading slash), so no extra normalization is required; if a provider can emit "/public/...", strip a leading '/' before comparing.
🤖 Prompt for AI Agents
In
apps/web/client/src/app/project/[id]/_components/left-panel/image-tab/providers/folder-provider.tsx
around lines 391 to 404, the current prefix check can produce ambiguous matches
(e.g., "public" matching "publications"); change the logic to build a prefix
const prefix = `${folder.fullPath}/` and compare against a normalized image path
(strip a leading '/' if present) using image.startsWith(prefix), then slice by
prefix.length to get the relative child name and only include entries without
additional '/' and with length > 0; ensure you normalize folder.fullPath
similarly if providers may emit a leading '/'.

Comment on lines +21 to 33
() => {
return {
isIndexing: this.editorEngine.activeSandbox.isIndexing,
isIndexed: this.editorEngine.activeSandbox.isIndexed,
};
},
(sandboxStatus) => {
if (this.editorEngine.state.leftPanelTab !== LeftPanelTabValue.IMAGES) {
return;
}
if (sandboxStatus.isIndexed && !sandboxStatus.isIndexing) {
this.scanImages();
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Reaction misses leftPanelTab in its tracked derivation; IMAGES tab activation won’t trigger a scan.

Include the tab in the reaction’s data source so switching to the IMAGES tab fires scanImages when indexed.

Apply this diff:

-        this.indexingReactionDisposer = reaction(
-            () => {
-                return {
-                    isIndexing: this.editorEngine.activeSandbox.isIndexing,
-                    isIndexed: this.editorEngine.activeSandbox.isIndexed,
-                };
-            },
-            (sandboxStatus) => {
-                if (this.editorEngine.state.leftPanelTab !== LeftPanelTabValue.IMAGES) {
-                    return;
-                }
-                if (sandboxStatus.isIndexed && !sandboxStatus.isIndexing) {
-                    this.scanImages();
-                }
-            },
-        );
+        this.indexingReactionDisposer = reaction(
+            () => ({
+                tab: this.editorEngine.state.leftPanelTab,
+                isIndexing: this.editorEngine.activeSandbox.isIndexing,
+                isIndexed: this.editorEngine.activeSandbox.isIndexed,
+            }),
+            ({ tab, isIndexing, isIndexed }) => {
+                if (tab !== LeftPanelTabValue.IMAGES) return;
+                if (isIndexed && !isIndexing) {
+                    void this.scanImages();
+                }
+            },
+        );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
() => {
return {
isIndexing: this.editorEngine.activeSandbox.isIndexing,
isIndexed: this.editorEngine.activeSandbox.isIndexed,
};
},
(sandboxStatus) => {
if (this.editorEngine.state.leftPanelTab !== LeftPanelTabValue.IMAGES) {
return;
}
if (sandboxStatus.isIndexed && !sandboxStatus.isIndexing) {
this.scanImages();
}
this.indexingReactionDisposer = reaction(
() => ({
tab: this.editorEngine.state.leftPanelTab,
isIndexing: this.editorEngine.activeSandbox.isIndexing,
isIndexed: this.editorEngine.activeSandbox.isIndexed,
}),
({ tab, isIndexing, isIndexed }) => {
if (tab !== LeftPanelTabValue.IMAGES) return;
if (isIndexed && !isIndexing) {
void this.scanImages();
}
},
);
🤖 Prompt for AI Agents
In apps/web/client/src/components/store/editor/image/index.ts around lines 21 to
33, the reaction only tracks sandbox indexing state so switching the left panel
to IMAGES won’t trigger scanImages; update the derivation to also return the
current leftPanelTab (e.g., include leftPanelTab:
this.editorEngine.state.leftPanelTab) and adjust the reaction callback to read
that field so changes to the tab cause the reaction to re-run and call
this.scanImages() when the sandbox is indexed and not indexing.

Comment on lines +209 to 210
this._imagePaths = files.filter((file: string) => file.startsWith(DefaultSettings.IMAGE_FOLDER) && isImageFile(file));
} catch (error) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Avoid false positives: enforce folder boundary when filtering (public vs publications).

Use ${DefaultSettings.IMAGE_FOLDER}/ as the prefix.

Apply this diff:

-            this._imagePaths = files.filter((file: string) => file.startsWith(DefaultSettings.IMAGE_FOLDER) && isImageFile(file));
+            const prefix = `${DefaultSettings.IMAGE_FOLDER}/`;
+            this._imagePaths = files.filter((file: string) => file.startsWith(prefix) && isImageFile(file));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this._imagePaths = files.filter((file: string) => file.startsWith(DefaultSettings.IMAGE_FOLDER) && isImageFile(file));
} catch (error) {
const prefix = `${DefaultSettings.IMAGE_FOLDER}/`;
this._imagePaths = files.filter((file: string) => file.startsWith(prefix) && isImageFile(file));
} catch (error) {
🤖 Prompt for AI Agents
In apps/web/client/src/components/store/editor/image/index.ts around lines
209-210, the filter currently uses file.startsWith(DefaultSettings.IMAGE_FOLDER)
which can match sibling folders (causing false positives); change the prefix
check to require the folder boundary by using
file.startsWith(`${DefaultSettings.IMAGE_FOLDER}/`) so only files directly under
the IMAGE_FOLDER path are accepted, and keep the existing isImageFile(file)
check.

@coderabbitai coderabbitai bot mentioned this pull request Sep 19, 2025
6 tasks
@coderabbitai coderabbitai bot mentioned this pull request Sep 26, 2025
6 tasks
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.

2 participants