commit 5b8456aff59dd3ee8c4c599d4cc4416ee9f3963f
parent 16547480221abf17ef2b8494b17eeebbccc29ba0
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Tue, 22 Sep 2026 16:11:01 -0400
ask: the hub's /ask mounts its own search session
AskHub wrapped AskChat in MultiSiteDataProvider alone, but the chat's
retrieval goes through useSearchSession (useAskChat reads the committed
query tree, the filters and runQueryTree from it). In single-site mode
the workspace shell provides that session; hub mode is a pass-through,
so nothing did — and `next build` with INSTANCE_MODE=hub threw while
prerendering /ask and lost the route, which is why e2e:2origin has been
red. The fix is the provider HubHome already gets through
TranscriptSearch, not an opt-out of prerendering.
A hub spec loads /ask and asserts the composer is there, so the rendered
page is guarded as well as the build.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
2 files changed, 64 insertions(+), 1 deletion(-)
diff --git a/export/app/ask/AskHub.tsx b/export/app/ask/AskHub.tsx
@@ -9,6 +9,7 @@ import {
MultiSiteDataProvider,
type FederatedSite,
} from "yt-dlp-transcript-common/components/SearchDataContext";
+import { SearchSessionProvider } from "yt-dlp-transcript-common/components/SearchSessionContext";
import { useRegistry } from "yt-dlp-transcript-common/components/siteRegistry";
import AskChat from "./AskChat";
@@ -26,7 +27,16 @@ export default function AskHub() {
return (
<MultiSiteDataProvider sites={federated}>
- <AskChat />
+ {/* AskChat's retrieval runs through the SAME session the results page
+ uses (useAskChat calls useSearchSession for the query tree, the
+ filters and runQueryTree), so the provider has to be here. In
+ single-site mode the workspace shell supplies it; hub mode bypasses
+ that shell, and without this the hub build's prerender of /ask threw
+ on the missing context — the whole route, not just the chat. This is
+ the same nesting HubHome gets through TranscriptSearch. */}
+ <SearchSessionProvider>
+ <AskChat />
+ </SearchSessionProvider>
</MultiSiteDataProvider>
);
}
diff --git a/export/e2e-hub/ask.spec.ts b/export/e2e-hub/ask.spec.ts
@@ -0,0 +1,53 @@
+import { expect, test, type Page } from "@playwright/test";
+
+// The hub's /ask route.
+//
+// Hub mode bypasses the workspace shell that mounts the search session for a
+// single site, so AskHub has to supply its own — AskChat's retrieval reads the
+// committed query tree and filters out of it. When it did not, `next build`
+// with INSTANCE_MODE=hub threw while prerendering /ask ("useSearchSession must
+// be used within a SearchSessionProvider") and took the whole route with it.
+// This spec is the cheap guard on the rendered page; the build itself is the
+// other half, covered by the 2-origin suite which builds the hub for real.
+
+// No built-in pool: this route has to stand up on a hub with an empty shelf,
+// which is what a fresh hub is.
+async function stubBuiltins(page: Page) {
+ await page.route("**/hub-sites.json", (r) =>
+ r.fulfill({
+ status: 200,
+ contentType: "application/json",
+ headers: { "access-control-allow-origin": "*" },
+ body: "[]",
+ }),
+ );
+}
+
+test.describe("hub /ask", () => {
+ test("renders the chat with its composer", async ({ page }) => {
+ const errors: string[] = [];
+ page.on("pageerror", (e) => errors.push(String(e)));
+
+ await stubBuiltins(page);
+ await page.goto("/ask");
+
+ // The route's own header — hub mode says "the federation", not
+ // "the transcripts".
+ await expect(
+ page.getByRole("heading", { name: /Ask a question about the federation/ }),
+ ).toBeVisible();
+
+ // The composer is AskChat's whole point, and it only renders once the
+ // session context AskHub now provides is there.
+ const box = page.getByRole("textbox").first();
+ await expect(box).toBeVisible();
+ await expect(box).toHaveAttribute(
+ "placeholder",
+ /Ask about the transcripts|Loading transcripts/,
+ );
+ await expect(page.getByRole("button", { name: "Ask" })).toBeVisible();
+
+ // A missing provider surfaces as a client-side throw, not a blank page.
+ expect(errors).toEqual([]);
+ });
+});