Skip to content

refactor: align file mentions with sandbox files flow#1664

Merged
arpitgupta1214 merged 16 commits into
testfrom
codex/file-list-dedicated-chat
Apr 14, 2026
Merged

refactor: align file mentions with sandbox files flow#1664
arpitgupta1214 merged 16 commits into
testfrom
codex/file-list-dedicated-chat

Conversation

@arpitgupta1214

@arpitgupta1214 arpitgupta1214 commented Apr 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR has been repurposed after the dedicated files-list API migration was rolled back.

  • Keep mentions aligned with the same sandbox-backed source used by the Files page (/api/sandboxes + /api/sandboxes/file)
  • Remove legacy/unused chat-side file-listing path that depended on /api/files/list
  • Restore mention suggestion image thumbnails using sandbox file content
  • Consolidate duplicate preview/content logic into a single reusable hook

Why this changed

The original plan introduced a separate files-list endpoint for mentions, but the Files section already relies on sandbox APIs. To avoid parallel data paths and closed API/docs workstreams, mentions now reuse the existing sandbox file flow.

Key Changes

  • Mentions source
    • useArtistFilesForMentions now reads from sandbox filetree (useSandboxes) rather than files table listing
  • Mention attachments + context in chat
    • Mentioned files are resolved via sandbox file API (getFileContents)
    • Images/PDFs are attached as file parts (data URLs), text file contents are prepended as context
  • Suggestion thumbnails
    • Thumbnail rendering restored in SuggestionItem
    • Uses sandbox file content fetch path and local caching behavior through shared hook
  • Hook consolidation
    • useSandboxFileContent refactored to support both:
      • select-driven sandbox file preview usage
      • path-driven usage for mention thumbnails
    • Removed duplicate useSandboxFilePreview
  • Cleanup
    • Removed unused legacy route/hook path in chat:
      • /app/api/files/list/route.ts
      • hooks/useFilesManager.ts
      • hooks/useBatchSignedUrls.ts

Verification

  • Targeted lint checks on all touched files in this PR pass.
  • Full typecheck still has unrelated pre-existing test typing errors in lib/emails/__tests__/extractSendEmailResults.test.ts.

Notes

  • Backend /api/sandboxes/file returns { status, content, encoding? }.
  • imageUrl is derived client-side in getFileContents when encoding === "base64".

Summary by CodeRabbit

  • New Features

    • Mentions now read directly from the sandbox file tree with on-demand file content and MIME detection.
    • Image previews are loaded from sandbox content when available.
  • Bug Fixes

    • More reliable suggestion rendering and preview fallbacks for non-image files.
  • Refactor

    • Removed legacy signed-URL batching and moved attachment/context handling to sandbox-based retrieval.
  • Chores

    • Cleaned up obsolete file-listing and file-manager code paths.

@vercel

vercel Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

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

Project Deployment Actions Updated (UTC)
recoup-chat Ready Ready Preview Apr 14, 2026 9:51pm

Request Review

@coderabbitai

coderabbitai Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@sweetmantech has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 47 minutes and 41 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 47 minutes and 41 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 54995f72-f99a-40e5-8942-125afc3a13e9

📥 Commits

Reviewing files that changed from the base of the PR and between 5cf4ba4 and d61cea0.

📒 Files selected for processing (2)
  • hooks/useArtistFilesForMentions.ts
  • lib/sandboxes/workspaceOrgsPath.ts
📝 Walkthrough

Walkthrough

This PR replaces Supabase/signed-URL and ListedFileRow-based file mention flows with a sandbox-backed FileNode tree: types change from storage_keypath, signed-URL preloading is removed, file content is read from sandboxes, and mention attachments/text are derived from sandbox file contents.

Changes

Cohort / File(s) Summary
Artist file source & suggestion types
hooks/useArtistFilesForMentions.ts, hooks/useFileMentionSuggestions.ts
Switched source to useSandboxes/filetree; flattened FileNode subtree into mentionable files; replaced storage_key with path in exported types (MentionableFile, GroupedSuggestion); compute relative_path by stripping WORKSPACE_ORGS_PREFIX; use getMimeFromPath.
Signed URL / preview removal
hooks/useBatchSignedUrls.ts, components/VercelChat/FileMentionsInput.tsx
Removed useBatchSignedUrls hook and removed parent-driven signed-URL/image preloading; FileMentionsInput no longer derives/passes imageUrl.
Suggestion rendering / image loading
components/VercelChat/SuggestionItem.tsx, hooks/useSandboxFileContent.ts
SuggestionItem drops imageUrl prop and detects images via entry.path/entry.display; it calls useSandboxFileContent({ path, enabled }) to fetch image data. useSandboxFileContent changed from mutation-based to query-based API with options: { path?, enabled? }.
Chat hook: composing messages with mentions
hooks/useVercelChat.ts
Replaced signed-URL resolution with sandbox reads using usePrivy().getAccessToken() + getFileContents(accessToken, path); builds mentionAttachments (data-URL FileUIParts for images/PDFs) and mentionTextContext (inline text/code) and includes them in outgoing message payload.
Sandbox helpers
lib/sandboxes/getSubtreeAtPath.ts, lib/sandboxes/workspaceOrgsPath.ts
Imported WORKSPACE_ORGS_PATH from new workspaceOrgsPath file; added exports WORKSPACE_ORGS_PATH and WORKSPACE_ORGS_PREFIX for canonical workspace subtree handling.
Removed files / API
hooks/useFilesManager.ts, app/api/files/list/route.ts
Deleted legacy useFilesManager hook and ListedFileRow types; removed server GET /api/files/list handler previously used by the old listing flow.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant UI as FileMentionsInput
    participant Suggestion as SuggestionItem
    participant ChatHook as useVercelChat
    participant Sandbox as Sandbox API

    User->>UI: open mention picker / select file
    UI->>Suggestion: render entry (uses entry.path)
    Suggestion->>Suggestion: detect image via path/display
    alt image
        Suggestion->>Sandbox: useSandboxFileContent.query(path, enabled)
        Sandbox-->>Suggestion: imageUrl / content
        Suggestion-->>UI: render <img> or placeholder
    else non-image
        Suggestion-->>UI: render placeholder
    end

    User->>UI: submit message with mentions
    UI->>ChatHook: send with mention paths
    activate ChatHook
    ChatHook->>Sandbox: getFileContents(accessToken, path) for each mention
    Sandbox-->>ChatHook: file contents
    ChatHook->>ChatHook: derive MIME, build mentionAttachments and mentionTextContext
    ChatHook-->>UI: finalize message payload (text + attachments)
    deactivate ChatHook
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

📁 From rows to trees the mentions drift,
Paths take the place of keys once swift,
Sandboxes fetch the bits we need,
Images load only when we plead,
A tidier flow — a lighter lift. 🎏

🚥 Pre-merge checks | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning Pull request violates DRY, SRP, and OCP principles through image detection logic duplication, oversized hook handling multiple concerns, and unaddressed critical review comments on file size validation, unsafe ID encoding, and incomplete resolution detection. Unify image detection logic via isImagePath utility, extract mention resolution into separate hook with size validation, implement ID encoding and complete resolution checks, and decompose 369-line useVercelChat hook into focused single-responsibility hooks.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/file-list-dedicated-chat

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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 3 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Requires human review: Refactor involves deleting a local API route, switching to an external API, and changing authentication logic (Bearer tokens), which requires human verification.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

0 issues found across 2 files (changes from recent commits).

Requires human review: This PR refactors file-fetching logic to a new API endpoint and introduces authentication changes, which could impact core file management functionality.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

0 issues found across 1 file (changes from recent commits).

Requires human review: This PR involves an API migration, changes the authentication mechanism to Bearer tokens, and modifies the request contract, which requires manual verification.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

0 issues found across 1 file (changes from recent commits).

Requires human review: This refactor involves architectural changes by moving logic to a dedicated API, deleting an existing route, and modifying the authentication flow for file management.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
hooks/useFilesManager.ts (1)

37-40: Extract token retrieval into one helper to remove duplication.

The same access-token guard appears twice; pulling it into a local helper keeps the hook easier to maintain.

