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({