Skip to content

Server-side pagination for the file list (#476)#481

Merged
NotYuSheng merged 2 commits into
mainfrom
feature/filelist-server-paging
Jul 8, 2026
Merged

Server-side pagination for the file list (#476)#481
NotYuSheng merged 2 commits into
mainfrom
feature/filelist-server-paging

Conversation

@NotYuSheng

@NotYuSheng NotYuSheng commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Closes #476 — the final piece (the drift-panel half landed in #480).

Problem

FileList fetched a hard-capped pageSize: 100 and rendered every row inside a fixed-height scroll box, so on the prod-scale target (millions of files) only the 100 most-recent uploads were ever reachable — truncation, not pagination.

Change (frontend-only)

The backend GET /files already returns a proper 1-indexed PagedResponse (page/pageSize/sort/source), so no backend change was needed:

  • Drive page/pageSize/totalItems/totalPages from the server response; reset to page 1 when page size or the monitor filter changes.
  • Monitor filter → server-side: the "hide monitor files" toggle now fetches ?source=ANALYSIS (ANALYSIS is the only non-monitor FileSource) instead of filtering a client array, so counts/pages stay authoritative. Removed the redundant client filter + maxHeight scroll box.
  • Mutations refetch: delete / delete-page / merge refetch the current page (stepping back a page if the last row on a non-first page is removed) rather than mutating a local array.
  • Selector stays reachable: pager shows whenever totalItems > 10 (smallest page-size option), so bumping the size to a single page can't strand the user — the same fix as Paginate the extracted-files table (#478) #479.
  • "Delete all" honesty: with no bulk-delete endpoint (and fetching all ids would reintroduce an unbounded fetch), it now deletes only the current page; the modal states the scope and how many files remain elsewhere. Relabelled trigger/modal to "Delete page".

Verification

  • tsc --noEmit clean; docker compose up -d --build succeeds.
  • Playwright (11 files: 2 ANALYSIS + 9 monitor), zero console errors:
    • Default (hide monitor): 2 rows, request …&source=ANALYSIS, no pager.
    • Toggle monitor on: 11 rows, "Showing 1 to 11 of 11", request drops source.
    • Page size → 10: 2 pages; page 2 shows the 11th row ("Showing 11 to 11 of 11"), request page=2&pageSize=10.
    • Confirmed the old pageSize:100 cap is gone.

With this, every truncating fetch identified in the pagination audit is resolved.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added page-based file browsing with pagination controls and page-size changes.
    • Improved compare/analysis selections so chosen files stay available across pages, including generated names.
  • Bug Fixes

    • File deletions now refresh the current page correctly and handle edge cases when removing the last item on a page.
    • The delete action now applies to the current page instead of all files.
    • Empty-state messaging and filter resets now behave more consistently.

FileList fetched a hard-capped `pageSize: 100` and rendered every row inside a
fixed-height scroll box, so uploads beyond 100 were silently invisible — the
last truncation case in #476. The backend GET /files already returns a proper
1-indexed PagedResponse (page/pageSize/sort/source), so this is a frontend fix.

- Drive page/pageSize/total(Items|Pages) from the server response; reset to
  page 1 when the page size or monitor filter changes.
- Map the "hide monitor files" toggle to `?source=ANALYSIS` (ANALYSIS is the only
  non-monitor source) instead of filtering a client-side array, so counts and
  pages stay authoritative. Removed the now-redundant client filter + scroll box.
- Refetch the current page after delete / delete-page / merge (stepping back a
  page if the last row on a non-first page is removed) rather than mutating a
  local array.
- Show the pager (with page-size selector) whenever the total exceeds the
  smallest page-size option, so bumping the size to one page can't strand the
  user — same fix as #479.
- "Delete all" now honestly deletes only the current page (there is no bulk
  endpoint, and fetching all ids would reintroduce an unbounded fetch); the
  modal states the scope and how many files remain on other pages. Relabelled
  the trigger/modal to "Delete page" to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

FileList.tsx was updated to implement server-side pagination, replacing a single bulk fetch with paginated requests tracking page, pageSize, totalItems, and totalPages. Compare selection now persists filenames across pages, and delete operations (single-file and page-level) were changed to refetch data rather than mutate local state.

Changes

FileList Pagination and Deletion

Layer / File(s) Summary
Pagination and selection state setup
frontend/src/components/upload/FileList/FileList.tsx
Imports useCallback and shared Pagination component; adds state for page, pageSize, totalItems, totalPages, and selectedFileNames.
Paginated fetch and filter reset
frontend/src/components/upload/FileList/FileList.tsx
fetchFiles useCallback requests paged data with optional source: 'ANALYSIS' filter; refetches on change; toggling hideMonitor resets page to 1.
Cross-page compare selection
frontend/src/components/upload/FileList/FileList.tsx
toggleCompareSelect and handleRowClick persist filenames via selectedFileNames so buildAutoMergeName works across pages; selection cleared after merge.
Single-file deletion refetch
frontend/src/components/upload/FileList/FileList.tsx
Deletion treats 404 as success, clears pending state, and steps back a page or refetches instead of mutating local list.
Delete-page action and confirmation
frontend/src/components/upload/FileList/FileList.tsx
"Delete all" renamed to "Delete Page"; modal text updated; handler deletes only current page's files via Promise.allSettled.
Empty state and pagination controls
frontend/src/components/upload/FileList/FileList.tsx
Empty-state messaging depends on hideMonitor; pagination UI renders when totalItems > 10 and resets page on size change.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant FileList
  participant API

  User->>FileList: toggle hideMonitor filter
  FileList->>FileList: reset page to 1
  FileList->>API: fetchFiles(page, pageSize, source)
  API-->>FileList: files, totalItems, totalPages
  FileList-->>User: render paged file list

  User->>FileList: confirm "Delete Page"
  FileList->>API: delete each file in current page (allSettled)
  API-->>FileList: deletion results
  FileList->>FileList: step back page or refetch current page
  FileList->>API: fetchFiles(page, pageSize, source)
  API-->>FileList: updated files, totals
  FileList-->>User: render updated page
Loading

Possibly related issues

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR updates FileList pagination, but it doesn't address the linked issue's drift-panel truncation fixes or repo-wide hard-cap sweep. Also update the drift panels to avoid capped conversation fetches and verify the repo-wide hard-cap sweep required by #476.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: server-side pagination for the file list.
Out of Scope Changes check ✅ Passed The described changes stay focused on FileList pagination and related deletion behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces server-side pagination to the FileList component, updating file fetching, deletion, and filtering logic to support paginated results. Feedback highlights critical issues with this transition: first, using a Set of IDs for selected files breaks features like auto-merge name generation and monitor file deselection because the files array now only holds the current page's data; second, a useEffect hook resetting the page causes redundant API requests and race conditions, which should be resolved by batching state updates in event handlers; and finally, page deletion logic should only step back a page if the deleted page was the last page.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread frontend/src/components/upload/FileList/FileList.tsx
Comment thread frontend/src/components/upload/FileList/FileList.tsx Outdated
Comment thread frontend/src/components/upload/FileList/FileList.tsx Outdated
Comment thread frontend/src/components/upload/FileList/FileList.tsx Outdated
- Drop the setPage(1) effect and reset the page directly in the monitor toggle
  and onPageSizeChange, so a filter/size change fires a single page-1 request
  instead of a stale-page fetch followed by a second reset fetch.
- buildAutoMergeName now reads an accumulated id→filename map recorded at
  selection time, not `files` (current page only), so off-page selections still
  contribute to the merged name. Cleared with the selection after a merge.
- Both delete paths step back a page only when the emptied page was the last
  one (page === totalPages); earlier pages backfill from below, so refetch
  in place.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

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

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/components/upload/FileList/FileList.tsx (1)

257-271: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Move setPage/setSelectedForCompare out of the setHideMonitor updater.

The function passed to setHideMonitor should be a pure state derivation, but it triggers setPage(1) and setSelectedForCompare(...) as side effects. Under React 19 Strict Mode, updater functions are intentionally double-invoked, so these nested setters run twice. They happen to be idempotent here, but this is a fragile anti-pattern — compute next and dispatch the setters from the handler body instead.

Also note the monitor cleanup filters only the current-page files, so monitor selections from other pages remain in selectedForCompare once server-side paging is active.

♻️ Proposed refactor
-                onClick={() => setHideMonitor(v => {
-                  const next = !v;
-                  // New filter → back to page 1 so the current page can't fall out of range.
-                  setPage(1);
-                  if (next) {
-                    setSelectedForCompare(prev => {
-                      const monitorIds = new Set(files.filter(f => f.source === 'MONITOR').map(f => f.fileId));
-                      if ([...prev].every(id => !monitorIds.has(id))) return prev;
-                      const next = new Set(prev);
-                      monitorIds.forEach(id => next.delete(id));
-                      return next;
-                    });
-                  }
-                  return next;
-                })}
+                onClick={() => {
+                  const next = !hideMonitor;
+                  setHideMonitor(next);
+                  // New filter → back to page 1 so the current page can't fall out of range.
+                  setPage(1);
+                  if (next) {
+                    setSelectedForCompare(prev => {
+                      const monitorIds = new Set(files.filter(f => f.source === 'MONITOR').map(f => f.fileId));
+                      if ([...prev].every(id => !monitorIds.has(id))) return prev;
+                      const updated = new Set(prev);
+                      monitorIds.forEach(id => updated.delete(id));
+                      return updated;
+                    });
+                  }
+                }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/upload/FileList/FileList.tsx` around lines 257 - 271,
The `setHideMonitor` updater in `FileList` is doing side effects by calling
`setPage(1)` and `setSelectedForCompare(...)`, which should be moved out of the
state updater and into the click handler body. Compute the next hide/show value
first, then dispatch `setHideMonitor`, `setPage`, and the compare-selection
cleanup separately so the updater stays pure and safe under React 19 Strict
Mode. While refactoring, make the monitor selection cleanup use the full
monitor-file set rather than only the current-page `files`, so
`selectedForCompare` stays consistent with server-side paging.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@frontend/src/components/upload/FileList/FileList.tsx`:
- Around line 257-271: The `setHideMonitor` updater in `FileList` is doing side
effects by calling `setPage(1)` and `setSelectedForCompare(...)`, which should
be moved out of the state updater and into the click handler body. Compute the
next hide/show value first, then dispatch `setHideMonitor`, `setPage`, and the
compare-selection cleanup separately so the updater stays pure and safe under
React 19 Strict Mode. While refactoring, make the monitor selection cleanup use
the full monitor-file set rather than only the current-page `files`, so
`selectedForCompare` stays consistent with server-side paging.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 13ce348c-81b0-452f-bf1d-380d6bd8c055

📥 Commits

Reviewing files that changed from the base of the PR and between d93f853 and e8034ab.

📒 Files selected for processing (1)
  • frontend/src/components/upload/FileList/FileList.tsx

@NotYuSheng NotYuSheng merged commit 1e0b034 into main Jul 8, 2026
3 checks passed
@NotYuSheng NotYuSheng deleted the feature/filelist-server-paging branch July 8, 2026 00:55
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.

Frontend fetches silently truncate data via hard-coded pageSize caps

1 participant