diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 4500b12e..3d1b998a 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -547,6 +547,15 @@ export default tseslint.config( // lib/*.contract.test.ts). "no-restricted-syntax": [ "error", + // A next-page fetch called bare CANCELS the one in flight and sends the same cursor again; + // our requests ignore the abort, so the server ran both (E31 S4e: every feed page 2–3×). + // lib/nextPage.ts NEXT_PAGE makes the second call join the first. + { + selector: + "CallExpression[arguments.length=0]:matches([callee.name='fetchNextPage'], [callee.property.name='fetchNextPage'])", + message: + "fetchNextPage() cancels and re-sends a page already loading: call fetchNextPage(NEXT_PAGE) (lib/nextPage.ts).", + }, // `enabled:` sees only the native attribute; a FocusSafeButton that holds the focus is // disabled by aria-disabled, so a bare `enabled:hover:` lit it up on hover as if it worked. { diff --git a/frontend/src/components/AdminLogs.tsx b/frontend/src/components/AdminLogs.tsx index bfc0a8e0..656ce33f 100644 --- a/frontend/src/components/AdminLogs.tsx +++ b/frontend/src/components/AdminLogs.tsx @@ -13,6 +13,7 @@ import { api, type LogFacet, type LogQuery, type ServerLogRow } from "../lib/api import { copyText } from "../lib/clipboard"; import { formatDateTimeSec } from "../lib/format"; import { nextNavId, notifyNav, rememberNavPosition } from "../lib/history"; +import { NEXT_PAGE } from "../lib/nextPage"; import { LEVEL_GROUPS, type LevelGroup } from "../lib/log"; import { DEFAULT_LOGS_STATE, @@ -156,7 +157,7 @@ export default function AdminLogs() { ? "countsFailed" : null; const retryFailed = () => { - if (logs.isFetchNextPageError) void logs.fetchNextPage(); + if (logs.isFetchNextPageError) void logs.fetchNextPage(NEXT_PAGE); else if (logs.isError) void logs.refetch(); if (facets.isError) void facets.refetch(); }; @@ -190,7 +191,7 @@ export default function AdminLogs() { const loadOlder = () => { setLive(false); - void logs.fetchNextPage(); + void logs.fetchNextPage(NEXT_PAGE); }; const copy = async () => { diff --git a/frontend/src/components/Feed.tsx b/frontend/src/components/Feed.tsx index a6fabbf5..3eb7d287 100644 --- a/frontend/src/components/Feed.tsx +++ b/frontend/src/components/Feed.tsx @@ -7,6 +7,7 @@ import { useQueryClient, } from "@tanstack/react-query"; import { qk } from "../lib/queryKeys"; +import { NEXT_PAGE } from "../lib/nextPage"; import { ArrowLeft, Columns2, @@ -556,7 +557,7 @@ export default function Feed({