From 14546eedde3663a8578453003aeefa14b59cc91d Mon Sep 17 00:00:00 2001 From: ssavutu Date: Thu, 3 Sep 2026 22:01:35 -0400 Subject: [PATCH] Show the whole poll archive, not the first 50 /v1/polls answers 50 at a time unless asked otherwise, and getPolls sent no limit, so the archive page rendered 50 of the 179 polls it should have. The missing ones were unreachable by any means -- there is no "load more" for scrolling to reveal. Page through with limit and offset until a short page comes back, rather than asking for one large number: the endpoint caps a request at 200, so a single big limit would only move the cliff rather than remove it. A failure part-way through keeps the pages already read instead of returning empty, so a mid-walk blip degrades the archive rather than blanking it. Only the first page still falls back to empty. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Q11ahEMb1HbNDmkd7gwRpr --- src/utils/pollStore.ts | 55 +++++++++++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/src/utils/pollStore.ts b/src/utils/pollStore.ts index 861623b..ac7f252 100644 --- a/src/utils/pollStore.ts +++ b/src/utils/pollStore.ts @@ -174,25 +174,52 @@ function coerceArchivedPoll(value: unknown): ArchivedPoll | null { // Published polls, newest first, for the archive page. The CMS already excludes // drafts from this endpoint, so nothing unpublished can reach the public site. +// /v1/polls answers 50 at a time unless asked otherwise and caps a request at +// 200, so a single unparameterised fetch silently truncates the archive -- it +// returned 50 of 179 polls. Page through instead of asking for one big number, +// so the archive keeps working when it outgrows the cap too. +const POLL_PAGE_SIZE = 200; +const POLL_PAGE_LIMIT = 50; + export async function getPolls(): Promise { - const response = await fetchCms(pollsUrl, { - method: 'GET', - headers: { Accept: 'application/json' }, - cache: 'no-store' - }); + const polls: ArchivedPoll[] = []; + + for (let page = 0; page < POLL_PAGE_LIMIT; page += 1) { + const offset = page * POLL_PAGE_SIZE; + const response = await fetchCms( + `${pollsUrl}?limit=${POLL_PAGE_SIZE}&offset=${offset}`, + { + method: 'GET', + headers: { Accept: 'application/json' }, + cache: 'no-store' + } + ); + + // A failure part-way through would render a truncated archive as if it were + // the whole thing, so only the first page can fall back to empty; later + // ones keep what has already been read. + if (!response) { + return page === 0 ? [] : polls; + } - if (!response) { - return []; - } + const payload = await response.json() as { polls?: unknown }; + if (!Array.isArray(payload?.polls)) { + return page === 0 ? [] : polls; + } - const payload = await response.json() as { polls?: unknown }; - if (!Array.isArray(payload?.polls)) { - return []; + for (const poll of payload.polls.map(coerceArchivedPoll)) { + if (poll !== null) { + polls.push(poll); + } + } + + // A short page is the last page. + if (payload.polls.length < POLL_PAGE_SIZE) { + break; + } } - return payload.polls - .map(coerceArchivedPoll) - .filter((poll): poll is ArchivedPoll => poll !== null); + return polls; } // Why this returns a reason instead of null: a vote can fail because the option