"use client"; // Client fetch for a site's shipped /tags.json — the curated per-video tag // vocabulary (lib/curatedTags.ts) with the per-site counts compose-site wrote. // Mirrors aliasesCache / summariesCache exactly, including its answer to // absence, because the two documents have the same shape of optionality: a // site publishes one only when it has something to publish. // // A 404 is the NORMAL state here in two different ways, and neither is an // error: compose-site writes no file at all for a site with no visible, // non-zero-count tag, and a site built before corpus spec 4 has never heard of // tags. Both resolve to an empty list, which the chip row reads as "offer no // chips" — the operator's rule that a site's row is built only from what that // site actually publishes, with no static list anywhere. import { useQuery } from "@tanstack/react-query"; import { ArchiveHttpError } from "../lib/archive/reader"; import { readerFor } from "../lib/archive/readers"; import { coercePublishedTags } from "../lib/publishedTags"; import type { PublishedTag } from "../lib/curatedTags"; const EMPTY: PublishedTag[] = []; // ANY status the archive itself returned resolves empty — a 404 because the // site ships no tags, but a 500 or a 403 the same way. A TRANSPORT failure is // different and is rethrown: it is not an answer at all, and with // staleTime: Infinity below a swallowed blip would mean "this site has no // tags" for the rest of the session. Same reasoning as fetchAliases. export async function fetchTags(origin = ""): Promise { try { return coercePublishedTags(await readerFor(origin).readTags()); } catch (err) { if (err instanceof ArchiveHttpError) return []; throw err; } } export function useCuratedTags(origin = ""): PublishedTag[] { const { data } = useQuery({ queryKey: ["curated-tags", origin], queryFn: () => fetchTags(origin), staleTime: Infinity, // static per export build }); return data ?? EMPTY; }