mirror of
https://github.com/block/buzz.git
synced 2026-11-08 10:21:46 +00:00
fix(desktop): suppress fresh focus-return query herd
Gate the seven query families measured refetching together on focus so only results older than five minutes catch up. Keep their normal stale times, focused polling, and live invalidation behavior unchanged. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
||||
import { updateCachedChannelMemberDisplayName } from "@/features/channels/channelMemberProfileCache";
|
||||
import { evictUsersBatchEntries } from "@/features/profile/hooks";
|
||||
import {
|
||||
refetchOnFocusWhenOld,
|
||||
useAppFocused,
|
||||
useFocusedRefetchInterval,
|
||||
} from "@/shared/lib/useDocumentVisible";
|
||||
@@ -340,7 +341,7 @@ export function useRelayAgentsQuery(options?: { enabled?: boolean }) {
|
||||
// reconcile (kinds PERSONA/TEAM/MANAGED_AGENT), never for kind:10100. So we
|
||||
// keep polling but at a relaxed cadence and pause it while backgrounded.
|
||||
refetchInterval,
|
||||
refetchOnWindowFocus: true,
|
||||
refetchOnWindowFocus: refetchOnFocusWhenOld,
|
||||
enabled: options?.enabled,
|
||||
});
|
||||
}
|
||||
@@ -364,7 +365,7 @@ export function useManagedAgentsQuery(options?: { enabled?: boolean }) {
|
||||
? 5_000
|
||||
: false;
|
||||
},
|
||||
refetchOnWindowFocus: true,
|
||||
refetchOnWindowFocus: refetchOnFocusWhenOld,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,10 @@ import {
|
||||
} from "@/features/agents/lib/personaCatalogRelay";
|
||||
import { invalidatePersonaEditCaches } from "@/features/agents/lib/personaEditCaches";
|
||||
import { relayClient } from "@/shared/api/relayClient";
|
||||
import { useFocusedRefetchInterval } from "@/shared/lib/useDocumentVisible";
|
||||
import {
|
||||
refetchOnFocusWhenOld,
|
||||
useFocusedRefetchInterval,
|
||||
} from "@/shared/lib/useDocumentVisible";
|
||||
import {
|
||||
setPersonaShared,
|
||||
updatePersonaAndPublish,
|
||||
@@ -27,7 +30,7 @@ export function usePersonaCatalogQuery(communityId: string | null) {
|
||||
queryFn: fetchPersonaCatalogPublications,
|
||||
staleTime: 30_000,
|
||||
refetchInterval,
|
||||
refetchOnWindowFocus: true,
|
||||
refetchOnWindowFocus: refetchOnFocusWhenOld,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { useFocusedRefetchInterval } from "@/shared/lib/useDocumentVisible";
|
||||
import {
|
||||
refetchOnFocusWhenOld,
|
||||
useFocusedRefetchInterval,
|
||||
} from "@/shared/lib/useDocumentVisible";
|
||||
|
||||
import {
|
||||
createChannelTemplate,
|
||||
@@ -25,7 +28,7 @@ export function useChannelTemplatesQuery() {
|
||||
queryFn: listChannelTemplates,
|
||||
staleTime: 30_000,
|
||||
refetchInterval,
|
||||
refetchOnWindowFocus: true,
|
||||
refetchOnWindowFocus: refetchOnFocusWhenOld,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,10 @@ import {
|
||||
setCustomEmoji,
|
||||
} from "@/shared/api/customEmoji";
|
||||
import { relayClient } from "@/shared/api/relayClient";
|
||||
import { useFocusedRefetchInterval } from "@/shared/lib/useDocumentVisible";
|
||||
import {
|
||||
refetchOnFocusWhenOld,
|
||||
useFocusedRefetchInterval,
|
||||
} from "@/shared/lib/useDocumentVisible";
|
||||
import type { CustomEmoji } from "@/shared/lib/remarkCustomEmoji";
|
||||
|
||||
/**
|
||||
@@ -38,7 +41,7 @@ export function useCustomEmojiQuery() {
|
||||
// but poll every 2 minutes as a backstop for any missed live event.
|
||||
staleTime: 60_000,
|
||||
refetchInterval,
|
||||
refetchOnWindowFocus: true,
|
||||
refetchOnWindowFocus: refetchOnFocusWhenOld,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,10 @@ import { useRelayConnection } from "@/shared/api/useRelayConnection";
|
||||
import { getOsIdleSeconds } from "@/shared/api/osIdle";
|
||||
import { getPresence } from "@/shared/api/tauri";
|
||||
import { normalizePubkey } from "@/shared/lib/pubkey";
|
||||
import { useFocusedRefetchInterval } from "@/shared/lib/useDocumentVisible";
|
||||
import {
|
||||
refetchOnFocusWhenOld,
|
||||
useFocusedRefetchInterval,
|
||||
} from "@/shared/lib/useDocumentVisible";
|
||||
import {
|
||||
mergePresenceUpdate,
|
||||
parseLivePresenceEvent,
|
||||
@@ -95,7 +98,7 @@ export function usePresenceQuery(
|
||||
// (crashed clients). WS events handle the fast path. Pause on degraded
|
||||
// connections — HTTP presence calls fail anyway and consume relay quota.
|
||||
refetchInterval,
|
||||
refetchOnWindowFocus: true,
|
||||
refetchOnWindowFocus: refetchOnFocusWhenOld,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,10 @@ import type {
|
||||
UserStatusLookup,
|
||||
} from "@/shared/api/types";
|
||||
import { normalizePubkey } from "@/shared/lib/pubkey";
|
||||
import { useFocusedRefetchInterval } from "@/shared/lib/useDocumentVisible";
|
||||
import {
|
||||
refetchOnFocusWhenOld,
|
||||
useFocusedRefetchInterval,
|
||||
} from "@/shared/lib/useDocumentVisible";
|
||||
import { KIND_USER_STATUS } from "@/shared/constants/kinds";
|
||||
|
||||
function normalizePubkeys(pubkeys: string[]) {
|
||||
@@ -78,7 +81,7 @@ export function useUserStatusQuery(pubkeys: string[]) {
|
||||
},
|
||||
staleTime: 60_000,
|
||||
refetchInterval,
|
||||
refetchOnWindowFocus: true,
|
||||
refetchOnWindowFocus: refetchOnFocusWhenOld,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
126
desktop/src/shared/lib/focusReturnPolicy.test.mjs
Normal file
126
desktop/src/shared/lib/focusReturnPolicy.test.mjs
Normal file
@@ -0,0 +1,126 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { afterEach, test } from "node:test";
|
||||
|
||||
import {
|
||||
focusManager,
|
||||
QueryClient,
|
||||
QueryObserver,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
import {
|
||||
FOCUS_RETURN_STALE_TIME_MS,
|
||||
refetchOnFocusWhenOld,
|
||||
shouldRefetchOnFocus,
|
||||
} from "./useDocumentVisible.ts";
|
||||
|
||||
const NOW = 1_800_000_000_000;
|
||||
|
||||
afterEach(() => {
|
||||
focusManager.setFocused(undefined);
|
||||
});
|
||||
|
||||
async function focusRefetchCount({
|
||||
ageMs,
|
||||
status = "success",
|
||||
dataUpdatedAt = Date.now() - ageMs,
|
||||
}) {
|
||||
focusManager.setFocused(false);
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false } },
|
||||
});
|
||||
queryClient.mount();
|
||||
|
||||
const queryKey = ["focus-return-policy", ageMs, status, dataUpdatedAt];
|
||||
queryClient.setQueryData(queryKey, "cached", { updatedAt: dataUpdatedAt });
|
||||
if (status !== "success") {
|
||||
queryClient.getQueryCache().find({ queryKey }).setState({ status });
|
||||
}
|
||||
let fetchCount = 0;
|
||||
const observer = new QueryObserver(queryClient, {
|
||||
queryKey,
|
||||
queryFn: async () => {
|
||||
fetchCount += 1;
|
||||
return "refetched";
|
||||
},
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: refetchOnFocusWhenOld,
|
||||
staleTime: 5_000,
|
||||
});
|
||||
const unsubscribe = observer.subscribe(() => {});
|
||||
|
||||
focusManager.setFocused(true);
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
unsubscribe();
|
||||
queryClient.unmount();
|
||||
return fetchCount;
|
||||
}
|
||||
|
||||
test("focus return keeps recently fetched stale queries cached", async () => {
|
||||
assert.equal(
|
||||
await focusRefetchCount({ ageMs: FOCUS_RETURN_STALE_TIME_MS - 1_000 }),
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
test("focus return catches up genuinely old queries", async () => {
|
||||
assert.equal(
|
||||
await focusRefetchCount({ ageMs: FOCUS_RETURN_STALE_TIME_MS + 1 }),
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
test("focus return fetches a missing result", () => {
|
||||
assert.equal(
|
||||
shouldRefetchOnFocus({ status: "pending", dataUpdatedAt: 0 }, NOW),
|
||||
"always",
|
||||
);
|
||||
});
|
||||
|
||||
test("focus return retries an error with recent retained data", async () => {
|
||||
assert.equal(
|
||||
shouldRefetchOnFocus({ status: "error", dataUpdatedAt: NOW - 10_000 }, NOW),
|
||||
"always",
|
||||
);
|
||||
assert.equal(await focusRefetchCount({ ageMs: 10_000, status: "error" }), 1);
|
||||
});
|
||||
|
||||
test("focus return fetches after the clock moves backward", async () => {
|
||||
assert.equal(
|
||||
shouldRefetchOnFocus(
|
||||
{ status: "success", dataUpdatedAt: NOW + 1_000 },
|
||||
NOW,
|
||||
),
|
||||
"always",
|
||||
);
|
||||
assert.equal(
|
||||
await focusRefetchCount({
|
||||
ageMs: -1_000,
|
||||
dataUpdatedAt: Date.now() + 1_000,
|
||||
}),
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
test("focus return age-gates successful results at the exact boundary", () => {
|
||||
assert.equal(
|
||||
shouldRefetchOnFocus(
|
||||
{
|
||||
status: "success",
|
||||
dataUpdatedAt: NOW - FOCUS_RETURN_STALE_TIME_MS + 1,
|
||||
},
|
||||
NOW,
|
||||
),
|
||||
false,
|
||||
);
|
||||
assert.equal(
|
||||
shouldRefetchOnFocus(
|
||||
{
|
||||
status: "success",
|
||||
dataUpdatedAt: NOW - FOCUS_RETURN_STALE_TIME_MS,
|
||||
},
|
||||
NOW,
|
||||
),
|
||||
true,
|
||||
);
|
||||
});
|
||||
@@ -1,5 +1,23 @@
|
||||
import * as React from "react";
|
||||
|
||||
export const FOCUS_RETURN_STALE_TIME_MS = 5 * 60_000;
|
||||
|
||||
export function shouldRefetchOnFocus(
|
||||
state: { dataUpdatedAt: number; status: string },
|
||||
now = Date.now(),
|
||||
): boolean | "always" {
|
||||
if (state.status !== "success" || state.dataUpdatedAt <= 0) return "always";
|
||||
const ageMs = now - state.dataUpdatedAt;
|
||||
if (ageMs < 0) return "always";
|
||||
return ageMs >= FOCUS_RETURN_STALE_TIME_MS;
|
||||
}
|
||||
|
||||
export function refetchOnFocusWhenOld(query: {
|
||||
state: { dataUpdatedAt: number; status: string };
|
||||
}): boolean | "always" {
|
||||
return shouldRefetchOnFocus(query.state);
|
||||
}
|
||||
|
||||
export function isDocumentVisible(): boolean {
|
||||
if (typeof document === "undefined") return true;
|
||||
return document.visibilityState === "visible";
|
||||
|
||||
Reference in New Issue
Block a user