Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

MODSetter
Copy link
Owner

@MODSetter MODSetter commented Oct 22, 2025

Description

feat: fixed chat Documents table

Screenshots

API Changes

  • This PR includes API changes

Change Type

  • Bug fix
  • New feature
  • Performance improvement
  • Refactoring
  • Documentation
  • Dependency/Build system
  • Breaking change
  • Other (specify):

Testing Performed

  • Tested locally
  • Manual/QA verification

Checklist

  • Follows project coding standards and conventions
  • Documentation updated as needed
  • Dependencies updated as needed
  • No lint/build errors or new warnings
  • All relevant tests are passing

High-level PR Summary

This PR refactors the chat Documents table to implement server-side pagination, search, and filtering instead of client-side operations. The changes move data fetching responsibility from the parent ChatInputGroup component to the DocumentsDataTable component itself, replacing eager loading with lazy loading. The refactor includes a new multi-select filter UI using a popover with checkboxes, debounced search functionality, and proper state management to track selected documents across paginated pages.

⏱️ Estimated Review Time: 30-90 minutes

💡 Review Order Suggestion
Order File Path
1 surfsense_web/hooks/use-documents.ts
2 surfsense_web/components/chat/DocumentsDataTable.tsx
3 surfsense_web/components/chat/ChatInputGroup.tsx

Need help? Join our Discord

Analyze latest changes

Summary by CodeRabbit

  • New Features

    • Introduced server-side pagination and search for efficient document browsing
    • Added type-based filtering with dynamic count display of available document types
  • Improvements

    • Streamlined document selector workflow with simplified dialog interactions
    • Enhanced document selection tracking and persistence across pagination

@vercel
Copy link

vercel bot commented Oct 22, 2025

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

Project Deployment Preview Comments Updated (UTC)
surf-sense-frontend Ready Ready Preview Comment Oct 22, 2025 5:57am

@coderabbitai
Copy link

coderabbitai bot commented Oct 22, 2025

Walkthrough

The document selection flow in the chat component has been refactored. ChatInputGroup simplifies the DocumentSelector by removing lazy-load document fetching logic and reducing it to state toggle behavior. DocumentsDataTable now fetches documents server-side using a searchSpaceId prop instead of receiving a pre-loaded documents array, implementing pagination, debounced search, and document-type filtering.

Changes

Cohort / File(s) Summary
ChatInputGroup simplification
surfsense_web/components/chat/ChatInputGroup.tsx
Removed lazy-load document fetch flow and loading/isLoaded state tracking from DocumentSelector. OpenChange handler now only toggles dialog open state. Eliminated loading skeleton and conditional DocumentsDataTable rendering. Made Document import type-only. Preserved selection callbacks delegated to parent.
DocumentsDataTable server-side refactor
surfsense_web/components/chat/DocumentsDataTable.tsx
Changed prop signature from documents: Document[] to searchSpaceId: number. Shifted data fetching, pagination (page, pageSize), and filtering (documentTypeFilter, debounced search) to server-side via useDocuments hook. Reworked selection tracking with selectedDocumentsMap for cross-page persistence. Replaced static type selector with Popover-based Type filter showing dynamic counts. Redesigned pagination controls with Previous/Next buttons and Select-based page size. Added debounced search utility and loading state UI.

Sequence Diagram

sequenceDiagram
    participant User
    participant ChatInputGroup
    participant DocumentsDataTable
    participant useDocuments Hook
    participant Backend

    User->>ChatInputGroup: Opens document selector
    ChatInputGroup->>DocumentsDataTable: Renders (passes searchSpaceId)
    
    DocumentsDataTable->>DocumentsDataTable: Initialize: pageIndex=0, search="", filters={}
    DocumentsDataTable->>useDocuments Hook: Fetch with searchSpaceId, page, search, filters
    useDocuments Hook->>Backend: Request documents + type counts
    Backend-->>useDocuments Hook: Return paginated data + counts
    useDocuments Hook-->>DocumentsDataTable: Load state complete
    DocumentsDataTable->>DocumentsDataTable: Render table + type filter options
    
    User->>DocumentsDataTable: Type in search (debounced)
    DocumentsDataTable->>DocumentsDataTable: Trigger debounced effect
    DocumentsDataTable->>useDocuments Hook: Re-fetch with new search query
    useDocuments Hook->>Backend: Request filtered documents
    Backend-->>useDocuments Hook: Return filtered results
    DocumentsDataTable->>DocumentsDataTable: Update table, persist selection in map
    
    User->>DocumentsDataTable: Select document
    DocumentsDataTable->>DocumentsDataTable: Update selectedDocumentsMap
    DocumentsDataTable->>ChatInputGroup: Notify onSelectionChange (parent)
    
    User->>DocumentsDataTable: Navigate to next page
    DocumentsDataTable->>DocumentsDataTable: Increment pageIndex
    DocumentsDataTable->>useDocuments Hook: Fetch next page
    useDocuments Hook->>Backend: Request page N
    Backend-->>useDocuments Hook: Return page data
    DocumentsDataTable->>DocumentsDataTable: Render new page, restore prior selections
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

