commit 9af7e7bf1ffa687c18dddab1d60e69e7f43ca145
parent 99e42237f2067d2aeaa428f56b661c7f97587b7b
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Wed, 1 Jul 2026 23:41:37 -0400
Phase 3: SearchDataContext provider (data source behind context)
TranscriptSearch now reads its summaries + subs-manifest feeds from a
SearchData provider instead of calling useSummaries()/useSubsManifest()
directly, so the component becomes mode-agnostic. SingleSiteDataProvider is a
thin same-origin wrapper (origin=""), leaving single-site behavior identical;
a MultiSiteDataProvider that merges feeds across origins lands with the
channelKey work in the next phase.
- common/components/SearchDataContext.tsx: useSearchData() + SingleSiteDataProvider
- TranscriptSearch.tsx: consume useSearchData() for summariesState + subsManifest
- export/app/page.tsx: wrap TranscriptSearch in SingleSiteDataProvider
Verified: 29/29 data-path e2e (browse-all, live-chat, modal-open, query-tree,
pwa-search, filter-profile-persistence) green; all packages typecheck.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat:
3 files changed, 51 insertions(+), 6 deletions(-)
diff --git a/common/components/SearchDataContext.tsx b/common/components/SearchDataContext.tsx
@@ -0,0 +1,41 @@
+"use client";
+
+// Data source for TranscriptSearch. In single-site mode this is a thin wrapper
+// over the same summaries/subs hooks the component used to call directly, so
+// behavior is byte-for-byte unchanged. In hub mode (added in a later phase) a
+// MultiSiteDataProvider merges these feeds across many origins and hands the
+// component one unified, origin-qualified view — so TranscriptSearch itself
+// stays mode-agnostic and reads everything from this context.
+
+import { createContext, useContext, type ReactNode } from "react";
+import { useSummaries, type SummariesState } from "./summariesCache";
+import { useSubsManifest } from "./subsCache";
+import type { SubsManifest } from "../lib/manifest";
+
+export type SearchDataValue = {
+ // Summaries + cross-channel manifest. Merged across origins in hub mode.
+ summariesState: SummariesState;
+ // Cross-channel subs manifest (merged across origins in hub mode), or null.
+ subsManifest: SubsManifest | null;
+};
+
+const SearchDataContext = createContext<SearchDataValue | null>(null);
+
+export function useSearchData(): SearchDataValue {
+ const value = useContext(SearchDataContext);
+ if (!value) {
+ throw new Error("useSearchData must be used within a SearchData provider");
+ }
+ return value;
+}
+
+// Single-site data source: the built-in export site (same-origin, origin="").
+export function SingleSiteDataProvider({ children }: { children: ReactNode }) {
+ const summariesState = useSummaries("");
+ const subsManifest = useSubsManifest("").data ?? null;
+ return (
+ <SearchDataContext.Provider value={{ summariesState, subsManifest }}>
+ {children}
+ </SearchDataContext.Provider>
+ );
+}
diff --git a/common/components/TranscriptSearch.tsx b/common/components/TranscriptSearch.tsx
@@ -4,8 +4,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useWindowVirtualizer } from "@tanstack/react-virtual";
import { usePlayer } from "./PlayerProvider";
import { VirtualRow } from "./VirtualRow";
-import { useSummaries } from "./summariesCache";
-import { useChannelSubsManifests, useSubsManifest } from "./subsCache";
+import { useChannelSubsManifests } from "./subsCache";
+import { useSearchData } from "./SearchDataContext";
import type { LayerHit } from "./searchPipeline";
import {
runQueryTree,
@@ -257,15 +257,16 @@ export default function TranscriptSearch() {
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
+ // Data feeds come from the SearchData provider (single-site or, in hub mode,
+ // merged across origins) so this component is mode-agnostic.
+ const { summariesState, subsManifest } = useSearchData();
const { manifest, summaries, loadedPages, pageCount, summariesReady } =
- useSummaries();
+ summariesState;
const transcripts: Summary[] | null = summariesReady ? summaries : null;
// We need subs manifests whenever any leaf has scope=chat OR a chat leaf
// could be added (we eagerly load when the tab existed in v1). For v2 we
// only load subs manifests when a chat leaf is actually in the tree.
- const subsManifestQuery = useSubsManifest();
- const subsManifest = subsManifestQuery.data ?? null;
const liveChatTotalCount = subsManifest?.liveChatTotalCount ?? 0;
const hasSubs = subsManifest !== null && liveChatTotalCount > 0;
const draftHasChatLeaf = useMemo(
diff --git a/export/app/page.tsx b/export/app/page.tsx
@@ -2,6 +2,7 @@ import { countTranscripts } from "yt-dlp-transcript-common/lib/transcripts";
import { PlayerProvider } from "yt-dlp-transcript-common/components/PlayerProvider";
import TranscriptModal from "yt-dlp-transcript-common/components/TranscriptModal";
import TranscriptSearch from "yt-dlp-transcript-common/components/TranscriptSearch";
+import { SingleSiteDataProvider } from "yt-dlp-transcript-common/components/SearchDataContext";
import { currentSite } from "./lib/site";
import NewSinceLastVisit from "./components/NewSinceLastVisit";
@@ -22,7 +23,9 @@ export default async function Home() {
)}
</div>
<NewSinceLastVisit siteId={settings.siteId} count={count} />
- <TranscriptSearch />
+ <SingleSiteDataProvider>
+ <TranscriptSearch />
+ </SingleSiteDataProvider>
</div>
<TranscriptModal />
</PlayerProvider>