Archilyzer · Source

archilyzer

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

commit 695a44ef43a277814b263206f3495d520eefd251
parent ba5ae2347fc02867852ddc27aa079bd6b16db7f5
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Tue, 29 Sep 2026 20:52:10 -0400

common: a clear screen until the first Search — the results area renders nothing until the visitor asks in this page life (a Search, Enter, a Filters Apply, a profile load, or a link that carries a query or a filter); the bar's "Press Enter or click Search to apply" line shows meanwhile; ranThisPageLife feeds a reactive searchedThisPageLife, and the dead searchExecuted state is deleted

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Mcommon/components/SearchBar.tsx | 10++++++++--
Mcommon/components/SearchResults.tsx | 8++++++++
Mcommon/components/SearchSessionContext.tsx | 60+++++++++++++++++++++++++++++++++++++++++-------------------
3 files changed, 57 insertions(+), 21 deletions(-)

diff --git a/common/components/SearchBar.tsx b/common/components/SearchBar.tsx @@ -30,6 +30,7 @@ export default function SearchBar({ nav }: { nav?: ReactNode }) { groupStates, queryDirty, filtersDirty, + searchedThisPageLife, handleResetLayers, layersResetDisabled, hasSubs, @@ -79,6 +80,11 @@ export default function SearchBar({ nav }: { nav?: ReactNode }) { draftTags, ]); + // The line under the bar says what to do: after an edit that is not applied + // yet, and before the first Search of the page life, when the results area + // is empty and waits for it. + const promptApply = queryDirty || filtersDirty || !searchedThisPageLife; + const submit = ( <Button type="submit" @@ -220,12 +226,12 @@ export default function SearchBar({ nav }: { nav?: ReactNode }) { {/* One status line under the bar instead of two hints competing for room inside it — and a SIBLING of the form, so the pinned block is never more than the input row plus the chips. */} - {(queryDirty || filtersDirty || hasSubs) && ( + {(promptApply || hasSubs) && ( <p className="flex flex-wrap items-center gap-x-3 gap-y-0.5 text-xs"> {/* The site's accent, not the warning hue: an unapplied edit is the next step, not a fault. `--brand` clears 4.5:1 on every base's page ground (lib/brand.ts MIN_ACCENT_CONTRAST). */} - {(queryDirty || filtersDirty) && ( + {promptApply && ( <span className="text-brand"> Press Enter or click Search to apply </span> diff --git a/common/components/SearchResults.tsx b/common/components/SearchResults.tsx @@ -55,6 +55,7 @@ const CARD_HIT_CAP = 8; export default function SearchResults() { const { + searchedThisPageLife, hasActiveQuery, resultGroups, totalHits, @@ -112,6 +113,13 @@ export default function SearchResults() { // results" and the footer sit underneath it and cannot be reached. const selectionBar = view !== "chart" && resultGroups.length > 0 && selectedCount > 0; + // A clear screen until the visitor asks: no count, no controls, no chart and + // no listing — the bar, the page's intro and the footer, with the bar's + // "Press Enter or click Search" line saying what to do. An empty Search shows + // every video; a link with a query or a filter shows its results on load. + // `resultGroups` is still computed underneath. + if (!searchedThisPageLife) return null; + return ( <section data-testid="results-section" diff --git a/common/components/SearchSessionContext.tsx b/common/components/SearchSessionContext.tsx @@ -52,6 +52,7 @@ import { } from "../lib/availability"; import { useUrlParams, writeUrlParams } from "./urlState"; import { + FILTER_URL_KEYS_V1, buildShareSearchParams, hasShareV1, parseShareV1, @@ -197,12 +198,15 @@ export type GroundingMode = "search" | "selection"; // export-filter parser. const SELECTION_KEY = "ytdlp-tb:selection"; -// Whether a search has run in this page life: a commit, a profile load, or a -// query arriving on the URL. Module state, so it survives client-side -// navigation and resets on a reload or a new visit. A stored query is held +// Whether the visitor has asked in this page life: a commit (Search, Enter, a +// Filters Apply), a profile load, or a link that carries a query or a filter +// (`urlAsks`). Module state, so it survives client-side navigation and resets +// on a reload or a new visit. It decides two things. A stored query is held // (see `runHeld`) only on the page life's FIRST load — the hub mounts a fresh // provider per route, and `/` → `/ask` must keep grounding in the search the -// visitor just ran there. +// visitor just ran there. And the results area shows nothing until it is set +// (`searchedThisPageLife`, its reactive copy): until the visitor asks, the page +// is the bar, the intro and the footer. let ranThisPageLife = false; function loadSelection(): string[] { @@ -257,6 +261,22 @@ function urlHasAnyFilterParam(): boolean { return FILTER_URL_KEYS.some((k) => p.has(k)); } +// A link that carries a query (`qt`, legacy `q` / `m`) or a filter (`tg`, the +// share-link keys, the legacy filter keys) is the visitor asking: it runs on +// load and shows its results, as a Search would. A video (`v`, `t`) or a +// chart's shape (`view`, `cs`) alone is not a search. +const ASKING_URL_KEYS: readonly string[] = [ + "qt", + "q", + "tg", + ...FILTER_URL_KEYS, + ...FILTER_URL_KEYS_V1, +]; + +function urlAsks(params: URLSearchParams): boolean { + return ASKING_URL_KEYS.some((k) => params.has(k)); +} + // Resolve a stored snapshot's per-channel deltas (plus group defaults) into // the concrete set of EXCLUDED channel names. function snapshotToExcluded( @@ -457,18 +477,14 @@ function useSearchSessionState() { const [view, setView] = useState<"results" | "chart">("results"); const [chartShape, setChartShape] = useState<ChartShape | null>(null); - // Tracks whether the user has committed at least once this session. Used - // by the placeholder copy below — until the user has searched, we show a - // "tip" hint rather than the "no matching videos" empty state. - const [, setSearchExecuted] = useState<boolean>(() => { - if (typeof window === "undefined") return true; - const params = new URLSearchParams(window.location.search); - return !( - params.has("v") && - !params.has("qt") && - (params.get("q") ?? "") === "" - ); - }); + // The reactive copy of `ranThisPageLife`, which stays the source of truth + // across remounts: a mount later in the page life starts from it, and every + // place that sets it sets this too. False until the visitor asks, and the + // results area renders nothing until then. False on the server, where the + // module variable is never set, so the first client render matches. + const [searchedThisPageLife, setSearchedThisPageLife] = useState( + () => ranThisPageLife, + ); // Defer rendering of the QueryBuilder to the client. The builder's leaf // IDs come from a module-scoped counter that's necessarily out of sync @@ -1273,9 +1289,11 @@ function useSearchSessionState() { setDraftRoot(resolvedRoot); setCommittedRoot(resolvedRoot); if (holdRestored) setRunHeld(true); - else setSearchExecuted(true); - if (rootFromUrl && isNodeActive(rootFromUrl)) ranThisPageLife = true; } + // After `holdRestored` is decided, so a query restored beside a + // filter-only link is still held on this first load. + if (urlAsks(params)) ranThisPageLife = true; + setSearchedThisPageLife(ranThisPageLife); setDraftExcludedChannels(initialExcluded); setDraftNov(initialNov); @@ -1388,9 +1406,9 @@ function useSearchSessionState() { }, [persistUiCollapse]); const commitSearch = () => { - setSearchExecuted(true); setRunHeld(false); ranThisPageLife = true; + setSearchedThisPageLife(true); setCommittedRoot(draftRoot); const nextSnapshot = buildDraftSnapshot(); const current = loadStoredState() ?? emptyStoredState(); @@ -1455,6 +1473,7 @@ function useSearchSessionState() { // Loading a profile commits it, which runs it — as it always has. setRunHeld(false); ranThisPageLife = true; + setSearchedThisPageLife(true); applyDraftSnapshot(snapshot); const excluded = snapshotToExcluded( snapshot, @@ -2040,6 +2059,9 @@ function useSearchSessionState() { hitBatchSize, setHitLimit, // ── Results ── + // False until the visitor asks in this page life; nothing in the results + // area renders until then (see `ranThisPageLife`). + searchedThisPageLife, hasActiveQuery, resultGroups, totalHits,