The DocumentsDataTable component undergoes substantial refactoring with multiple interacting systems: server-side pagination and filtering logic, debounced search integration, selection persistence across pages via a new map-based structure, and significant UI restructuring (Popover-based type filters, redesigned pagination controls). The shift from client-side to server-side data flow requires careful validation of state management and API contract changes. ChatInputGroup's changes are simpler but must be reviewed in context of the prop signature change to DocumentsDataTable.

Possibly related PRs

Poem

🐰 The old documents flew in with a hop,
Now they trickle from the server, page by page, non-stop!
Debounce and filter, type by type with care,
Selection maps persist through pages, everywhere!

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "feat: fixed chat Documents table" directly addresses the primary focus of the changeset. The modifications are entirely concentrated on refactoring and improving the DocumentsDataTable component, with significant architectural changes including server-side pagination, debounced search, and restructured selection logic. The title is concise, specific to the chat Documents table component, and clearly communicates that the main change involves fixing or improving this table. A teammate reviewing the PR history would immediately understand the scope of the changes based on this title.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

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.

@MODSetter MODSetter merged commit 70808eb into main Oct 22, 2025
7 of 9 checks passed
Copy link

@recurseml recurseml bot left a comment

Choose a reason for hiding this comment

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

Review by RecurseML

🔍 Review performed on 6750c47..c4b9ce6

✨ No bugs found, your code is sparkling clean

✅ Files analyzed, no issues (2)

surfsense_web/components/chat/ChatInputGroup.tsx
surfsense_web/components/chat/DocumentsDataTable.tsx

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
surfsense_web/components/chat/DocumentsDataTable.tsx (1)

547-575: Fix zero‑results pagination text (“1 to 0 of 0”).

When total === 0 you display 1→0. Compute safe start/end and clamp pageCount.

Apply this diff:

- pageCount: Math.ceil(total / pageSize),
+ const pageCount = Math.max(1, Math.ceil(total / pageSize));
+ // …
+ pageCount,
- <div className="text-center sm:text-left">
-   Showing {pageIndex * pageSize + 1} to {Math.min((pageIndex + 1) * pageSize, total)} of {total} documents
- </div>
+ const shownStart = total === 0 ? 0 : pageIndex * pageSize + 1;
+ const shownEnd = total === 0 ? 0 : Math.min((pageIndex + 1) * pageSize, total);
+ // …
+ <div className="text-center sm:text-left">
+   Showing {shownStart} to {shownEnd} of {total} documents
+ </div>

Also applies to: 327-329

🧹 Nitpick comments (4)
surfsense_web/components/chat/DocumentsDataTable.tsx (4)

42-49: Consider React’s useDeferredValue instead of a custom debounce.

For keystroke‑heavy search UIs, useDeferredValue avoids re‑renders without timers. If you need a fixed delay, keep your hook; otherwise:

- function useDebounced<T>(value: T, delay = 300) {
-   const [debounced, setDebounced] = useState(value);
-   useEffect(() => {
-     const t = setTimeout(() => setDebounced(value), delay);
-     return () => clearTimeout(t);
-   }, [value, delay]);
-   return debounced;
- }
+ // import { useDeferredValue } from "react";
+ // const deferredSearch = useDeferredValue(search);

Then switch debouncedSearch to deferredSearch.


372-377: Minor: use input type="search" for better UX.

Enables native clear buttons and semantics.

- <Input
+ <Input
+   type="search"
    placeholder="Search documents..."

398-407: Sanitize HTML id/for to avoid invalid characters.

Document types may contain spaces/symbols. Create a safe id slug.

- <Checkbox
-   id={`type-${type}`}
+ {(() => {
+   const safe = `type-${type.replace(/[^a-zA-Z0-9_-]/g, "")}`;
+   return (
+     <Checkbox
+       id={safe}
        checked={documentTypeFilter.includes(type)}
        onCheckedChange={(checked) => handleToggleType(type, !!checked)}
-     />
- )()}
+     />
+   );
+ })()}
- <Label htmlFor={`type-${type}`} …
+ <Label htmlFor={safe} …

