Skip to content

All documents folder paths#2098

Merged
mfts merged 2 commits intomainfrom
cursor/all-documents-folder-paths-5418
Mar 11, 2026
Merged

All documents folder paths#2098
mfts merged 2 commits intomainfrom
cursor/all-documents-folder-paths-5418

Conversation

@mfts
Copy link
Owner

@mfts mfts commented Mar 5, 2026

Add folder names and their paths to the "All Documents" search results to improve search functionality.


Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features
    • Search results now include matching folders and show breadcrumb trails for matched folder paths when a search is active.
    • Document list shows folder results during filtered searches.
    • Pagination displays folder match counts (e.g., "1 folder" / "N folders") as extra info when search filters are active.

…crumbs

- API: search folders by name when query is present, build parent path
  breadcrumbs (folderList), exclude hidden folders and children of hidden
  folders, return matching folders alongside documents
- SWR hook: add FolderWithCountAndPath type, expose searchFolders from
  useDocuments hook
- Documents page: display search-matched folders when filtered instead
  of empty array, pass correct loading state for filtered folders
- Folder card: render path breadcrumb (Home > parent > folder) when
  search query is active, matching the existing document card pattern
- Pagination: add optional extraInfo prop showing folder match count

Co-authored-by: Marc Seitz <mfts@users.noreply.github.com>
@cursor
Copy link

cursor bot commented Mar 5, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@vercel
Copy link

vercel bot commented Mar 5, 2026

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

Project Deployment Actions Updated (UTC)
papermark Ready Ready Preview, Comment Mar 11, 2026 1:04am

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 5, 2026

Walkthrough

Adds folder path data and breadcrumb UI for search results: the API and hook now surface matching folders with ancestor paths, components accept the new folder shape, FolderCard renders breadcrumbs when searching, and Pagination can show an extraInfo string with folder counts.

Changes

Cohort / File(s) Summary
Folder search data & types
lib/swr/use-documents.ts, pages/api/teams/[teamId]/documents/index.ts
Introduces FolderWithCountAndPath (adds folderList: string[]), wires folders?: FolderWithCountAndPath[] into the useDocuments response as searchFolders, and API GET now returns matching folders with ancestor path resolution when a search query is present.
Documents list surface
components/documents/documents-list.tsx
Expands DocumentsList props to accept FolderWithCountAndPath[] in addition to existing folder shapes; no runtime control-flow changes aside from typing.
Folder card UI & props
components/documents/folder-card.tsx
Updates FolderCard props to accept FolderWithCountAndPath; computes folderList from folder data and conditionally renders a horizontal breadcrumb trail (icons, chevrons, links) when a search query is active.
Pagination UI
components/documents/pagination.tsx, pages/documents/index.tsx
Adds optional extraInfo?: string to PaginationProps and renders it when provided; pages/documents now passes extraInfo showing matching folder count when filtered and switches folder source to searchFolders while adjusting folders-loading logic.

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'All documents folder paths' directly aligns with the PR objective to add folder names and their paths to search results.

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


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
Contributor

@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.

🧹 Nitpick comments (3)
components/documents/folder-card.tsx (1)

71-74: Unused variable sortQuery.

The sortQuery variable is extracted from query params but never used in this component.

🧹 Remove unused variable
   const router = useRouter();
   const queryParams = router.query;
   const searchQuery = queryParams["search"];