♻️ Suggested DRY refactor
+  async function getRequiredAccessToken() {
+    const accessToken = await getAccessToken();
+    if (!accessToken) throw new Error("Please sign in to view files");
+    return accessToken;
+  }
+
   const { data, isLoading } = useQuery<{ files: Array<ListedFileRow> }>({
@@
-      const accessToken = await getAccessToken();
-      if (!accessToken) {
-        throw new Error("Please sign in to view files");
-      }
+      const accessToken = await getRequiredAccessToken();
@@
-          const accessToken = await getAccessToken();
-          if (!accessToken) {
-            throw new Error("Please sign in to view files");
-          }
+          const accessToken = await getRequiredAccessToken();

As per coding guidelines, "hooks/**/*.ts: DRY: Extract common hook logic into shared utilities; KISS: Avoid complex hook compositions, prefer simple state management."

Also applies to: 66-69

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useFilesManager.ts` around lines 37 - 40, Extract the duplicated
access-token check into a small helper (e.g., ensureAccessToken or
getRequiredAccessToken) and use it wherever getAccessToken + throw is repeated
in this hook; the helper should call getAccessToken(), throw the same
Error("Please sign in to view files") when falsy, and return the token otherwise
so callers (the functions currently containing the duplicated blocks around
getAccessToken) simply call the helper and proceed. Ensure the helper is defined
near the top of useFilesManager.ts (or moved to a shared hook util if used
elsewhere) and replace both occurrences (the blocks around getAccessToken at the
current duplicated locations) with a single call to the new helper.
lib/files/fetchFiles.ts (1)

53-53: Add a minimal runtime response-shape guard before returning JSON.

response.json() is trusted as FetchFilesResponse without validation; malformed payloads will fail downstream with less actionable errors.

♻️ Suggested hardening
-  return response.json();
+  const payload: unknown = await response.json();
+  if (
+    !payload ||
+    typeof payload !== "object" ||
+    !Array.isArray((payload as { files?: unknown }).files)
+  ) {
+    throw new Error("Invalid response shape from /api/files");
+  }
+  return payload as FetchFilesResponse;

As per coding guidelines, "lib/**/*.ts: For utility functions, ensure: Proper error handling; Use TypeScript for type safety; KISS: Prefer simple, readable implementations."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/files/fetchFiles.ts` at line 53, The call that returns response.json()
should first parse and minimally validate the runtime shape against the expected
FetchFilesResponse before trusting it; update the fetchFiles function (the place
that does return response.json()) to await and store const payload = await
response.json(), check simple invariants (e.g., payload is an object/array and
contains the expected keys or types used downstream — reference the
FetchFilesResponse shape), and if validation fails throw a clear Error
describing the unexpected response (including response.status or a short payload
snippet) so callers don't receive malformed data.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hooks/useFilesManager.ts`:
- Around line 63-65: The prefetchQuery call in qc.prefetchQuery uses a queryKey
of ["files", ownerAccountId, artistAccountId, childPath] but the main query
includes the `recursive` flag, so add `recursive` to the queryKey to match cache
keys; update both qc.prefetchQuery occurrences (the ones referencing
ownerAccountId, artistAccountId, childPath) to use ["files", ownerAccountId,
artistAccountId, childPath, recursive] so prefetched results are reusable by the
main query.

In `@lib/files/fetchFiles.ts`:
- Around line 28-46: The client is calling GET /api/files from
lib/files/fetchFiles.ts but there is no server handler; add a new route handler
file that implements the root GET endpoint (app/api/files/route.ts) which reads
the query params artist_account_id, path, and recursive, validates the
Authorization header, fetches and returns the file listing as JSON (proper
200/401/4xx error responses), and matches the shape expected by fetchFiles.ts;
ensure the handler exports an async GET function that parses req.nextUrl or
Request.query, performs authorization and error handling, and returns a JSON
Response with the file list.

---

Nitpick comments:
In `@hooks/useFilesManager.ts`:
- Around line 37-40: Extract the duplicated access-token check into a small
helper (e.g., ensureAccessToken or getRequiredAccessToken) and use it wherever
getAccessToken + throw is repeated in this hook; the helper should call
getAccessToken(), throw the same Error("Please sign in to view files") when
falsy, and return the token otherwise so callers (the functions currently
containing the duplicated blocks around getAccessToken) simply call the helper
and proceed. Ensure the helper is defined near the top of useFilesManager.ts (or
moved to a shared hook util if used elsewhere) and replace both occurrences (the
blocks around getAccessToken at the current duplicated locations) with a single
call to the new helper.

In `@lib/files/fetchFiles.ts`:
- Line 53: The call that returns response.json() should first parse and
minimally validate the runtime shape against the expected FetchFilesResponse
before trusting it; update the fetchFiles function (the place that does return
response.json()) to await and store const payload = await response.json(), check
simple invariants (e.g., payload is an object/array and contains the expected
keys or types used downstream — reference the FetchFilesResponse shape), and if
validation fails throw a clear Error describing the unexpected response
(including response.status or a short payload snippet) so callers don't receive
malformed data.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 765bf83b-5dfe-4dfd-b130-e1c674e032f0

📥 Commits

Reviewing files that changed from the base of the PR and between 48ef4ed and e8f951a.

📒 Files selected for processing (4)
  • app/api/files/list/route.ts
  • hooks/useArtistFilesForMentions.ts
  • hooks/useFilesManager.ts
  • lib/files/fetchFiles.ts
💤 Files with no reviewable changes (1)
  • app/api/files/list/route.ts

Comment thread hooks/useFilesManager.ts Outdated
Comment thread lib/files/fetchFiles.ts Outdated
@sweetmantech

Copy link
Copy Markdown
Collaborator

Change Request

@arpitgupta1214 — after reviewing this PR and the related docs PR (recoupable/docs#122), I think we should take a different approach here.

Problem with the current approach

The @ mention file suggestions (useArtistFilesForMentions) currently fetch from a local /api/files/list route. This PR migrates that to a dedicated /api/files endpoint on the API — but that introduces a second endpoint for file listing when we already have one.

What we already have

The Files page (app/files/page.tsx) already pulls file data from /api/sandboxes via useSandboxes. That endpoint returns the same file tree data the @ mention feature needs.

Requested change

Instead of migrating to a new /api/files endpoint, refactor useArtistFilesForMentions to consume the sandbox data from useSandboxes (or share its query). This way:

  • We eliminate the need for a separate /api/files endpoint entirely
  • Both the Files page and @ mentions use the same data source (/api/sandboxes)
  • We can close the docs PR (docs: move file list endpoint to dedicated api docs#122) since /api/files won't be needed
  • Less surface area to maintain

Let me know if you have questions or see any reason the sandbox data wouldn't work for the @ mentions use case.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2 issues found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="hooks/useFilesManager.ts">

<violation number="1">
P1: Prefetch sends the full storage path (e.g. `files/owner123/artist456/subfolder/`) as the `path` query param, but the main query strips the `files/{owner}/{artist}/` prefix first. The old relative-path computation was removed, so every prefetch request sends the wrong path to the API, making all prefetched results incorrect (likely empty).</violation>
</file>

<file name="app/api/files/list/route.ts">

<violation number="1">
P2: Custom agent: **Flag AI Slop and Fabricated Changes**

Narrating comment restates the next line. `listFilesByArtist` is self-documenting; remove the comment or replace it with *why* this specific helper is appropriate (e.g., the authorization/filtering guarantee the caller relies on).</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2 issues found across 6 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="hooks/useVercelChat.ts">

<violation number="1" location="hooks/useVercelChat.ts:111">
P1: Removing the signed-URL cache without a replacement causes `getFileContents` to fire a network request for every mentioned file **on every keystroke** while a mention is present in the input. `selectedFileIds` is a new array reference whenever `input` changes (even if the actual IDs are identical), which re-triggers this effect.

Consider adding a cache (e.g. a `useRef<Map>` keyed by file path) or stabilising `selectedFileIds` so the effect only re-runs when the set of IDs actually changes.</violation>
</file>

<file name="hooks/useBatchSignedUrls.ts">

<violation number="1" location="hooks/useBatchSignedUrls.ts:6">
P2: This hook is dead code — it's exported but never imported anywhere in the codebase. Per the project's YAGNI principle ("delete unused code; don't anticipate needs"), delete the file entirely rather than maintaining a no-op stub. The `void suggestions` trick to silence the unused-param warning is a clear signal the function serves no purpose.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Comment thread hooks/useVercelChat.ts
throw new Error("Please sign in to attach mentioned files");
}

const results = await Promise.all(

@cubic-dev-ai cubic-dev-ai Bot Apr 13, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Removing the signed-URL cache without a replacement causes getFileContents to fire a network request for every mentioned file on every keystroke while a mention is present in the input. selectedFileIds is a new array reference whenever input changes (even if the actual IDs are identical), which re-triggers this effect.

Consider adding a cache (e.g. a useRef<Map> keyed by file path) or stabilising selectedFileIds so the effect only re-runs when the set of IDs actually changes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At hooks/useVercelChat.ts, line 111:

<comment>Removing the signed-URL cache without a replacement causes `getFileContents` to fire a network request for every mentioned file **on every keystroke** while a mention is present in the input. `selectedFileIds` is a new array reference whenever `input` changes (even if the actual IDs are identical), which re-triggers this effect.

Consider adding a cache (e.g. a `useRef<Map>` keyed by file path) or stabilising `selectedFileIds` so the effect only re-runs when the set of IDs actually changes.</comment>

<file context>
@@ -78,91 +76,90 @@ export function useVercelChat({
+          throw new Error("Please sign in to attach mentioned files");
+        }
+
+        const results = await Promise.all(
+          selected.map(async (f) => {
+            const fileResult = await getFileContents(accessToken, f.path);
</file context>
Fix with Cubic

Comment thread hooks/useBatchSignedUrls.ts Outdated

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (6)
hooks/useVercelChat.ts (3)

88-89: Simplify state updates — the conditional checks add noise without benefit.

The pattern setX((prev) => (prev.length ? [] : prev)) prevents an update only when state is already falsy/empty. React already bails out on identical state values for primitive comparisons, but for arrays/strings this micro-optimization adds cognitive overhead. Consider straightforward updates since the effect already guards against unnecessary runs.

♻️ Cleaner state resets
       if (!selectedFileIds.length) {
-        if (!cancelled) setMentionAttachments((prev) => (prev.length ? [] : prev));
-        if (!cancelled) setMentionTextContext((prev) => (prev ? "" : prev));
+        if (!cancelled) setMentionAttachments([]);
+        if (!cancelled) setMentionTextContext("");
         if (!cancelled) setIsLoadingSignedUrls((prev) => (prev ? false : prev));
         return;
       }

Also applies to: 97-98, 147-149, 153-154

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useVercelChat.ts` around lines 88 - 89, Remove the unnecessary
prev-based conditional updates and directly reset the state inside the cancelled
guard: where you currently call setMentionAttachments((prev) => (prev.length ?
[] : prev)) and setMentionTextContext((prev) => (prev ? "" : prev)) (and the
similar occurrences at the other locations referenced), replace them with direct
setters setMentionAttachments([]) and setMentionTextContext("") while keeping
the existing if (!cancelled) guards; this simplifies the logic and relies on
React's state equality behavior to avoid redundant re-renders.

111-122: Consider handling partial failures when fetching multiple files.

Promise.all will reject entirely if any single file fetch fails, leaving the user with no attachments. Using Promise.allSettled would allow graceful degradation — successfully fetched files can still be attached while failures are logged.

♻️ Resilient file fetching with partial success
-        const results = await Promise.all(
-          selected.map(async (f) => {
-            const fileResult = await getFileContents(accessToken, f.path);
-            const mimeType = f.mime_type || getMimeFromPath(f.path);
-            return {
-              path: f.path,
-              mimeType,
-              content: fileResult.content,
-              dataUrl: fileResult.imageUrl,
-            };
-          }),
-        );
+        const settled = await Promise.allSettled(
+          selected.map(async (f) => {
+            const fileResult = await getFileContents(accessToken, f.path);
+            const mimeType = f.mime_type || getMimeFromPath(f.path);
+            return {
+              path: f.path,
+              mimeType,
+              content: fileResult.content,
+              dataUrl: fileResult.imageUrl,
+            };
+          }),
+        );
+
+        const results = settled
+          .filter((r): r is PromiseFulfilledResult<typeof r extends PromiseFulfilledResult<infer T> ? T : never> => r.status === "fulfilled")
+          .map((r) => r.value);
+
+        const failures = settled.filter((r) => r.status === "rejected");
+        if (failures.length > 0) {
+          console.warn(`Failed to fetch ${failures.length} mentioned file(s)`);
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useVercelChat.ts` around lines 111 - 122, The current batch fetch uses
Promise.all which fails entirely if any getFileContents call rejects; change the
selected map call in useVercelChat.ts to use Promise.allSettled over the array
of getFileContents promises, then filter the settled results for status ===
"fulfilled" and build the results array from result.value (ensuring you still
compute mimeType via getMimeFromPath and include content and dataUrl), while
logging or reporting any entries with status === "rejected" (include f.path and
the error) so partial successes are preserved and failures are visible.

39-380: Hook is approaching complexity threshold — consider extraction.

At ~340 lines with multiple concerns (message loading, file mention handling, form submission, model selection), this hook could benefit from extracting the mention-file resolution logic into a dedicated useMentionAttachments hook. This would improve testability and align with the single-responsibility guideline for hooks.

As per coding guidelines: "Single responsibility per hook" and "Extract custom hooks for complex logic".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useVercelChat.ts` around lines 39 - 380, The hook is doing too much —
extract the mention-file resolution logic (selectedFileIds computation, the
useEffect that fetches getFileContents/getMimeFromPath, and the state vars
mentionAttachments, mentionTextContext, isLoadingSignedUrls) into a new custom
hook (e.g., useMentionAttachments) that accepts input, allArtistFiles and
getAccessToken and returns { mentionAttachments, mentionTextContext,
isLoadingSignedUrls }; move helper logic around building results and error
handling into that hook and keep useVercelChat only responsible for chat flow
(replace the local selectedFileIds, setMentionAttachments,
setMentionTextContext, setIsLoadingSignedUrls and the long useEffect with a call
to useMentionAttachments and update imports/usages accordingly).
components/VercelChat/SuggestionItem.tsx (1)

23-39: Consider adding accessibility attributes for screen reader support.

This element is interactive (cursor-pointer, part of a selectable list). Adding role="option" and aria-selected={focused} would improve screen reader compatibility within the suggestion dropdown.

♿ Accessibility enhancement
     <div
+      role="option"
+      aria-selected={focused}
       className={cn(
         "px-3 py-2 text-[13px] cursor-pointer select-none",
         "flex items-center gap-2 rounded-md",
         focused ? "bg-accent text-foreground" : "text-muted-foreground hover:bg-accent/50"
       )}
     >

As per coding guidelines: "Provide proper ARIA roles/states".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/VercelChat/SuggestionItem.tsx` around lines 23 - 39, The outer
interactive div in SuggestionItem.tsx (the returned element in the component
that uses focused, isImage, ImageIcon, highlightedDisplay, and entry.display)
needs ARIA attributes for screen readers: add role="option" and set
aria-selected equal to the focused boolean (aria-selected={focused}) on that
same div so assistive tech can treat it as a selectable list option; ensure
these attributes are applied where the className and focus logic currently live.
hooks/useFileMentionSuggestions.ts (1)

26-37: Consider removing the type assertion.

The as GroupedSuggestion assertion on line 37 can be eliminated by ensuring the mapped object satisfies the interface directly. This improves type safety and makes the compiler catch mismatches.

♻️ Suggested improvement
-        return { 
-          id: f.id, 
-          display: name, 
-          group,
-          mime_type: f.mime_type,
-          path: f.path,
-        } as GroupedSuggestion;
+        const suggestion: GroupedSuggestion = { 
+          id: f.id, 
+          display: name, 
+          group,
+          mime_type: f.mime_type,
+          path: f.path,
+        };
+        return suggestion;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useFileMentionSuggestions.ts` around lines 26 - 37, Remove the "as
GroupedSuggestion" type assertion on the mapped return and make the object
conform to the GroupedSuggestion type explicitly: annotate the map callback
return type (or set the array.map generic) so the compiler enforces fields,
ensure the returned object from the map includes all required GroupedSuggestion
properties (e.g., id, display, group, mime_type, path) with correct names and
types, and adjust the computed variables (rel, lastSlash, group, name) if needed
to match expected types; this will allow you to delete the trailing type
assertion in useFileMentionSuggestions.ts.
hooks/useBatchSignedUrls.ts (1)

5-9: Delete this hook entirely — it serves no purpose and is unused elsewhere.

The hook returns an empty object and ignores all inputs. No callers reference it in the codebase, so removing it eliminates dead code without breaking anything. This aligns with clean code principles and reduces maintenance burden.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useBatchSignedUrls.ts` around lines 5 - 9, Remove the dead hook
useBatchSignedUrls (exported function useBatchSignedUrls and its file
hooks/useBatchSignedUrls.ts) since it always returns an empty Record, ignores
its GroupedSuggestion[] parameter, and has no callers; delete the file and any
associated export from index barrels if present to eliminate unused code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hooks/useVercelChat.ts`:
- Line 162: The effect that fetches file contents (the useEffect covering lines
~84-162) is re-running because the Privy getAccessToken function reference is
unstable; update the code so the effect no longer depends on a non-memoized
function reference: either wrap getAccessToken with useCallback in the parent
(so the getAccessToken reference passed into useVercelChat is stable) or change
the effect to fetch the token once outside the effect (or store the token in a
ref/state) and replace getAccessToken from the dependency array with the stable
token/ref; target the dependency list that currently uses [selectedFileIds,
allArtistFiles, getAccessToken] and ensure only stable values remain so
fetchFiles logic (inside the effect) does not run every render.

---

Nitpick comments:
In `@components/VercelChat/SuggestionItem.tsx`:
- Around line 23-39: The outer interactive div in SuggestionItem.tsx (the
returned element in the component that uses focused, isImage, ImageIcon,
highlightedDisplay, and entry.display) needs ARIA attributes for screen readers:
add role="option" and set aria-selected equal to the focused boolean
(aria-selected={focused}) on that same div so assistive tech can treat it as a
selectable list option; ensure these attributes are applied where the className
and focus logic currently live.

In `@hooks/useBatchSignedUrls.ts`:
- Around line 5-9: Remove the dead hook useBatchSignedUrls (exported function
useBatchSignedUrls and its file hooks/useBatchSignedUrls.ts) since it always
returns an empty Record, ignores its GroupedSuggestion[] parameter, and has no
callers; delete the file and any associated export from index barrels if present
to eliminate unused code.

In `@hooks/useFileMentionSuggestions.ts`:
- Around line 26-37: Remove the "as GroupedSuggestion" type assertion on the
mapped return and make the object conform to the GroupedSuggestion type
explicitly: annotate the map callback return type (or set the array.map generic)
so the compiler enforces fields, ensure the returned object from the map
includes all required GroupedSuggestion properties (e.g., id, display, group,
mime_type, path) with correct names and types, and adjust the computed variables
(rel, lastSlash, group, name) if needed to match expected types; this will allow
you to delete the trailing type assertion in useFileMentionSuggestions.ts.

In `@hooks/useVercelChat.ts`:
- Around line 88-89: Remove the unnecessary prev-based conditional updates and
directly reset the state inside the cancelled guard: where you currently call
setMentionAttachments((prev) => (prev.length ? [] : prev)) and
setMentionTextContext((prev) => (prev ? "" : prev)) (and the similar occurrences
at the other locations referenced), replace them with direct setters
setMentionAttachments([]) and setMentionTextContext("") while keeping the
existing if (!cancelled) guards; this simplifies the logic and relies on React's
state equality behavior to avoid redundant re-renders.
- Around line 111-122: The current batch fetch uses Promise.all which fails
entirely if any getFileContents call rejects; change the selected map call in
useVercelChat.ts to use Promise.allSettled over the array of getFileContents
promises, then filter the settled results for status === "fulfilled" and build
the results array from result.value (ensuring you still compute mimeType via
getMimeFromPath and include content and dataUrl), while logging or reporting any
entries with status === "rejected" (include f.path and the error) so partial
successes are preserved and failures are visible.
- Around line 39-380: The hook is doing too much — extract the mention-file
resolution logic (selectedFileIds computation, the useEffect that fetches
getFileContents/getMimeFromPath, and the state vars mentionAttachments,
mentionTextContext, isLoadingSignedUrls) into a new custom hook (e.g.,
useMentionAttachments) that accepts input, allArtistFiles and getAccessToken and
returns { mentionAttachments, mentionTextContext, isLoadingSignedUrls }; move
helper logic around building results and error handling into that hook and keep
useVercelChat only responsible for chat flow (replace the local selectedFileIds,
setMentionAttachments, setMentionTextContext, setIsLoadingSignedUrls and the
long useEffect with a call to useMentionAttachments and update imports/usages
accordingly).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 419f9739-39c7-42a8-b1c8-22d952b9d43e

📥 Commits

Reviewing files that changed from the base of the PR and between e8f951a and 762c7ce.

📒 Files selected for processing (6)
  • components/VercelChat/FileMentionsInput.tsx
  • components/VercelChat/SuggestionItem.tsx
  • hooks/useArtistFilesForMentions.ts
  • hooks/useBatchSignedUrls.ts
  • hooks/useFileMentionSuggestions.ts
  • hooks/useVercelChat.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • hooks/useArtistFilesForMentions.ts

Comment thread hooks/useVercelChat.ts
}
return outputs;
}, [knowledgeFiles]);
}, [selectedFileIds, allArtistFiles, getAccessToken]);

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Does usePrivy getAccessToken return a stable function reference across renders?

💡 Result:

No, usePrivy getAccessToken does not return a stable function reference across renders. In standard React custom hooks like usePrivy, methods such as getAccessToken are typically recreated on each render unless explicitly memoized with useCallback, which official Privy documentation does not indicate. Official docs show usage as const { getAccessToken } = usePrivy; without stability guarantees, and general React behavior for hook returns supports new references per render (sources 1,2,5). To use stably, wrap in useCallback: const stableGetAccessToken = useCallback(getAccessToken, [getAccessToken]); but native return changes reference.

Citations:


🏁 Script executed:

find . -name "useVercelChat.ts" -type f

Repository: recoupable/chat

Length of output: 83


🏁 Script executed:

cat -n ./hooks/useVercelChat.ts | head -180 | tail -50

Repository: recoupable/chat

Length of output: 1959


🏁 Script executed:

cat -n ./hooks/useVercelChat.ts | head -130 | tail -50

Repository: recoupable/chat

Length of output: 2286


Fix unnecessary effect re-runs caused by unstable getAccessToken reference.

The effect that fetches file contents (lines 84-162) will re-run on every render because getAccessToken from Privy is recreated on each render without explicit memoization. This triggers unnecessary async file fetches. Wrap getAccessToken with useCallback in the parent component, or restructure the dependency to avoid passing the function reference directly. Consider extracting the token fetching logic outside the effect or using a ref to cache the last token to prevent excessive API calls on every render.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useVercelChat.ts` at line 162, The effect that fetches file contents
(the useEffect covering lines ~84-162) is re-running because the Privy
getAccessToken function reference is unstable; update the code so the effect no
longer depends on a non-memoized function reference: either wrap getAccessToken
with useCallback in the parent (so the getAccessToken reference passed into
useVercelChat is stable) or change the effect to fetch the token once outside
the effect (or store the token in a ref/state) and replace getAccessToken from
the dependency array with the stable token/ref; target the dependency list that
currently uses [selectedFileIds, allArtistFiles, getAccessToken] and ensure only
stable values remain so fetchFiles logic (inside the effect) does not run every
render.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="hooks/useMentionImagePreviews.ts">

<violation number="1" location="hooks/useMentionImagePreviews.ts:82">
P2: Including `previewUrls` in the dependency array while also calling `setPreviewUrls` inside the effect creates an unnecessary re-execution cycle after every batch fetch. Use a `useRef<Set<string>>` to track already-fetched paths instead, and remove `previewUrls` from the dependency array. This avoids the wasted re-run and the redundant `getAccessToken()` call it triggers.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Comment thread hooks/useMentionImagePreviews.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="components/VercelChat/SuggestionItem.tsx">

<violation number="1" location="components/VercelChat/SuggestionItem.tsx:16">
P2: Custom agent: **Code Structure and Size Limits for Readability and Single Responsibility**

File is 104 lines (limit: 100). Extract the image preview loading logic (cache maps, in-flight deduplication, and the `useEffect`) into a dedicated `useImagePreview(path, isImage)` hook to keep this component focused on rendering.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

Comment thread components/VercelChat/SuggestionItem.tsx

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

0 issues found across 2 files (changes from recent commits).

Requires human review: Auto-approval blocked by 1 unresolved issue from previous reviews.

@arpitgupta1214 arpitgupta1214 changed the title feat: move file list to dedicated api refactor: align file mentions with sandbox files flow Apr 13, 2026
@cubic-dev-ai

cubic-dev-ai Bot commented Apr 13, 2026

Copy link
Copy Markdown

You're iterating quickly on this pull request. To help protect your rate limits, cubic has paused automatic reviews on new pushes for now—when you're ready for another review, comment @cubic-dev-ai review.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🧹 Nitpick comments (2)
hooks/useSandboxFileContent.ts (1)

24-28: State initialization pattern works but may cause confusion.

The useState initializer on line 24-26 only captures the initial value of options?.path. If a caller later changes options.path, the internal selectedPath won't sync. This is technically fine because effectivePath (line 27) prioritizes options.path over selectedPath, making the controlled path always win.

However, this creates a subtle contract: callers using the select() method and callers passing options.path are effectively two different usage patterns that don't interact cleanly. Consider documenting this distinction or simplifying by removing the initial state assignment from options?.path since effectivePath handles the fallback anyway.

🔧 Optional: Simplify state initialization
-  const [selectedPath, setSelectedPath] = useState<string | undefined>(
-    options?.path,
-  );
+  const [selectedPath, setSelectedPath] = useState<string | undefined>();

This makes the two modes clearer: controlled via options.path OR uncontrolled via select().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useSandboxFileContent.ts` around lines 24 - 28, The state initializer
should not capture options?.path to avoid the mixed controlled/uncontrolled
contract: change the useState for selectedPath to start as undefined
(useState<string | undefined>(undefined)) and keep effectivePath = options?.path
?? selectedPath so callers either control via options.path or use select() which
calls setSelectedPath; update any references to selectedPath/setSelectedPath and
the select() method to rely on this uncontrolled initial state and adjust
docs/tests to state the two exclusive usage modes.
hooks/useArtistFilesForMentions.ts (1)

10-17: Consider documenting or removing is_directory.

The is_directory field is always false since the flatten logic explicitly filters out folders (line 25-27). If folder mentions are never intended, this field adds noise to the type. If they might be supported later, a brief comment would clarify the current constraint.

Not blocking—the implementation is correct.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useArtistFilesForMentions.ts` around lines 10 - 17, The MentionableFile
type includes is_directory but the flatten logic that filters out folders
ensures it's always false; either remove is_directory from the MentionableFile
type to avoid dead/noisy fields or keep it but add a brief comment on the type
(next to is_directory) stating that folder entries are filtered out by the
flatten logic and is_directory will always be false; update any consumers of
MentionableFile accordingly (search for MentionableFile and the flatten/filter
implementation that excludes directories).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/VercelChat/SuggestionItem.tsx`:
- Around line 19-25: Compute an explicit effectivePath (e.g., entry.path ||
entry.display || "") and use that when calling useSandboxFileContent instead of
passing entry.path directly; also make the enabled flag depend on both isImage
and Boolean(effectivePath) so the hook is only invoked when there is a real
path. This touches the isImage computation and the useSandboxFileContent
call—ensure you pass effectivePath to useSandboxFileContent({ path:
effectivePath, enabled: isImage && Boolean(effectivePath) }).

---

Nitpick comments:
In `@hooks/useArtistFilesForMentions.ts`:
- Around line 10-17: The MentionableFile type includes is_directory but the
flatten logic that filters out folders ensures it's always false; either remove
is_directory from the MentionableFile type to avoid dead/noisy fields or keep it
but add a brief comment on the type (next to is_directory) stating that folder
entries are filtered out by the flatten logic and is_directory will always be
false; update any consumers of MentionableFile accordingly (search for
MentionableFile and the flatten/filter implementation that excludes
directories).

In `@hooks/useSandboxFileContent.ts`:
- Around line 24-28: The state initializer should not capture options?.path to
avoid the mixed controlled/uncontrolled contract: change the useState for
selectedPath to start as undefined (useState<string | undefined>(undefined)) and
keep effectivePath = options?.path ?? selectedPath so callers either control via
options.path or use select() which calls setSelectedPath; update any references
to selectedPath/setSelectedPath and the select() method to rely on this
uncontrolled initial state and adjust docs/tests to state the two exclusive
usage modes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 146249f3-afb8-47c5-adb5-07a2092d36e5

📥 Commits

Reviewing files that changed from the base of the PR and between 762c7ce and dbdc342.

📒 Files selected for processing (6)
  • app/api/files/list/route.ts
  • components/VercelChat/SuggestionItem.tsx
  • hooks/useArtistFilesForMentions.ts
  • hooks/useBatchSignedUrls.ts
  • hooks/useFilesManager.ts
  • hooks/useSandboxFileContent.ts
💤 Files with no reviewable changes (3)
  • hooks/useBatchSignedUrls.ts
  • app/api/files/list/route.ts
  • hooks/useFilesManager.ts

Comment on lines +19 to +25
const isImage =
entry.mime_type?.startsWith("image/") ||
/\.(png|jpg|jpeg|gif|webp|svg)$/i.test(entry.path || entry.display || "");
const { imageUrl } = useSandboxFileContent({
path: entry.path,
enabled: isImage,
});

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.

⚠️ Potential issue | 🟡 Minor

Potential undefined path passed to hook.

The regex fallback on line 21 suggests entry.path might be undefined (entry.path || entry.display || ""), yet line 23 passes entry.path directly to the hook without a fallback. If path is undefined and isImage is true (matched via regex on display), the hook will receive undefined, causing the query to be disabled anyway (due to the Boolean(enabled && effectivePath) check in the hook).

This works but is fragile—the safety depends on the hook's internal guard rather than explicit intent here.

🛡️ Suggested defensive approach
-  const isImage =
-    entry.mime_type?.startsWith("image/") ||
-    /\.(png|jpg|jpeg|gif|webp|svg)$/i.test(entry.path || entry.display || "");
-  const { imageUrl } = useSandboxFileContent({
-    path: entry.path,
-    enabled: isImage,
-  });
+  const pathForContent = entry.path;
+  const isImage =
+    entry.mime_type?.startsWith("image/") ||
+    /\.(png|jpg|jpeg|gif|webp|svg)$/i.test(pathForContent || entry.display || "");
+  const { imageUrl } = useSandboxFileContent({
+    path: pathForContent,
+    enabled: isImage && Boolean(pathForContent),
+  });

This makes the enabled condition explicitly guard against undefined paths at the call site.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/VercelChat/SuggestionItem.tsx` around lines 19 - 25, Compute an
explicit effectivePath (e.g., entry.path || entry.display || "") and use that
when calling useSandboxFileContent instead of passing entry.path directly; also
make the enabled flag depend on both isImage and Boolean(effectivePath) so the
hook is only invoked when there is a real path. This touches the isImage
computation and the useSandboxFileContent call—ensure you pass effectivePath to
useSandboxFileContent({ path: effectivePath, enabled: isImage &&
Boolean(effectivePath) }).

Comment thread hooks/useArtistFilesForMentions.ts Outdated
import getMimeFromPath from "@/lib/files/getMimeFromPath";
import type { FileNode } from "@/lib/sandboxes/parseFileTree";

const WORKSPACE_ORGS_PREFIX = ".openclaw/workspace/orgs/";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

DRY principle

  • actual: this prefix is duplicated across /files page and file mentions in the chat.
  • required: shared const imported across both usages.

sweetmantech and others added 2 commits April 14, 2026 16:41
The .openclaw/workspace/orgs prefix was duplicated in
useArtistFilesForMentions (file-mention picker) and getSubtreeAtPath
(/files page filetree). Share a single const so both stay in lockstep
if the sandbox layout changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread lib/sandboxes/workspaceOrgsPath.ts Outdated
* stay in lockstep if the sandbox layout changes.
*/
export const WORKSPACE_ORGS_PATH = ".openclaw/workspace/orgs";
export const WORKSPACE_ORGS_PREFIX = `${WORKSPACE_ORGS_PATH}/`;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

DRY

  • actual: exporting WORKSPACE_ORGS_PATH and WORKSPACE_ORGS_PREFIX
  • required: only export WORKSPACE_ORGS_PATH. If trailing / is needed, update the caller to include it.

Drop the WORKSPACE_ORGS_PREFIX export. The only caller that needs the
trailing `/` constructs it locally, keeping the shared module minimal.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@hooks/useArtistFilesForMentions.ts`:
- Around line 34-42: The mention IDs currently use raw file paths in
useArtistFilesForMentions (id: path), which breaks the @[label](id) parser for
names with characters like ')'; change the id to an encoded opaque value (e.g.,
encodeURIComponent(path) or another reversible encoder) when building the file
objects in useArtistFilesForMentions, and update the mention resolver in
useVercelChat to decode the incoming mention id (e.g., decodeURIComponent from
the parser match) before looking up files so filenames like "Q1 (final).pdf"
resolve correctly.

In `@hooks/useVercelChat.ts`:
- Around line 108-146: The current flow eagerly fetches full contents via
getFileContents for every item in selected and may inline huge text or base64
blobs; update the logic in the selected.map / Promise.all block to first check
file size (use f.size if available or call a lightweight metadata
endpoint/getFileMetadata) and skip or switch to a server-side reference/summary
for files over a safe threshold (e.g., configurable MAX_INLINE_SIZE), only
calling getFileContents for files below that threshold; when skipping, ensure
setMentionAttachments and setMentionTextContext receive either a placeholder
reference (e.g., "[File omitted due to size: path]") or a server-side
URL/summary instead of inlining full content.
- Around line 91-98: The current block only aborts when no ids resolve; change
it to treat any partial resolution as failure by comparing selected.length
against selectedFileIds.length (i.e., if selected.length !==
selectedFileIds.length) and bail out in that case; preserve the existing
cancelled checks and the same cleanup calls to setMentionAttachments,
setMentionTextContext, and setIsLoadingSignedUrls, and ensure you derive
selection by resolving ids in mention order (use selectedFileIds order rather
than unordered Set filtering) so that any mismatch triggers the early return.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 038d2c09-1006-44de-a53b-64196780fd56

📥 Commits

Reviewing files that changed from the base of the PR and between dbdc342 and 5cf4ba4.

📒 Files selected for processing (5)
  • components/VercelChat/FileMentionsInput.tsx
  • hooks/useArtistFilesForMentions.ts
  • hooks/useVercelChat.ts
  • lib/sandboxes/getSubtreeAtPath.ts
  • lib/sandboxes/workspaceOrgsPath.ts
✅ Files skipped from review due to trivial changes (1)
  • lib/sandboxes/workspaceOrgsPath.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/VercelChat/FileMentionsInput.tsx

Comment on lines +34 to +42
return [
{
id: path,
file_name: fileName,
path,
mime_type: getMimeFromPath(path),
is_directory: false,
relative_path: relativePath,
},

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.

⚠️ Potential issue | 🟠 Major

Avoid raw paths as mention IDs.

id: path is not safe with the current @[label](id) parser in hooks/useVercelChat.ts; a filename like Q1 (final).pdf gets truncated at the first ), so the mention stops resolving. Use an encoded or opaque id here and decode it when resolving mentions.

Possible direction
-            id: path,
+            id: encodeURIComponent(path),
// hooks/useVercelChat.ts
if (match[1]) ids.add(decodeURIComponent(match[1]));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useArtistFilesForMentions.ts` around lines 34 - 42, The mention IDs
currently use raw file paths in useArtistFilesForMentions (id: path), which
breaks the @[label](id) parser for names with characters like ')'; change the id
to an encoded opaque value (e.g., encodeURIComponent(path) or another reversible
encoder) when building the file objects in useArtistFilesForMentions, and update
the mention resolver in useVercelChat to decode the incoming mention id (e.g.,
decodeURIComponent from the parser match) before looking up files so filenames
like "Q1 (final).pdf" resolve correctly.

Comment thread hooks/useVercelChat.ts
Comment on lines 91 to 98
const idSet = new Set(selectedFileIds);
const selected = allArtistFiles.filter((f) => idSet.has(f.id));
if (selected.length === 0) {
if (!cancelled) setKnowledgeFiles((prev) => (prev.length ? [] : prev));
if (!cancelled) setMentionAttachments((prev) => (prev.length ? [] : prev));
if (!cancelled) setMentionTextContext((prev) => (prev ? "" : prev));
if (!cancelled) setIsLoadingSignedUrls((prev) => (prev ? false : prev));
return;
}

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.

⚠️ Potential issue | 🟠 Major

Abort on partial mention resolution.

This only bails when zero ids resolve. If one mentioned file is stale or malformed, the hook still sends a partial file/text set, which makes the prompt diverge from what the composer shows. Resolve ids in mention order and treat any selected.length !== selectedFileIds.length case as a failure.

Suggested fix
-      const idSet = new Set(selectedFileIds);
-      const selected = allArtistFiles.filter((f) => idSet.has(f.id));
-      if (selected.length === 0) {
+      const filesById = new Map(allArtistFiles.map((file) => [file.id, file]));
+      const selected = selectedFileIds.flatMap((id) => {
+        const file = filesById.get(id);
+        return file ? [file] : [];
+      });
+      if (selected.length !== selectedFileIds.length) {
📝 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
const idSet = new Set(selectedFileIds);
const selected = allArtistFiles.filter((f) => idSet.has(f.id));
if (selected.length === 0) {
if (!cancelled) setKnowledgeFiles((prev) => (prev.length ? [] : prev));
if (!cancelled) setMentionAttachments((prev) => (prev.length ? [] : prev));
if (!cancelled) setMentionTextContext((prev) => (prev ? "" : prev));
if (!cancelled) setIsLoadingSignedUrls((prev) => (prev ? false : prev));
return;
}
const filesById = new Map(allArtistFiles.map((file) => [file.id, file]));
const selected = selectedFileIds.flatMap((id) => {
const file = filesById.get(id);
return file ? [file] : [];
});
if (selected.length !== selectedFileIds.length) {
if (!cancelled) setMentionAttachments((prev) => (prev.length ? [] : prev));
if (!cancelled) setMentionTextContext((prev) => (prev ? "" : prev));
if (!cancelled) setIsLoadingSignedUrls((prev) => (prev ? false : prev));
return;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useVercelChat.ts` around lines 91 - 98, The current block only aborts
when no ids resolve; change it to treat any partial resolution as failure by
comparing selected.length against selectedFileIds.length (i.e., if
selected.length !== selectedFileIds.length) and bail out in that case; preserve
the existing cancelled checks and the same cleanup calls to
setMentionAttachments, setMentionTextContext, and setIsLoadingSignedUrls, and
ensure you derive selection by resolving ids in mention order (use
selectedFileIds order rather than unordered Set filtering) so that any mismatch
triggers the early return.

Comment thread hooks/useVercelChat.ts
Comment on lines +108 to +146
const results = await Promise.all(
selected.map(async (f) => {
const fileResult = await getFileContents(accessToken, f.path);
const mimeType = f.mime_type || getMimeFromPath(f.path);
return {
path: f.path,
mimeType,
content: fileResult.content,
dataUrl: fileResult.imageUrl,
};
cache.set(f.storage_key, entry);
}),
);

// Compose final entries in the order of selection
const entries = selected
.map((f) => signedUrlCacheRef.current.get(f.storage_key))
.filter((e): e is KnowledgeBaseEntry => Boolean(e));
const nextAttachments: FileUIPart[] = [];
const textBlocks: string[] = [];
for (const result of results) {
if (
result.dataUrl &&
(result.mimeType === "application/pdf" ||
result.mimeType.startsWith("image/"))
) {
nextAttachments.push({
type: "file",
url: result.dataUrl,
mediaType: result.mimeType,
});
continue;
}

if (result.content) {
textBlocks.push(
`[Mentioned File: ${result.path}]\n${result.content}`,
);
}
}

if (!cancelled) setKnowledgeFiles(entries);
if (!cancelled) setMentionAttachments(nextAttachments);
if (!cancelled)
setMentionTextContext(textBlocks.length ? textBlocks.join("\n\n") : "");

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.

⚠️ Potential issue | 🟠 Major

Add size caps before inlining mentioned file contents.

This eagerly fetches every mentioned file and then inlines either the raw text or the full base64 data URL into state and the outgoing chat payload. Large text files will explode prompt size, and large PDFs/images will blow browser/request limits. Gate on file size before fetching, or switch this flow back to server-side references/summarization.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/useVercelChat.ts` around lines 108 - 146, The current flow eagerly
fetches full contents via getFileContents for every item in selected and may
inline huge text or base64 blobs; update the logic in the selected.map /
Promise.all block to first check file size (use f.size if available or call a
lightweight metadata endpoint/getFileMetadata) and skip or switch to a
server-side reference/summary for files over a safe threshold (e.g.,
configurable MAX_INLINE_SIZE), only calling getFileContents for files below that
threshold; when skipping, ensure setMentionAttachments and setMentionTextContext
receive either a placeholder reference (e.g., "[File omitted due to size:
path]") or a server-side URL/summary instead of inlining full content.

@arpitgupta1214 arpitgupta1214 merged commit 877d35a into test Apr 14, 2026
3 checks passed
arpitgupta1214 added a commit that referenced this pull request Apr 14, 2026
refactor: align file mentions with sandbox files flow (#1664)
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