diff --git a/src/commands/blueprint/list.tsx b/src/commands/blueprint/list.tsx index 00222c5a..fabb0f66 100644 --- a/src/commands/blueprint/list.tsx +++ b/src/commands/blueprint/list.tsx @@ -113,7 +113,11 @@ const ListBlueprintsUI = ({ // Fetch function for pagination hook const fetchPage = React.useCallback( - async (params: { limit: number; startingAt?: string }) => { + async (params: { + limit: number; + startingAt?: string; + includeTotalCount?: boolean; + }) => { const client = getClient(); const pageBlueprints: BlueprintListItem[] = []; @@ -127,11 +131,17 @@ const ListBlueprintsUI = ({ if (search.submittedSearchQuery) { queryParams.search = search.submittedSearchQuery; } + // Only request total_count on first page (expensive for backend) + if (params.includeTotalCount) { + queryParams.include_total_count = true; + } // Fetch ONE page only const page = (await client.blueprints.list( queryParams, - )) as unknown as BlueprintsCursorIDPage; + )) as unknown as BlueprintsCursorIDPage & { + total_count?: number; + }; // Extract data and create defensive copies if (page.blueprints && Array.isArray(page.blueprints)) { @@ -148,7 +158,7 @@ const ListBlueprintsUI = ({ const result = { items: pageBlueprints, hasMore: page.has_more || false, - totalCount: pageBlueprints.length, + totalCount: page.total_count, }; return result; @@ -356,7 +366,11 @@ const ListBlueprintsUI = ({ // Calculate pagination info for display const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE)); const startIndex = currentPage * PAGE_SIZE; - const endIndex = startIndex + blueprints.length; + const endIndex = Math.min(startIndex + blueprints.length, totalCount); + const showingRange = + endIndex === startIndex + 1 + ? `${startIndex + 1}` + : `${startIndex + 1}-${endIndex}`; const executeOperation = async ( blueprintOverride?: BlueprintListItem, @@ -883,7 +897,7 @@ const ListBlueprintsUI = ({ data={blueprints} keyExtractor={(blueprint: BlueprintListItem) => blueprint.id} selectedIndex={selectedIndex} - title={`blueprints[${hasMore ? `${totalCount}+` : totalCount}]`} + title={`blueprints[${totalCount}]`} columns={blueprintColumns} emptyState={ @@ -897,7 +911,7 @@ const ListBlueprintsUI = ({ {!showPopup && ( - {figures.hamburger} {hasMore ? `${totalCount}+` : totalCount} + {figures.hamburger} {totalCount} {" "} @@ -915,8 +929,7 @@ const ListBlueprintsUI = ({ ) : ( - Page {currentPage + 1} of{" "} - {hasMore ? `${totalPages}+` : totalPages} + Page {currentPage + 1} of {totalPages} )} @@ -926,8 +939,7 @@ const ListBlueprintsUI = ({ •{" "} - Showing {startIndex + 1}-{endIndex} of{" "} - {hasMore ? `${totalCount}+` : totalCount} + Showing {showingRange} of {totalCount} {search.submittedSearchQuery && ( <> diff --git a/src/commands/devbox/list.tsx b/src/commands/devbox/list.tsx index 6af2a778..b33973a5 100644 --- a/src/commands/devbox/list.tsx +++ b/src/commands/devbox/list.tsx @@ -78,7 +78,11 @@ const ListDevboxesUI = ({ // Fetch function for pagination hook const fetchPage = React.useCallback( - async (params: { limit: number; startingAt?: string }) => { + async (params: { + limit: number; + startingAt?: string; + includeTotalCount?: boolean; + }) => { const client = getClient(); const pageDevboxes: Devbox[] = []; @@ -95,11 +99,15 @@ const ListDevboxesUI = ({ if (search.submittedSearchQuery) { queryParams.search = search.submittedSearchQuery; } + // Only request total_count on first page (expensive for backend) + if (params.includeTotalCount) { + queryParams.include_total_count = true; + } // Fetch ONE page only const page = (await client.devboxes.list( queryParams, - )) as unknown as DevboxesCursorIDPage; + )) as unknown as DevboxesCursorIDPage & { total_count?: number }; // Extract data and create defensive copies using JSON serialization if (page.devboxes && Array.isArray(page.devboxes)) { @@ -111,7 +119,7 @@ const ListDevboxesUI = ({ const result = { items: pageDevboxes, hasMore: page.has_more || false, - totalCount: pageDevboxes.length, + totalCount: page.total_count, }; return result; @@ -419,7 +427,11 @@ const ListDevboxesUI = ({ // Calculate pagination info for display const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE)); const startIndex = currentPage * PAGE_SIZE; - const endIndex = startIndex + devboxes.length; + const endIndex = Math.min(startIndex + devboxes.length, totalCount); + const showingRange = + endIndex === startIndex + 1 + ? `${startIndex + 1}` + : `${startIndex + 1}-${endIndex}`; // Filter operations based on devbox status const hasTunnel = !!( @@ -709,7 +721,7 @@ const ListDevboxesUI = ({ data={devboxes} keyExtractor={(devbox: Devbox) => devbox.id} selectedIndex={selectedIndex} - title="devboxes" + title={`devboxes[${totalCount}]`} columns={tableColumns} emptyState={ @@ -723,7 +735,7 @@ const ListDevboxesUI = ({ {!showPopup && ( - {figures.hamburger} {hasMore ? `${totalCount}+` : totalCount} + {figures.hamburger} {totalCount} {" "} @@ -741,8 +753,7 @@ const ListDevboxesUI = ({ ) : ( - Page {currentPage + 1} of{" "} - {hasMore ? `${totalPages}+` : totalPages} + Page {currentPage + 1} of {totalPages} )} @@ -752,8 +763,7 @@ const ListDevboxesUI = ({ •{" "} - Showing {startIndex + 1}-{endIndex} of{" "} - {hasMore ? `${totalCount}+` : totalCount} + Showing {showingRange} of {totalCount} {search.submittedSearchQuery && ( <> diff --git a/src/commands/gateway-config/list.tsx b/src/commands/gateway-config/list.tsx index 10a4252c..720e4f80 100644 --- a/src/commands/gateway-config/list.tsx +++ b/src/commands/gateway-config/list.tsx @@ -122,7 +122,11 @@ const ListGatewayConfigsUI = ({ // Fetch function for pagination hook const fetchPage = React.useCallback( - async (params: { limit: number; startingAt?: string }) => { + async (params: { + limit: number; + startingAt?: string; + includeTotalCount?: boolean; + }) => { const client = getClient(); const pageConfigs: GatewayConfigListItem[] = []; @@ -136,11 +140,17 @@ const ListGatewayConfigsUI = ({ if (search.submittedSearchQuery) { queryParams.name = search.submittedSearchQuery; } + // Only request total_count on first page (expensive for backend) + if (params.includeTotalCount) { + queryParams.include_total_count = true; + } // Fetch ONE page only const page = (await client.gatewayConfigs.list( queryParams, - )) as unknown as GatewayConfigsCursorIDPage; + )) as unknown as GatewayConfigsCursorIDPage & { + total_count?: number; + }; // Extract data and create defensive copies if (page.gateway_configs && Array.isArray(page.gateway_configs)) { @@ -163,7 +173,7 @@ const ListGatewayConfigsUI = ({ const result = { items: pageConfigs, hasMore: page.has_more || false, - totalCount: pageConfigs.length, + totalCount: page.total_count, }; return result; @@ -304,7 +314,11 @@ const ListGatewayConfigsUI = ({ // Calculate pagination info for display const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE)); const startIndex = currentPage * PAGE_SIZE; - const endIndex = startIndex + configs.length; + const endIndex = Math.min(startIndex + configs.length, totalCount); + const showingRange = + endIndex === startIndex + 1 + ? `${startIndex + 1}` + : `${startIndex + 1}-${endIndex}`; const executeOperation = async ( config: GatewayConfigListItem, @@ -653,7 +667,7 @@ const ListGatewayConfigsUI = ({ data={configs} keyExtractor={(config: GatewayConfigListItem) => config.id} selectedIndex={selectedIndex} - title={`gateway_configs[${hasMore ? `${totalCount}+` : totalCount}]`} + title={`gateway_configs[${totalCount}]`} columns={columns} emptyState={ @@ -668,7 +682,7 @@ const ListGatewayConfigsUI = ({ {!showPopup && ( - {figures.hamburger} {hasMore ? `${totalCount}+` : totalCount} + {figures.hamburger} {totalCount} {" "} @@ -686,8 +700,7 @@ const ListGatewayConfigsUI = ({ ) : ( - Page {currentPage + 1} of{" "} - {hasMore ? `${totalPages}+` : totalPages} + Page {currentPage + 1} of {totalPages} )} @@ -697,8 +710,7 @@ const ListGatewayConfigsUI = ({ •{" "} - Showing {startIndex + 1}-{endIndex} of{" "} - {hasMore ? `${totalCount}+` : totalCount} + Showing {showingRange} of {totalCount} {search.submittedSearchQuery && ( <> diff --git a/src/commands/mcp-config/list.tsx b/src/commands/mcp-config/list.tsx index 6a3ecd90..18029ee3 100644 --- a/src/commands/mcp-config/list.tsx +++ b/src/commands/mcp-config/list.tsx @@ -103,7 +103,11 @@ const ListMcpConfigsUI = ({ const nameWidth = Math.min(80, Math.max(15, remainingWidth)); const fetchPage = React.useCallback( - async (params: { limit: number; startingAt?: string }) => { + async (params: { + limit: number; + startingAt?: string; + includeTotalCount?: boolean; + }) => { const client = getClient(); const pageConfigs: McpConfigListItem[] = []; @@ -116,10 +120,16 @@ const ListMcpConfigsUI = ({ if (search.submittedSearchQuery) { queryParams.name = search.submittedSearchQuery; } + // Only request total_count on first page (expensive for backend) + if (params.includeTotalCount) { + queryParams.include_total_count = true; + } const page = (await client.mcpConfigs.list( queryParams, - )) as unknown as McpConfigsCursorIDPage; + )) as unknown as McpConfigsCursorIDPage & { + total_count?: number; + }; if (page.mcp_configs && Array.isArray(page.mcp_configs)) { page.mcp_configs.forEach((m: McpConfigListItem) => { @@ -139,7 +149,7 @@ const ListMcpConfigsUI = ({ return { items: pageConfigs, hasMore: page.has_more || false, - totalCount: pageConfigs.length, + totalCount: page.total_count, }; }, [search.submittedSearchQuery], @@ -266,7 +276,11 @@ const ListMcpConfigsUI = ({ const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE)); const startIndex = currentPage * PAGE_SIZE; - const endIndex = startIndex + configs.length; + const endIndex = Math.min(startIndex + configs.length, totalCount); + const showingRange = + endIndex === startIndex + 1 + ? `${startIndex + 1}` + : `${startIndex + 1}-${endIndex}`; const executeOperation = async ( config: McpConfigListItem, @@ -580,7 +594,7 @@ const ListMcpConfigsUI = ({ data={configs} keyExtractor={(config: McpConfigListItem) => config.id} selectedIndex={selectedIndex} - title={`mcp_configs[${hasMore ? `${totalCount}+` : totalCount}]`} + title={`mcp_configs[${totalCount}]`} columns={columns} emptyState={ @@ -593,7 +607,7 @@ const ListMcpConfigsUI = ({ {!showPopup && ( - {figures.hamburger} {hasMore ? `${totalCount}+` : totalCount} + {figures.hamburger} {totalCount} {" "} @@ -611,8 +625,7 @@ const ListMcpConfigsUI = ({ ) : ( - Page {currentPage + 1} of{" "} - {hasMore ? `${totalPages}+` : totalPages} + Page {currentPage + 1} of {totalPages} )} @@ -622,8 +635,7 @@ const ListMcpConfigsUI = ({ •{" "} - Showing {startIndex + 1}-{endIndex} of{" "} - {hasMore ? `${totalCount}+` : totalCount} + Showing {showingRange} of {totalCount} {search.submittedSearchQuery && ( <> diff --git a/src/commands/network-policy/list.tsx b/src/commands/network-policy/list.tsx index 5b19e808..40dc9b62 100644 --- a/src/commands/network-policy/list.tsx +++ b/src/commands/network-policy/list.tsx @@ -135,7 +135,11 @@ const ListNetworkPoliciesUI = ({ // Fetch function for pagination hook const fetchPage = React.useCallback( - async (params: { limit: number; startingAt?: string }) => { + async (params: { + limit: number; + startingAt?: string; + includeTotalCount?: boolean; + }) => { const client = getClient(); const pagePolicies: NetworkPolicyListItem[] = []; @@ -149,11 +153,17 @@ const ListNetworkPoliciesUI = ({ if (search.submittedSearchQuery) { queryParams.search = search.submittedSearchQuery; } + // Only request total_count on first page (expensive for backend) + if (params.includeTotalCount) { + queryParams.include_total_count = true; + } // Fetch ONE page only const page = (await client.networkPolicies.list( queryParams, - )) as unknown as NetworkPoliciesCursorIDPage; + )) as unknown as NetworkPoliciesCursorIDPage & { + total_count?: number; + }; // Extract data and create defensive copies if (page.network_policies && Array.isArray(page.network_policies)) { @@ -178,7 +188,7 @@ const ListNetworkPoliciesUI = ({ const result = { items: pagePolicies, hasMore: page.has_more || false, - totalCount: pagePolicies.length, + totalCount: page.total_count, }; return result; @@ -341,7 +351,11 @@ const ListNetworkPoliciesUI = ({ // Calculate pagination info for display const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE)); const startIndex = currentPage * PAGE_SIZE; - const endIndex = startIndex + policies.length; + const endIndex = Math.min(startIndex + policies.length, totalCount); + const showingRange = + endIndex === startIndex + 1 + ? `${startIndex + 1}` + : `${startIndex + 1}-${endIndex}`; const executeOperation = async ( policy: NetworkPolicyListItem, @@ -683,7 +697,7 @@ const ListNetworkPoliciesUI = ({ data={policies} keyExtractor={(policy: NetworkPolicyListItem) => policy.id} selectedIndex={selectedIndex} - title={`network_policies[${hasMore ? `${totalCount}+` : totalCount}]`} + title={`network_policies[${totalCount}]`} columns={columns} emptyState={ @@ -697,7 +711,7 @@ const ListNetworkPoliciesUI = ({ {!showPopup && ( - {figures.hamburger} {hasMore ? `${totalCount}+` : totalCount} + {figures.hamburger} {totalCount} {" "} @@ -715,8 +729,7 @@ const ListNetworkPoliciesUI = ({ ) : ( - Page {currentPage + 1} of{" "} - {hasMore ? `${totalPages}+` : totalPages} + Page {currentPage + 1} of {totalPages} )} @@ -726,8 +739,7 @@ const ListNetworkPoliciesUI = ({ •{" "} - Showing {startIndex + 1}-{endIndex} of{" "} - {hasMore ? `${totalCount}+` : totalCount} + Showing {showingRange} of {totalCount} {search.submittedSearchQuery && ( <> diff --git a/src/commands/object/list.tsx b/src/commands/object/list.tsx index d7579290..3e0be10d 100644 --- a/src/commands/object/list.tsx +++ b/src/commands/object/list.tsx @@ -135,7 +135,11 @@ const ListObjectsUI = ({ // Fetch function for pagination hook const fetchPage = React.useCallback( - async (params: { limit: number; startingAt?: string }) => { + async (params: { + limit: number; + startingAt?: string; + includeTotalCount?: boolean; + }) => { const client = getClient(); const pageObjects: ObjectListItem[] = []; @@ -149,6 +153,10 @@ const ListObjectsUI = ({ if (search.submittedSearchQuery) { queryParams.search = search.submittedSearchQuery; } + // Only request total_count on first page (expensive for backend) + if (params.includeTotalCount) { + queryParams.include_total_count = true; + } // Fetch ONE page only const result = await client.objects.list(queryParams); @@ -174,12 +182,13 @@ const ListObjectsUI = ({ const pageResult = result as unknown as { objects: unknown[]; has_more?: boolean; + total_count?: number; }; return { items: pageObjects, hasMore: pageResult.has_more || false, - totalCount: pageObjects.length, + totalCount: pageResult.total_count, }; }, [search.submittedSearchQuery], @@ -344,7 +353,11 @@ const ListObjectsUI = ({ // Calculate pagination info for display const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE)); const startIndex = currentPage * PAGE_SIZE; - const endIndex = startIndex + objects.length; + const endIndex = Math.min(startIndex + objects.length, totalCount); + const showingRange = + endIndex === startIndex + 1 + ? `${startIndex + 1}` + : `${startIndex + 1}-${endIndex}`; const executeOperation = async ( obj: ObjectListItem, @@ -724,7 +737,7 @@ const ListObjectsUI = ({ data={objects} keyExtractor={(obj: ObjectListItem) => obj.id} selectedIndex={selectedIndex} - title={`storage_objects[${hasMore ? `${totalCount}+` : totalCount}]`} + title={`storage_objects[${totalCount}]`} columns={columns} emptyState={ @@ -739,7 +752,7 @@ const ListObjectsUI = ({ {!showPopup && ( - {figures.hamburger} {hasMore ? `${totalCount}+` : totalCount} + {figures.hamburger} {totalCount} {" "} @@ -757,8 +770,7 @@ const ListObjectsUI = ({ ) : ( - Page {currentPage + 1} of{" "} - {hasMore ? `${totalPages}+` : totalPages} + Page {currentPage + 1} of {totalPages} )} @@ -768,8 +780,7 @@ const ListObjectsUI = ({ •{" "} - Showing {startIndex + 1}-{endIndex} of{" "} - {hasMore ? `${totalCount}+` : totalCount} + Showing {showingRange} of {totalCount} {search.submittedSearchQuery && ( <> diff --git a/src/commands/secret/list.tsx b/src/commands/secret/list.tsx index 2f721c66..7602d137 100644 --- a/src/commands/secret/list.tsx +++ b/src/commands/secret/list.tsx @@ -90,7 +90,11 @@ const ListSecretsUI = ({ // Fetch function for pagination hook const fetchPage = React.useCallback( - async (params: { limit: number; startingAt?: string }) => { + async (params: { + limit: number; + startingAt?: string; + includeTotalCount?: boolean; + }) => { const client = getClient(); const pageSecrets: SecretListItem[] = []; @@ -237,7 +241,11 @@ const ListSecretsUI = ({ // Calculate pagination info for display const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE)); const startIndex = currentPage * PAGE_SIZE; - const endIndex = startIndex + secrets.length; + const endIndex = Math.min(startIndex + secrets.length, totalCount); + const showingRange = + endIndex === startIndex + 1 + ? `${startIndex + 1}` + : `${startIndex + 1}-${endIndex}`; const executeOperation = async ( secret: SecretListItem, @@ -543,7 +551,7 @@ const ListSecretsUI = ({ {!showPopup && ( - {figures.hamburger} {hasMore ? `${totalCount}+` : totalCount} + {figures.hamburger} {totalCount} {" "} @@ -561,8 +569,7 @@ const ListSecretsUI = ({ ) : ( - Page {currentPage + 1} of{" "} - {hasMore ? `${totalPages}+` : totalPages} + Page {currentPage + 1} of {totalPages} )} @@ -572,8 +579,7 @@ const ListSecretsUI = ({ •{" "} - Showing {startIndex + 1}-{endIndex} of{" "} - {hasMore ? `${totalCount}+` : totalCount} + Showing {showingRange} of {totalCount} {search.submittedSearchQuery && ( <> diff --git a/src/commands/snapshot/list.tsx b/src/commands/snapshot/list.tsx index bf357a11..1bed9c2a 100644 --- a/src/commands/snapshot/list.tsx +++ b/src/commands/snapshot/list.tsx @@ -104,7 +104,11 @@ const ListSnapshotsUI = ({ // Fetch function for pagination hook const fetchPage = React.useCallback( - async (params: { limit: number; startingAt?: string }) => { + async (params: { + limit: number; + startingAt?: string; + includeTotalCount?: boolean; + }) => { const client = getClient(); const pageSnapshots: SnapshotListItem[] = []; @@ -121,11 +125,17 @@ const ListSnapshotsUI = ({ if (search.submittedSearchQuery) { queryParams.search = search.submittedSearchQuery; } + // Only request total_count on first page (expensive for backend) + if (params.includeTotalCount) { + queryParams.include_total_count = true; + } // Fetch ONE page only const page = (await client.devboxes.listDiskSnapshots( queryParams, - )) as unknown as DiskSnapshotsCursorIDPage; + )) as unknown as DiskSnapshotsCursorIDPage & { + total_count?: number; + }; // Extract data and create defensive copies if (page.snapshots && Array.isArray(page.snapshots)) { @@ -143,7 +153,7 @@ const ListSnapshotsUI = ({ const result = { items: pageSnapshots, hasMore: page.has_more || false, - totalCount: pageSnapshots.length, + totalCount: page.total_count, }; return result; @@ -268,7 +278,11 @@ const ListSnapshotsUI = ({ // Calculate pagination info for display const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE)); const startIndex = currentPage * PAGE_SIZE; - const endIndex = startIndex + snapshots.length; + const endIndex = Math.min(startIndex + snapshots.length, totalCount); + const showingRange = + endIndex === startIndex + 1 + ? `${startIndex + 1}` + : `${startIndex + 1}-${endIndex}`; const executeOperation = async ( snapshot: SnapshotListItem, @@ -595,7 +609,7 @@ const ListSnapshotsUI = ({ data={snapshots} keyExtractor={(snapshot: SnapshotListItem) => snapshot.id} selectedIndex={selectedIndex} - title={`snapshots[${hasMore ? `${totalCount}+` : totalCount}]`} + title={`snapshots[${totalCount}]`} columns={columns} emptyState={ @@ -610,7 +624,7 @@ const ListSnapshotsUI = ({ {!showPopup && ( - {figures.hamburger} {hasMore ? `${totalCount}+` : totalCount} + {figures.hamburger} {totalCount} {" "} @@ -628,8 +642,7 @@ const ListSnapshotsUI = ({ ) : ( - Page {currentPage + 1} of{" "} - {hasMore ? `${totalPages}+` : totalPages} + Page {currentPage + 1} of {totalPages} )} @@ -639,8 +652,7 @@ const ListSnapshotsUI = ({ •{" "} - Showing {startIndex + 1}-{endIndex} of{" "} - {hasMore ? `${totalCount}+` : totalCount} + Showing {showingRange} of {totalCount} {search.submittedSearchQuery && ( <> diff --git a/src/hooks/useCursorPagination.ts b/src/hooks/useCursorPagination.ts index 5aae8c39..b695596a 100644 --- a/src/hooks/useCursorPagination.ts +++ b/src/hooks/useCursorPagination.ts @@ -7,7 +7,11 @@ export interface UsePaginatedListConfig { /** * Fetch function that takes pagination params and returns a page of results */ - fetchPage: (params: { limit: number; startingAt?: string }) => Promise<{ + fetchPage: (params: { + limit: number; + startingAt?: string; + includeTotalCount?: boolean; + }) => Promise<{ items: T[]; hasMore: boolean; totalCount?: number; @@ -101,7 +105,8 @@ export function useCursorPagination( const [currentPage, setCurrentPage] = React.useState(0); const [hasMore, setHasMore] = React.useState(false); const [totalCount, setTotalCount] = React.useState(0); - const maxTotalCountRef = React.useRef(0); + // Track if we have a cached total count from the API (to avoid re-requesting) + const hasCachedTotalCountRef = React.useRef(false); // Cursor history: cursorHistory[N] = last item ID of page N // Used to determine startingAt for page N+1 @@ -171,9 +176,13 @@ export function useCursorPagination( const startingAt = page > 0 ? cursorHistoryRef.current[page - 1] : undefined; + // Only request total_count on first fetch or when we don't have it cached + const includeTotalCount = !hasCachedTotalCountRef.current; + const result = await fetchPageRef.current({ limit: pageSizeRef.current, startingAt, + includeTotalCount, }); if (!isMountedRef.current) return; @@ -191,12 +200,20 @@ export function useCursorPagination( // Update pagination state setHasMore(result.hasMore); - // Compute cumulative total: items seen on all previous pages + current page. - // Use a high-water mark so the count never decreases when navigating back. - const computed = page * pageSizeRef.current + result.items.length; - const newTotal = Math.max(computed, maxTotalCountRef.current); - maxTotalCountRef.current = newTotal; - setTotalCount(newTotal); + + // Use API's totalCount if available (only on first fetch), otherwise keep existing + if ( + result.totalCount !== undefined && + result.totalCount > 0 && + !hasCachedTotalCountRef.current + ) { + setTotalCount(result.totalCount); + hasCachedTotalCountRef.current = true; + } else if (!hasCachedTotalCountRef.current) { + // Fallback: compute from items seen so far (for APIs that don't support total_count) + const computed = page * pageSizeRef.current + result.items.length; + setTotalCount((prev) => Math.max(computed, prev)); + } } catch (err) { if (!isMountedRef.current) return; setError(err as Error); @@ -216,7 +233,7 @@ export function useCursorPagination( React.useEffect(() => { // Clear cursor history when deps change cursorHistoryRef.current = []; - maxTotalCountRef.current = 0; + hasCachedTotalCountRef.current = false; setCurrentPage(0); setItems([]); setHasMore(false);