-  const sortQuery = queryParams["sort"];
   const folderList = "folderList" in folder ? folder.folderList : undefined;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/documents/folder-card.tsx` around lines 71 - 74, The variable
sortQuery is extracted from router.query but not used; remove the unused
declaration to satisfy linting and reduce clutter by deleting the line that
assigns sortQuery (the const sortQuery = queryParams["sort"]; statement) in the
component where router.query and searchQuery are handled (leave router.query and
searchQuery intact and keep folderList logic as-is).
pages/api/teams/[teamId]/documents/index.ts (2)

281-307: N+1 query pattern for parent folder lookups.

Each matching folder triggers a separate query to fetch its parent path hierarchy. For searches returning many folders, this could impact performance.

Consider batching: collect all unique parent paths from all matching folders first, then fetch them in a single query, and map results back to each folder.

⚡ Batch parent folder lookups
+      // Collect all unique parent paths across all folders
+      const allParentPaths = new Set<string>();
+      for (const folder of folders) {
+        const parentPath = folder.path.substring(0, folder.path.lastIndexOf("/"));
+        if (parentPath) {
+          const pathSegments = parentPath.split("/").filter(Boolean);
+          for (let i = 0; i < pathSegments.length; i++) {
+            allParentPaths.add("/" + pathSegments.slice(0, i + 1).join("/"));
+          }
+        }
+      }
+
+      // Single query for all parent folders
+      const allParentFolders = allParentPaths.size > 0
+        ? await prisma.folder.findMany({
+            where: { teamId, path: { in: Array.from(allParentPaths) } },
+            select: { path: true, name: true },
+          })
+        : [];
+      const parentFolderMap = new Map(allParentFolders.map((f) => [f.path, f.name]));
+
+      // Build folderList for each folder using the map
       matchingFolders = folders.map((folder) => {
-        // ... async parent lookup
+        const folderNames: string[] = [];
+        const parentPath = folder.path.substring(0, folder.path.lastIndexOf("/"));
+        if (parentPath) {
+          const pathSegments = parentPath.split("/").filter(Boolean);
+          for (let i = 0; i < pathSegments.length; i++) {
+            const segmentPath = "/" + pathSegments.slice(0, i + 1).join("/");
+            const name = parentFolderMap.get(segmentPath);
+            if (name) folderNames.push(name);
+          }
+        }
+        return { ...folder, folderList: folderNames };
       });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pages/api/teams/`[teamId]/documents/index.ts around lines 281 - 307, The
current folders.map block causes an N+1 query pattern by calling
prisma.folder.findMany for each folder to fetch parentPaths; instead, gather all
unique parentPaths for every folder (use the same parentPath split logic to
produce parentPaths arrays and flatten+dedupe), run a single
prisma.folder.findMany({ where: { teamId, path: { in: allParentPaths } },
select: { path, name } }) to fetch all parent folders, then build a lookup map
from path to name and map those names back into each original folder to set
folderList (replace the per-folder prisma call inside the matchingFolders
computation with the batched query + mapping).

251-251: Avoid any[] type for better type safety.

Using any[] loses type information. Consider defining or reusing a proper type.

🛡️ Add explicit typing
-      let matchingFolders: any[] = [];
+      let matchingFolders: Array<{
+        id: string;
+        name: string;
+        path: string;
+        parentId: string | null;
+        _count: { documents: number; childFolders: number };
+        folderList: string[];
+        [key: string]: unknown;
+      }> = [];

Alternatively, import and extend FolderWithCount from the types if available server-side.

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

