From 88cbf3a0c47d3cc044d504c44560a0b28d7e47d4 Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Tue, 7 Apr 2026 11:13:15 -0700 Subject: [PATCH] Fix "Showing 1-0 of 0" in TUI, cache unfiltered total count through search reset --- src/components/ResourcePicker.tsx | 18 ++++++---- src/hooks/useCursorPagination.ts | 48 +++++++++++++++++++------- src/screens/BenchmarkJobListScreen.tsx | 18 ++++++---- src/screens/BenchmarkListScreen.tsx | 18 ++++++---- src/screens/BenchmarkRunListScreen.tsx | 18 ++++++---- src/screens/ScenarioRunListScreen.tsx | 18 ++++++---- 6 files changed, 90 insertions(+), 48 deletions(-) diff --git a/src/components/ResourcePicker.tsx b/src/components/ResourcePicker.tsx index 79221d8e..5d23759f 100644 --- a/src/components/ResourcePicker.tsx +++ b/src/components/ResourcePicker.tsx @@ -461,13 +461,17 @@ export function ResourcePicker({ )} )} - - {" "} - •{" "} - - - Showing {startIndex + 1}-{endIndex} of {totalCount} - + {endIndex > startIndex && ( + <> + + {" "} + •{" "} + + + Showing {startIndex + 1}-{endIndex} of {totalCount} + + + )} {/* Help Bar */} diff --git a/src/hooks/useCursorPagination.ts b/src/hooks/useCursorPagination.ts index 52528686..f509a29f 100644 --- a/src/hooks/useCursorPagination.ts +++ b/src/hooks/useCursorPagination.ts @@ -111,6 +111,11 @@ export function useCursorPagination( // Abort controller for cancelling in-flight count requests const countAbortRef = React.useRef(null); + // Cache the unfiltered (initial deps) total count to avoid re-fetching on filter clear + const initialDepsKeyRef = React.useRef(JSON.stringify(deps)); + const baseTotalCountRef = React.useRef(null); + const depsKeyRef = React.useRef(JSON.stringify(deps)); + // Cursor history: cursorHistory[N] = last item ID of page N // Used to determine startingAt for page N+1 const cursorHistoryRef = React.useRef<(string | undefined)[]>([]); @@ -206,8 +211,13 @@ export function useCursorPagination( // Cancel the background count request if still pending. if (!result.hasMore && !hasCachedTotalCountRef.current) { countAbortRef.current?.abort(); - setTotalCount(page * pageSizeRef.current + result.items.length); + const computedTotal = + page * pageSizeRef.current + result.items.length; + setTotalCount(computedTotal); hasCachedTotalCountRef.current = true; + if (depsKeyRef.current === initialDepsKeyRef.current) { + baseTotalCountRef.current = computedTotal; + } } } catch (err) { if (!isMountedRef.current) return; @@ -226,6 +236,7 @@ export function useCursorPagination( // Reset when deps change (e.g., filters, search) const depsKey = JSON.stringify(deps); React.useEffect(() => { + depsKeyRef.current = depsKey; // Clear cursor history when deps change cursorHistoryRef.current = []; hasCachedTotalCountRef.current = false; @@ -243,18 +254,29 @@ export function useCursorPagination( // Data fetch fetchPageData(0, true); - // Background count fetch — fires immediately alongside data - fetchPageRef - .current({ limit: 0, startingAt: undefined, includeTotalCount: true }) - .then((result) => { - if (cancelled || countAbort.signal.aborted || !isMountedRef.current) - return; - if (result.totalCount !== undefined) { - setTotalCount(result.totalCount); - hasCachedTotalCountRef.current = true; - } - }) - .catch(() => {}); // count failure is non-critical + // If returning to unfiltered state and we have a cached base count, reuse it + const isUnfiltered = depsKey === initialDepsKeyRef.current; + if (isUnfiltered && baseTotalCountRef.current !== null) { + setTotalCount(baseTotalCountRef.current); + hasCachedTotalCountRef.current = true; + } else { + // Background count fetch — fires immediately alongside data + fetchPageRef + .current({ limit: 0, startingAt: undefined, includeTotalCount: true }) + .then((result) => { + if (cancelled || countAbort.signal.aborted || !isMountedRef.current) + return; + if (result.totalCount !== undefined) { + setTotalCount(result.totalCount); + hasCachedTotalCountRef.current = true; + // Cache the unfiltered total count + if (isUnfiltered) { + baseTotalCountRef.current = result.totalCount; + } + } + }) + .catch(() => {}); // count failure is non-critical + } return () => { cancelled = true; diff --git a/src/screens/BenchmarkJobListScreen.tsx b/src/screens/BenchmarkJobListScreen.tsx index 83475e0b..6e99b0cc 100644 --- a/src/screens/BenchmarkJobListScreen.tsx +++ b/src/screens/BenchmarkJobListScreen.tsx @@ -635,13 +635,17 @@ export function BenchmarkJobListScreen() { )} )} - - {" "} - •{" "} - - - Showing {startIndex + 1}-{endIndex} of {totalCount} - + {endIndex > startIndex && ( + <> + + {" "} + •{" "} + + + Showing {startIndex + 1}-{endIndex} of {totalCount} + + + )} )} diff --git a/src/screens/BenchmarkListScreen.tsx b/src/screens/BenchmarkListScreen.tsx index fe953b30..ce38c16c 100644 --- a/src/screens/BenchmarkListScreen.tsx +++ b/src/screens/BenchmarkListScreen.tsx @@ -376,13 +376,17 @@ export function BenchmarkListScreen() { )} )} - - {" "} - •{" "} - - - Showing {startIndex + 1}-{endIndex} of {totalCount} - + {endIndex > startIndex && ( + <> + + {" "} + •{" "} + + + Showing {startIndex + 1}-{endIndex} of {totalCount} + + + )} )} diff --git a/src/screens/BenchmarkRunListScreen.tsx b/src/screens/BenchmarkRunListScreen.tsx index dadd0d9a..df81086e 100644 --- a/src/screens/BenchmarkRunListScreen.tsx +++ b/src/screens/BenchmarkRunListScreen.tsx @@ -377,13 +377,17 @@ export function BenchmarkRunListScreen() { )} )} - - {" "} - •{" "} - - - Showing {startIndex + 1}-{endIndex} of {totalCount} - + {endIndex > startIndex && ( + <> + + {" "} + •{" "} + + + Showing {startIndex + 1}-{endIndex} of {totalCount} + + + )} )} diff --git a/src/screens/ScenarioRunListScreen.tsx b/src/screens/ScenarioRunListScreen.tsx index df5543b6..8935c520 100644 --- a/src/screens/ScenarioRunListScreen.tsx +++ b/src/screens/ScenarioRunListScreen.tsx @@ -365,13 +365,17 @@ export function ScenarioRunListScreen({ )} )} - - {" "} - •{" "} - - - Showing {startIndex + 1}-{endIndex} of {totalCount} - + {endIndex > startIndex && ( + <> + + {" "} + •{" "} + + + Showing {startIndex + 1}-{endIndex} of {totalCount} + + + )} {benchmarkRunId && ( <>