commit 79ca2b0f4b0d5309ee6b0bb1385ccf9efc6405ad
parent cac99e26d8c7c8917c911623256d21c593bff02c
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Sat, 26 Sep 2026 02:29:23 -0400
hub: retry: false on the federated subs manifest — a member with no live chat is ready ~1 s sooner
Readiness waits for the subs query to settle, and a member with no live
chat answers 404; the client default (one retry after ~1 s) held its chip
at "loading" for that second. The e2e counts one subs request per member
when the chip turns ready (two before). Release 9 C2 re-read LOW.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
2 files changed, 32 insertions(+), 5 deletions(-)
diff --git a/common/components/SearchDataContext.tsx b/common/components/SearchDataContext.tsx
@@ -343,11 +343,17 @@ export function MultiSiteDataProvider({
// merged below only once the archive is ready, so all of an archive's
// contributions land in the search in one re-run. A posts 404 (no posts
// corpus) resolves to an empty manifest, not a failure.
+ //
+ // The subs manifest is asked for ONCE (`retry: false`). A member with no live
+ // chat has none, and its 404 is its answer: the client default (one retry,
+ // after ~1 s) only held that archive at "loading" for another second, since
+ // readiness waits for this query to settle and an error settles it.
const subsQueries = useQueries({
queries: sites.map((s) => ({
queryKey: ["subs-manifest", s.origin],
queryFn: () => readerFor(s.origin).readSubsSiteManifest(),
enabled: inScope(s),
+ retry: false,
})),
});
const postsQueries = useQueries({
diff --git a/export/e2e-hub/federated-search.spec.ts b/export/e2e-hub/federated-search.spec.ts
@@ -7,6 +7,8 @@ import { expect, test, type Page, type Route } from "@playwright/test";
// says "1 of 2 archives answered" with a Retry, and the healthy member's results
// still render), Retry, progressive readiness (a slow member does not hold the
// fast one's results back), and a card naming its source archive in text.
+// Release 10 (L1): a member with no live chat is ready on its first subs
+// answer (a 404 is not retried).
const ORIGIN_A = "http://localhost:4598";
const ORIGIN_B = "http://localhost:4599";
@@ -62,16 +64,17 @@ function pageOf(m: Member) {
}
// Every request to a member origin is counted; `pages` decides how its
-// summaries page is answered.
+// summaries page is answered, `subs` whether it has a subs manifest at all.
type PageMode = "ok" | "abort" | "hold";
type MemberMock = {
requests: string[];
pages: PageMode;
+ subs: "ok" | "missing";
held: Route[];
};
async function mockMember(page: Page, m: Member): Promise<MemberMock> {
- const mock: MemberMock = { requests: [], pages: "ok", held: [] };
+ const mock: MemberMock = { requests: [], pages: "ok", subs: "ok", held: [] };
await page.route(`${m.origin}/**`, async (route) => {
const url = new URL(route.request().url());
mock.requests.push(url.pathname);
@@ -86,7 +89,7 @@ async function mockMember(page: Page, m: Member): Promise<MemberMock> {
}
return fulfillJson(route, pageOf(m));
}
- if (url.pathname === "/subs/manifest.json") {
+ if (url.pathname === "/subs/manifest.json" && mock.subs === "ok") {
return fulfillJson(route, {
version: 4,
channels: [],
@@ -95,8 +98,8 @@ async function mockMember(page: Page, m: Member): Promise<MemberMock> {
generatedAt: "2026-01-01T00:00:00.000Z",
});
}
- // No posts, no aliases: a clean 404 (a member without a posts corpus is
- // not a failure).
+ // No posts, no aliases (and, when `subs` is "missing", no live chat): a
+ // clean 404 (a member without that corpus is not a failure).
return route.fulfill({ status: 404, headers: CORS, body: "" });
});
return mock;
@@ -272,4 +275,22 @@ test.describe("hub federated search — scope, per-archive state, attribution",
await expect(status(page)).toContainText("Origin B did not");
await expect(resultFrom(page, C)).toHaveCount(1);
});
+
+ test("a member with no live chat is ready on its first subs answer: a 404 is not retried", async ({
+ page,
+ }) => {
+ const mocks = await setup(page);
+ mocks.b.subs = "missing";
+ await page.goto("/");
+
+ // Ready means every feed of the archive has settled, subs included. With a
+ // retry, the 404 settled only on the second answer (~1 s later), so the
+ // chip turned ready with two requests on record; now there is one.
+ await expect(chip(page, B)).toHaveAttribute("data-status", "ready");
+ await expect(resultFrom(page, B)).toHaveCount(1);
+ const subsAsks = (m: MemberMock) =>
+ m.requests.filter((p) => p === "/subs/manifest.json").length;
+ expect(subsAsks(mocks.b)).toBe(1);
+ expect(subsAsks(mocks.a)).toBe(1);
+ });
});