In `@pages/api/teams/`[teamId]/documents/index.ts at line 251, The variable
matchingFolders is typed as any[] which loses type safety; change it to a
concrete folder type by importing or defining the appropriate type (e.g.,
FolderWithCount or an interface matching the folder shape) and declare
matchingFolders with that type (for example: let matchingFolders:
FolderWithCount[] = [];), then update any downstream uses to respect the new
properties (or narrow types) and add/adjust imports for the type in the file; if
a server-side type isn’t available, create a local interface (e.g., interface
FolderWithCount { id: string; name: string; documentCount: number; /*...*/ })
and use that instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@components/documents/folder-card.tsx`:
- Around line 71-74: The variable sortQuery is extracted from router.query but
not used; remove the unused declaration to satisfy linting and reduce clutter by
deleting the line that assigns sortQuery (the const sortQuery =
queryParams["sort"]; statement) in the component where router.query and
searchQuery are handled (leave router.query and searchQuery intact and keep
folderList logic as-is).

In `@pages/api/teams/`[teamId]/documents/index.ts:
- Around line 281-307: The current folders.map block causes an N+1 query pattern
by calling prisma.folder.findMany for each folder to fetch parentPaths; instead,
gather all unique parentPaths for every folder (use the same parentPath split
logic to produce parentPaths arrays and flatten+dedupe), run a single
prisma.folder.findMany({ where: { teamId, path: { in: allParentPaths } },
select: { path, name } }) to fetch all parent folders, then build a lookup map
from path to name and map those names back into each original folder to set
folderList (replace the per-folder prisma call inside the matchingFolders
computation with the batched query + mapping).
- Line 251: The variable matchingFolders is typed as any[] which loses type
safety; change it to a concrete folder type by importing or defining the
appropriate type (e.g., FolderWithCount or an interface matching the folder
shape) and declare matchingFolders with that type (for example: let
matchingFolders: FolderWithCount[] = [];), then update any downstream uses to
respect the new properties (or narrow types) and add/adjust imports for the type
in the file; if a server-side type isn’t available, create a local interface
(e.g., interface FolderWithCount { id: string; name: string; documentCount:
number; /*...*/ }) and use that instead.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 86af3754-4d4f-4fdb-aec6-1dac011c85db

📥 Commits

Reviewing files that changed from the base of the PR and between 7e3dc31 and 1976b9f.

📒 Files selected for processing (6)
  • components/documents/documents-list.tsx
  • components/documents/folder-card.tsx
  • components/documents/pagination.tsx
  • lib/swr/use-documents.ts
  • pages/api/teams/[teamId]/documents/index.ts
  • pages/documents/index.tsx

Copy link
Contributor

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

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

Inline comments:
In `@pages/api/teams/`[teamId]/documents/index.ts:
- Around line 253-287: The folders query using prisma.folder.findMany (assigning
to folders) is unbounded and should be limited/paginated like the documents
query: add a limit (take) and optional pagination (skip or cursor) parameters
and return pagination metadata; update the prisma.folder.findMany call to accept
query params (e.g., take/skip or cursor) and ensure ordering is stable (orderBy:
{ name: "asc", id: "asc" } or similar) so pagination is deterministic, and
mirror the same page-size/default cap used by the documents listing.
- Around line 305-338: The parentFolders query and subsequent
parentFolderNameByPath map can include hidden ancestors, leaking them into
folderList; update the prisma.folder.findMany call that populates parentFolders
to only return non-hidden ancestors (add where: { teamId, path: { in:
Array.from(allParentPaths) }, hiddenInAllDocuments: false }) or alternatively
filter parentFolders before building parentFolderNameByPath so
parentFolderNameByPath only contains folders with hiddenInAllDocuments ===
false, then leave the folders.map logic unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3031ddea-e4a3-418e-9724-97820b3fa9a3

📥 Commits

Reviewing files that changed from the base of the PR and between 1976b9f and df576ea.

📒 Files selected for processing (1)
  • pages/api/teams/[teamId]/documents/index.ts

Comment on lines +253 to +287
const folders = await prisma.folder.findMany({
where: {
teamId,
hiddenInAllDocuments: false,
name: {
contains: query,
mode: "insensitive",
},
OR: [
{ parentId: null },
{
parentFolder: {
hiddenInAllDocuments: false,
},
},
],
},
include: {
_count: {
select: {
documents: {
where: {
hiddenInAllDocuments: false,
},
},
childFolders: {
where: {
hiddenInAllDocuments: false,
},
},
},
},
},
orderBy: { name: "asc" },
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Cap or paginate folder search results.

This query is unbounded, unlike the document query above. A broad term can return every matching folder plus all ancestor metadata in one request, which will dominate latency and payload size on large teams.

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

In `@pages/api/teams/`[teamId]/documents/index.ts around lines 253 - 287, The
folders query using prisma.folder.findMany (assigning to folders) is unbounded
and should be limited/paginated like the documents query: add a limit (take) and
optional pagination (skip or cursor) parameters and return pagination metadata;
update the prisma.folder.findMany call to accept query params (e.g., take/skip
or cursor) and ensure ordering is stable (orderBy: { name: "asc", id: "asc" } or
similar) so pagination is deterministic, and mirror the same page-size/default
cap used by the documents listing.

Comment on lines +305 to +338
const parentFolders = allParentPaths.size
? await prisma.folder.findMany({
where: {
teamId,
path: { in: Array.from(allParentPaths) },
},
select: { path: true, name: true },
})
: [];

const parentFolderNameByPath = new Map(
parentFolders.map((folder) => [folder.path, folder.name]),
);

matchingFolders = folders.map((folder) => {
const folderNames: string[] = [];
const parentPath = folder.path.substring(
0,
folder.path.lastIndexOf("/"),
);

if (parentPath) {
const pathSegments = parentPath.split("/").filter(Boolean);
for (let index = 0; index < pathSegments.length; index++) {
const path = `/${pathSegments.slice(0, index + 1).join("/")}`;
const parentFolderName = parentFolderNameByPath.get(path);

if (parentFolderName) {
folderNames.push(parentFolderName);
}
}
}

return { ...folder, folderList: folderNames };
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t leak hidden ancestor names into folderList.

parentFolders is fetched without hiddenInAllDocuments: false, so search results can expose hidden ancestor names in the breadcrumb. That breaks the All Documents visibility rules.

Proposed fix
         const parentFolders = allParentPaths.size
           ? await prisma.folder.findMany({
               where: {
                 teamId,
+                hiddenInAllDocuments: false,
                 path: { in: Array.from(allParentPaths) },
               },
               select: { path: true, name: true },
             })
           : [];
📝 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 parentFolders = allParentPaths.size
? await prisma.folder.findMany({
where: {
teamId,
path: { in: Array.from(allParentPaths) },
},
select: { path: true, name: true },
})
: [];
const parentFolderNameByPath = new Map(
parentFolders.map((folder) => [folder.path, folder.name]),
);
matchingFolders = folders.map((folder) => {
const folderNames: string[] = [];
const parentPath = folder.path.substring(
0,
folder.path.lastIndexOf("/"),
);
if (parentPath) {
const pathSegments = parentPath.split("/").filter(Boolean);
for (let index = 0; index < pathSegments.length; index++) {
const path = `/${pathSegments.slice(0, index + 1).join("/")}`;
const parentFolderName = parentFolderNameByPath.get(path);
if (parentFolderName) {
folderNames.push(parentFolderName);
}
}
}
return { ...folder, folderList: folderNames };
const parentFolders = allParentPaths.size
? await prisma.folder.findMany({
where: {
teamId,
hiddenInAllDocuments: false,
path: { in: Array.from(allParentPaths) },
},
select: { path: true, name: true },
})
: [];
const parentFolderNameByPath = new Map(
parentFolders.map((folder) => [folder.path, folder.name]),
);
matchingFolders = folders.map((folder) => {
const folderNames: string[] = [];
const parentPath = folder.path.substring(
0,
folder.path.lastIndexOf("/"),
);
if (parentPath) {
const pathSegments = parentPath.split("/").filter(Boolean);
for (let index = 0; index < pathSegments.length; index++) {
const path = `/${pathSegments.slice(0, index + 1).join("/")}`;
const parentFolderName = parentFolderNameByPath.get(path);
if (parentFolderName) {
folderNames.push(parentFolderName);
}
}
}
return { ...folder, folderList: folderNames };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pages/api/teams/`[teamId]/documents/index.ts around lines 305 - 338, The
parentFolders query and subsequent parentFolderNameByPath map can include hidden
ancestors, leaking them into folderList; update the prisma.folder.findMany call
that populates parentFolders to only return non-hidden ancestors (add where: {
teamId, path: { in: Array.from(allParentPaths) }, hiddenInAllDocuments: false })
or alternatively filter parentFolders before building parentFolderNameByPath so
parentFolderNameByPath only contains folders with hiddenInAllDocuments ===
false, then leave the folders.map logic unchanged.

@mfts mfts merged commit b8e7a22 into main Mar 11, 2026
10 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Mar 11, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants