From 3748316c609e2495cf1672824b562d1de55dcc7e Mon Sep 17 00:00:00 2001 From: Amp Date: Wed, 11 Nov 2026 14:32:08 +0000 Subject: [PATCH] fix(network): never send an empty day list, and settle failed daily reads The goodbye tables' data starts today, after the last complete day, so chunksFor listed no days and sent day_start_date_in_values="", which the API client rejects before sending. With the app's retry-forever default the read never settled, census.isLoading stayed true and the whole network overview sat on its skeleton. - chunksFor returns no chunks when there is no complete day to read. - useDailyByClient retries a failed chunk three times, and never a request the API client rejected, so one broken table settles as empty instead of holding back every section. Amp-Thread-ID: https://ampcode.com/threads/T-01a1c912-2d49-710d-b746-bda9e576453d Co-authored-by: Sam Calder-Mason --- src/pages/ethereum/clients/hooks/useDailyByClient.ts | 9 +++++++++ .../ethereum/clients/hooks/useVersionCounts.test.ts | 9 +++++++++ src/pages/ethereum/clients/hooks/useVersionCounts.ts | 3 +++ 3 files changed, 21 insertions(+) diff --git a/src/pages/ethereum/clients/hooks/useDailyByClient.ts b/src/pages/ethereum/clients/hooks/useDailyByClient.ts index ddacc6548..964522613 100644 --- a/src/pages/ethereum/clients/hooks/useDailyByClient.ts +++ b/src/pages/ethereum/clients/hooks/useDailyByClient.ts @@ -1,5 +1,6 @@ import { useMemo } from 'react'; import { useQueries, type UseQueryResult } from '@tanstack/react-query'; +import { $ZodError } from 'zod/v4/core'; import { useNetwork } from '@/hooks/useNetwork'; import { useTableBounds } from '@/hooks/useBounds'; import { fetchAllPages } from '@/utils/api-pagination'; @@ -9,6 +10,13 @@ import { chunksFor, useKeepPrevious } from './useVersionCounts'; type ListFn = Parameters[0]; +/** Retries a failed chunk a few times, unlike the app default of forever, so one broken table settles as an error + * instead of loading forever and holding back the pages that wait on it. A request the API client rejects before + * sending fails the same way every time, so it is not retried. */ +function retryChunk(failureCount: number, error: unknown): boolean { + return !(error instanceof $ZodError) && failureCount < 3; +} + export interface UseDailyByClientResult { rows: T[]; /** First day the table has data for (unix seconds), when known */ @@ -56,6 +64,7 @@ export function useDailyByClient( fetchAllPages(list, { query: { ...chunk.query, ...filters, page_size: PAGE_SIZE } }, dataKey, signal), enabled: !!currentNetwork && filters !== null && !bounds.isLoading, staleTime: chunk.open ? 10 * 60 * 1000 : Infinity, + retry: retryChunk, })), combine: combine, }); diff --git a/src/pages/ethereum/clients/hooks/useVersionCounts.test.ts b/src/pages/ethereum/clients/hooks/useVersionCounts.test.ts index 8539fb96e..c9f480d27 100644 --- a/src/pages/ethereum/clients/hooks/useVersionCounts.test.ts +++ b/src/pages/ethereum/clients/hooks/useVersionCounts.test.ts @@ -20,6 +20,15 @@ describe('chunksFor', () => { expect(chunks).toHaveLength(1); expect(chunks[0].query).toEqual({ day_start_date_in_values: '2026-11-03,2026-11-04' }); }); + + it('reads nothing when the data starts after the last complete day', () => { + expect(chunksFor('daily', to + 7 * 3600, to)).toEqual([]); + expect(chunksFor('daily', to, to)).toEqual([]); + }); + + it('still lists the last complete day when the data starts during it', () => { + expect(chunksFor('daily', to - 3600, to)[0].query).toEqual({ day_start_date_in_values: '2026-11-04' }); + }); }); describe('weekChunks', () => { diff --git a/src/pages/ethereum/clients/hooks/useVersionCounts.ts b/src/pages/ethereum/clients/hooks/useVersionCounts.ts index e5c814d41..89d9ff97f 100644 --- a/src/pages/ethereum/clients/hooks/useVersionCounts.ts +++ b/src/pages/ethereum/clients/hooks/useVersionCounts.ts @@ -82,6 +82,9 @@ export function chunksFor(grain: UseVersionCountsOptions['grain'], from: number return chunks; } + // No complete day yet, such as a table whose data starts today: an empty day list is not a valid query + if (from !== undefined && Math.floor(from / DAY) * DAY >= to) return []; + if (from !== undefined && (to - from) / DAY <= MAX_LISTED_DAYS) { const days: string[] = []; for (let day = Math.floor(from / DAY) * DAY; day < to; day += DAY) days.push(isoDay(day));