188-194: Optional: prevent out‑of‑order fetch updates on rapid input.

Debounce helps, but concurrent fetches can still resolve out of order. Track a request id or use AbortController in useDocuments and ignore stale responses.

I can propose a small patch to use-documents.ts adding an AbortController per call if you want.

Also applies to: 202-229

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6750c47 and c4b9ce6.

📒 Files selected for processing (2)
  • surfsense_web/components/chat/ChatInputGroup.tsx (3 hunks)
  • surfsense_web/components/chat/DocumentsDataTable.tsx (6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.rules/require_unique_id_props.mdc)

**/*.{jsx,tsx}: When mapping arrays to React elements in JSX/TSX, each rendered element must include a unique key prop
Keys used for React list items should be stable, predictable, and unique among siblings

Files:

  • surfsense_web/components/chat/ChatInputGroup.tsx
  • surfsense_web/components/chat/DocumentsDataTable.tsx
🧬 Code graph analysis (2)
surfsense_web/components/chat/ChatInputGroup.tsx (1)
surfsense_web/components/chat/DocumentsDataTable.tsx (1)
  • DocumentsDataTable (174-579)
surfsense_web/components/chat/DocumentsDataTable.tsx (1)
surfsense_web/hooks/use-documents.ts (2)
  • Document (6-14)
  • useDocuments (42-265)
🔇 Additional comments (4)
surfsense_web/components/chat/ChatInputGroup.tsx (2)

28-28: Type-only import is correct and preferred here.

Reduces bundle surface and avoids runtime import. Nice.


43-45: Open-state handler simplification LGTM.

Removing fetch side-effects from open/close improves separation of concerns; fetching now lives in DocumentsDataTable.

surfsense_web/components/chat/DocumentsDataTable.tsx (2)

501-527: List keys look good and stable.

  • Header groups: key={headerGroup.id}
  • Headers: key={header.id}
  • Rows: key={row.id}
  • Cells: key={cell.id}
  • Type filter list: key={type}

Also applies to: 521-525, 398-413


35-40: Prop contract change verified at all call sites.

The only invocation of <DocumentsDataTable> at ChatInputGroup.tsx:83-88 correctly passes searchSpaceId={Number(search_space_id)} along with all other required props (onSelectionChange, onDone, initialSelectedDocuments). No usages with the old documents prop pattern remain anywhere in the codebase.

Comment on lines +83 to +88
<DocumentsDataTable
searchSpaceId={Number(search_space_id)}
onSelectionChange={handleSelectionChange}
onDone={handleDone}
initialSelectedDocuments={selectedDocuments}
/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Guard against invalid searchSpaceId (NaN) to avoid silent no‑fetch state.

Number(search_space_id) can be NaN; useDocuments then won’t fetch, leaving the dialog empty. Add a parse/guard and disable the trigger or render a message when invalid.

Apply this diff:

- <DocumentsDataTable
-   searchSpaceId={Number(search_space_id)}
+ const sid = Number.parseInt(String(search_space_id ?? ''), 10);
+ const hasValidSid = Number.isFinite(sid) && sid > 0;
+ // …
+ {hasValidSid ? (
+   <DocumentsDataTable
+     searchSpaceId={sid}
      onSelectionChange={handleSelectionChange}
      onDone={handleDone}
      initialSelectedDocuments={selectedDocuments}
-   />
+   />
+ ) : (
+   <div className="text-sm text-muted-foreground">Invalid or missing search space.</div>
+ )}

Run to check other call sites also pass a valid number:


🏁 Script executed:

#!/bin/bash
rg -nP --type=ts --type=tsx -C2 '<DocumentsDataTable\b' | sed -n '1,200p'

Length of output: 27


I'll fix the ripgrep command to properly search for DocumentsDataTable usage in the codebase.


---

🏁 Script executed:

```shell
#!/bin/bash
# Search for DocumentsDataTable usage without strict type filtering
rg -nP 'DocumentsDataTable' --type-add 'web:*.{ts,tsx,js,jsx}' --type web -C3 | head -100

Length of output: 3023


🏁 Script executed:

#!/bin/bash
# Alternative: search using glob patterns directly
rg -nP 'DocumentsDataTable' -g '*.tsx' -g '*.ts' -C3 | head -100

Length of output: 3023


🏁 Script executed:

#!/bin/bash
# Also find the DocumentsDataTable component definition
rg -nP 'export.*DocumentsDataTable|function DocumentsDataTable|const DocumentsDataTable' -g '*.tsx' -g '*.ts'

Length of output: 93


🏁 Script executed:

#!/bin/bash
# Search for search_space_id usage in ChatInputGroup
fd ChatInputGroup -e tsx -e ts -x cat {} \;

Length of output: 17613


🏁 Script executed:

#!/bin/bash
# Check if useDocuments hook and how it handles invalid IDs
rg -nP 'useDocuments|searchSpaceId' -g '*.tsx' -g '*.ts' -A2 -B2 | head -150

Length of output: 9599



---

🏁 Script executed:

```shell
#!/bin/bash
# Get the full useDocuments hook implementation
cat surfsense_web/hooks/use-documents.ts

Length of output: 7788


🏁 Script executed:

#!/bin/bash
# Get the full DocumentsDataTable implementation to see error handling
cat surfsense_web/components/chat/DocumentsDataTable.tsx

Length of output: 17730


🏁 Script executed:

#!/bin/bash
# Check if search_space_id can be undefined from useParams
rg -nP 'useParams' -g '*.tsx' -g '*.ts' -A5 | head -50

Length of output: 4578


🏁 Script executed:

#!/bin/bash
# Search for any existing NaN or isFinite guards in the codebase
rg -nP 'Number\.isFinite|isNaN|Number\.parseInt' -g '*.tsx' -g '*.ts'

Length of output: 1095


Guard against invalid searchSpaceId (NaN) to prevent silent loading state.

The concern is valid: Number(search_space_id) can be NaN if search_space_id is undefined from useParams(). The useDocuments hook checks if (!lazy && searchSpaceId) at line 113, and since NaN is falsy, the fetch never occurs. The UI then shows either an indefinite loading spinner or silently displays "No documents found" without indicating the root cause.

One call site confirmed (ChatInputGroup.tsx:84), and the codebase already uses Number.isNaN() guards elsewhere for similar validation (e.g., connectors/edit pages).

The proposed fix adds validation at the call site with conditional rendering. Alternatively, consider adding the guard inside DocumentsDataTable itself (lines 210-221) to catch the issue closer to where it manifests, or within the useDocuments hook to handle it universally.

🤖 Prompt for AI Agents
In surfsense_web/components/chat/ChatInputGroup.tsx around lines 83 to 88,
Number(search_space_id) can be NaN when search_space_id is undefined from
useParams(), causing useDocuments to skip fetching and leave the UI in a silent
loading/empty state; update the call site to validate the param first (e.g.,
parseInt/Number then check Number.isNaN or ensure search_space_id != null and
/^\d+$/.test(search_space_id)) and only render <DocumentsDataTable ... /> when
searchSpaceId is a valid number, otherwise render an appropriate fallback or
early return; alternatively, if you prefer a centralized fix, add the NaN guard
inside DocumentsDataTable (or inside useDocuments) to treat NaN as invalid and
surface an error/empty state instead of silently skipping the fetch.

Comment on lines 319 to 329
const table = useReactTable({
data: filteredDocuments,
data: documents || [],
columns,
getRowId: (row) => row.id.toString(),
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
initialState: { pagination: { pageSize: 10 } },
state: { sorting, columnFilters, columnVisibility, rowSelection },
manualPagination: true,
pageCount: Math.ceil(total / pageSize),
state: { sorting, rowSelection, pagination: { pageIndex, pageSize } },
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Sorting UI is currently a no‑op. Wire client sorting or remove toggles.

You track sorting but don’t apply it (no getSortedRowModel, no server sort params). This confuses users.

Two options; pick one:

  • Client per‑page sorting (quick fix):
- import {
-   type ColumnDef, flexRender, getCoreRowModel, type SortingState, useReactTable,
- } from "@tanstack/react-table";
+ import {
+   type ColumnDef, flexRender, getCoreRowModel, getSortedRowModel, type SortingState, useReactTable,
+ } from "@tanstack/react-table";

  const table = useReactTable({
    data: documents || [],
    columns,
    getRowId: (row) => row.id.toString(),
    onSortingChange: setSorting,
    getCoreRowModel: getCoreRowModel(),
+   getSortedRowModel: getSortedRowModel(),
    onRowSelectionChange: setRowSelection,
    manualPagination: true,
-   pageCount: Math.ceil(total / pageSize),
-   state: { sorting, rowSelection, pagination: { pageIndex, pageSize } },
+   pageCount,
+   state: { sorting, rowSelection, pagination: { pageIndex, pageSize } },
  });
  • Or implement server sorting: include sorting in deps and pass to useDocuments (API needs sort field and direction). Until then, consider disabling column sorting to avoid misleading UI.

Also applies to: 202-229

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant