fix(diagnostics): say which read failed, and let Live restart

- "Load older" is not offered off a loading filter's placeholder rows,
  whose cursor belongs to the previous filter.
- A failed older page, stale rows and missing counts each get their own
  message (HU/EN).
- Live shows as off once a failed read stopped the polling; pressing it
  again reads now.
This commit is contained in:
2026-09-19 03:48:01 +02:00
parent 8f6f89832f
commit eb29f22eb7
4 changed files with 77 additions and 16 deletions
+35 -16
View File
@@ -105,11 +105,26 @@ export default function AdminLogs() {
placeholderData: keepPreviousData,
refetchInterval: (q) => (live && q.state.status !== "error" ? LIVE_MS * 4 : false),
});
// What "Load older" continues from — read off the data, since `hasNextPage` is off while live.
const olderCursor = logs.data?.pages[logs.data.pages.length - 1]?.next_cursor ?? null;
// A read failed while rows (or counts) were already on screen: they are kept, and said to be old.
const stale = (logs.isError && !!logs.data) || facets.isError;
const retryStale = () => {
// A failed read stops the polling (the intervals above); the button shows that, and pressing it
// again reads now.
const polling = live && !logs.isError;
// What "Load older" continues from — read off the data, since `hasNextPage` is off while live; not
// off placeholder rows, which are the previous filter's.
const olderCursor = logs.isPlaceholderData
? null
: (logs.data?.pages[logs.data.pages.length - 1]?.next_cursor ?? null);
// A read that failed while lines were on screen: they are kept, and the message says what the
// failure means for them. (With no lines, the list's own error state says it.)
const failure = !logs.data
? null
: logs.isFetchNextPageError
? "olderFailed"
: logs.isError || (facets.isError && facets.data)
? "stale"
: facets.isError
? "countsFailed"
: null;
const retryFailed = () => {
if (logs.isFetchNextPageError) void logs.fetchNextPage();
else if (logs.isError) void logs.refetch();
if (facets.isError) void facets.refetch();
@@ -129,13 +144,17 @@ export default function AdminLogs() {
id === "none" ? t("adminLogs.noAccount") : (accounts[id] ?? `#${id}`);
const toggleLive = () => {
if (!live) {
// Watching the top: drop the older pages now; `liveRef` keeps each tick to the first page.
qc.setQueryData<typeof logs.data>(key, (d) =>
d ? { pages: d.pages.slice(0, 1), pageParams: d.pageParams.slice(0, 1) } : d,
);
if (polling) {
setLive(false);
return;
}
setLive(!live);
// Watching the top: drop the older pages now; `liveRef` keeps each tick to the first page.
qc.setQueryData<typeof logs.data>(key, (d) =>
d ? { pages: d.pages.slice(0, 1), pageParams: d.pageParams.slice(0, 1) } : d,
);
setLive(true);
if (logs.isError) void logs.refetch();
if (facets.isError) void facets.refetch();
};
const loadOlder = () => {
@@ -256,10 +275,10 @@ export default function AdminLogs() {
/>
<span className="ml-auto flex gap-1.5">
<Button
variant={live ? "outline-accent" : "outline"}
variant={polling ? "outline-accent" : "outline"}
size="xs"
icon={<Radio className="h-3.5 w-3.5" />}
aria-pressed={live}
aria-pressed={polling}
onClick={toggleLive}
>
{t("adminLogs.live")}
@@ -296,14 +315,14 @@ export default function AdminLogs() {
</div>
)}
{stale && (
{failure && (
<StateMessage
tone="error"
compact
onRetry={retryStale}
onRetry={retryFailed}
className="border-b border-border"
>
{t("adminLogs.stale")}
{t(`adminLogs.${failure}`)}
</StateMessage>
)}