Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 2efb49c6bd4d8acc72684cd9e25f9c44ad86bbd5
parent 556dd6e71302e64ed7fda62410df421fc7ee8f10
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Fri, 25 Sep 2026 16:40:01 -0400

common: a search restored from localStorage waits for the visitor

The session restored on load (query, filters, channel selection, profile)
is committed as before, so the profile, share-link and divergence logic see
it unchanged, but a restored non-empty query is HELD: the pipeline does not
run until the visitor presses Search / Enter or loads a profile. The results
area shows the browse listing under the restored filters and the bar shows
its existing "Press Enter or click Search to apply" line. A query on the URL
(qt=, legacy q=) is a shared link and still runs.

export/e2e/restore-no-refire.spec.ts proves no transcript shard is fetched
on load and that submitting fetches one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Diffstat:
Mcommon/components/SearchSessionContext.tsx | 30+++++++++++++++++++++++++-----
Aexport/e2e/restore-no-refire.spec.ts | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 150 insertions(+), 5 deletions(-)

diff --git a/common/components/SearchSessionContext.tsx b/common/components/SearchSessionContext.tsx @@ -393,7 +393,15 @@ function useSearchSessionState() { () => canonicalHash(committedRoot), [committedRoot], ); - const queryDirty = draftHash !== committedHash; + // A query restored from localStorage is committed (so the profile, share + // link and divergence logic all see the session exactly as it was left) but + // HELD: the pipeline does not run it until the visitor acts — Search / Enter, + // or a profile load. A restored query re-ran on every visit, refetching + // transcript shards (up to 8 MB each) on a cold device for a search nobody + // asked for this time. A query that arrives on the URL (`qt=`, legacy `q=`) + // is never held: a shared link is the visitor asking. + const [runHeld, setRunHeld] = useState(false); + const queryDirty = runHeld || draftHash !== committedHash; const [draftExcludedChannels, setDraftExcludedChannels] = useState< Set<string> @@ -789,9 +797,11 @@ function useSearchSessionState() { [committedTags, postScopeSlugs], ); + // False while a restored query is held (see `runHeld`): the results area + // shows the browse listing under the restored filters, and nothing fetches. const hasActiveQuery = useMemo( - () => isNodeActive(committedRoot), - [committedRoot], + () => !runHeld && isNodeActive(committedRoot), + [committedRoot, runHeld], ); // ─── Run the query tree ──────────────────────────────────────────────────── @@ -835,6 +845,7 @@ function useSearchSessionState() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [ committedHash, + hasActiveQuery, transcripts, filterKey, needsChatManifests, @@ -1201,6 +1212,8 @@ function useSearchSessionState() { } let resolvedRoot = rootFromUrl; + // True only when the query came from the stored snapshot, never the URL. + let holdRestored = false; if (!initialExcluded) { // Fall back to stored profile / working snapshot. const snapshot = @@ -1223,13 +1236,15 @@ function useSearchSessionState() { initialTags ??= new Set(snapshot?.tg ?? []); if (!resolvedRoot && snapshot?.query) { resolvedRoot = parseRoot(snapshot.query); + holdRestored = resolvedRoot != null && isNodeActive(resolvedRoot); } } if (resolvedRoot) { setDraftRoot(resolvedRoot); setCommittedRoot(resolvedRoot); - setSearchExecuted(true); + if (holdRestored) setRunHeld(true); + else setSearchExecuted(true); } setDraftExcludedChannels(initialExcluded); @@ -1259,7 +1274,9 @@ function useSearchSessionState() { if (csRaw) { setChartShape(parseChartShape(csRaw)); } else if (wantsChart) { - const active = resolvedRoot ? isNodeActive(resolvedRoot) : false; + // A held query has no results to chart until it runs. + const active = + resolvedRoot && !holdRestored ? isNodeActive(resolvedRoot) : false; setChartShape(defaultChartShape(active ? "search" : "metadata")); } if (wantsChart) setView("chart"); @@ -1342,6 +1359,7 @@ function useSearchSessionState() { const commitSearch = () => { setSearchExecuted(true); + setRunHeld(false); setCommittedRoot(draftRoot); const nextSnapshot = buildDraftSnapshot(); const current = loadStoredState() ?? emptyStoredState(); @@ -1403,6 +1421,8 @@ function useSearchSessionState() { const applySnapshot = useCallback( (snapshot: FilterSnapshot | null) => { + // Loading a profile commits it, which runs it — as it always has. + setRunHeld(false); applyDraftSnapshot(snapshot); const excluded = snapshotToExcluded( snapshot, diff --git a/export/e2e/restore-no-refire.spec.ts b/export/e2e/restore-no-refire.spec.ts @@ -0,0 +1,125 @@ +import { expect, test, type Page } from "@playwright/test"; +import { installRoutes } from "./helpers"; + +// Release 8 slice E. The search session restored from localStorage loads the +// query and the filters but does NOT run the search on first load: a restored +// query used to re-run the pipeline on every visit, refetching transcript +// shards (up to 8 MB each on a cold device) for a search the visitor had not +// asked for this time. The visitor runs it — Search / Enter. A query on the +// URL (`qt=`) is a shared link, i.e. the visitor asking, and still runs. + +const STORAGE_KEY = "ytdlp-tb:export-filters"; +const SHARD = /\/transcripts\/[^/]+\/page-\d+\.json$/; + +function countShards(page: Page): { n: number } { + const seen = { n: 0 }; + page.on("request", (req) => { + if (SHARD.test(new URL(req.url()).pathname)) seen.n += 1; + }); + return seen; +} + +const leafInput = (page: Page) => + page.locator('input[data-testid^="leaf-query-"]').first(); + +// A query that hits every fixture transcript (each has an "alpha line" cue), +// in the app's own serialization (`stringifyRoot`: the qt= JSON). +const ALPHA_TREE = { k: "g", o: "AND", c: [{ k: "l", q: "alpha", s: "transcripts" }] }; + +// Pre-seed the session a previous visit would have left: the query plus a +// filter (Livestreams off). Written via page.evaluate on a throwaway first load +// — not addInitScript, which would re-fire on every navigation. Nothing has +// searched in this browser context, so the IndexedDB layer cache is empty and a +// search that runs MUST fetch shards. +async function seedStoredSearch(page: Page) { + await page.goto("/"); + await page.evaluate( + ({ key, value }) => window.localStorage.setItem(key, value), + { + key: STORAGE_KEY, + value: JSON.stringify({ + v: 1, + working: { + channels: { included: [], excluded: [] }, + nol: true, + query: JSON.stringify(ALPHA_TREE), + }, + profiles: {}, + activeProfileName: null, + }), + }, + ); +} + +test.describe("restored search waits for the visitor", () => { + test.beforeEach(async ({ page }) => { + await installRoutes(page); + }); + + test("a restored query loads into the form, fetches no shard, and runs on submit", async ({ + page, + }) => { + const shards = countShards(page); + await seedStoredSearch(page); + + // A fresh visit with no query on the URL: the session comes from storage. + shards.n = 0; + await page.goto("/"); + // The settings load: the query is in the input, the filter is restored… + await expect(leafInput(page)).toHaveValue("alpha"); + await expect( + page.getByRole("checkbox", { name: "Livestreams" }), + ).not.toBeChecked(); + // …and the bar says what the visitor has to do, as for any unapplied edit. + await expect(page.getByTestId("search-submit")).toHaveAttribute( + "data-dirty", + "true", + ); + await expect( + page.getByText("Press Enter or click Search to apply"), + ).toBeVisible(); + // The results area is the browse listing under the restored filters. + await expect(page.getByTestId("results-summary")).toHaveText( + "All videos (3)", + ); + await expect(page.getByTestId("browse-hint")).toBeVisible(); + // Give a would-be pipeline every chance to start. + await page.waitForTimeout(1_500); + expect(shards.n).toBe(0); + + // The visitor runs it. + const fetched = page.waitForRequest( + (req) => SHARD.test(new URL(req.url()).pathname), + { timeout: 10_000 }, + ); + await leafInput(page).press("Enter"); + await fetched; + await expect(page.getByTestId("results-summary")).toContainText( + "Matching videos", + ); + await expect(page.getByTestId("search-submit")).toHaveAttribute( + "data-dirty", + "false", + ); + }); + + test("a query on the URL (a shared link) still runs on load", async ({ + page, + }) => { + const shards = countShards(page); + // A stored session is present too: the URL query is not held, it runs. + await seedStoredSearch(page); + const link = `/?qt=${encodeURIComponent(JSON.stringify(ALPHA_TREE))}`; + + shards.n = 0; + await page.goto(link); + await expect(page.getByTestId("results-summary")).toContainText( + "Matching videos", + ); + await expect.poll(() => shards.n).toBeGreaterThan(0); + await expect(page.getByTestId("search-submit")).toHaveAttribute( + "data-dirty", + "false", + ); + }); +});