Archilyzer · Source

archilyzer

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

commit 5735aee7217614ccdbc47a18c0bfd4ded1d31336
parent e1f1b62c70435c18afe811e6489e991b95d306c0
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Thu,  2 Jul 2026 11:16:29 -0400

Phase 6b: provenance accent on search result cards

Carry each source archive's accent onto its result cards in hub mode, completing
the provenance throughline (shelf spine → filter group → result row). The data
context gains accentOf(origin): undefined in single-site mode (a stable module
constant, so the hot resultGroups memo doesn't churn), the site's accent in hub
mode. Each ResultGroup records its accent and the card renders it as a left edge.

Single-site is byte-identical (accentOf returns undefined → no style; verified by
share + virtualization e2e); hub e2e green.

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

Diffstat:
Mcommon/components/SearchDataContext.tsx | 31+++++++++++++++++++++++++++++--
Mcommon/components/TranscriptSearch.tsx | 14+++++++++++++-
2 files changed, 42 insertions(+), 3 deletions(-)

diff --git a/common/components/SearchDataContext.tsx b/common/components/SearchDataContext.tsx @@ -7,7 +7,13 @@ // hands the component one unified, origin-qualified view — so TranscriptSearch // itself stays mode-agnostic and reads everything from this context. -import { createContext, useContext, useMemo, type ReactNode } from "react"; +import { + createContext, + useCallback, + useContext, + useMemo, + type ReactNode, +} from "react"; import { useQueries } from "@tanstack/react-query"; import { useSummaries, type SummariesState } from "./summariesCache"; import { useSubsManifest } from "./subsCache"; @@ -47,8 +53,16 @@ export type SearchDataValue = { defaultGroupId: string; // Map a summary to its channel selection key (matches ChannelOption.key). channelKeyOf: (t: ChannelKeyable) => string; + // Provenance accent for a content origin ("" = same-origin). Returns a color + // only in hub mode (each federated site's accent); undefined in single-site + // mode, so no provenance marker renders. Stable reference in single-site mode. + accentOf: (origin: string) => string | undefined; }; +// Single-site mode has no provenance accents; a module constant keeps the +// reference stable so downstream memos (e.g. result groups) don't churn. +const NO_ACCENT = (): undefined => undefined; + const SearchDataContext = createContext<SearchDataValue | null>(null); export function useSearchData(): SearchDataValue { @@ -109,6 +123,7 @@ export function SingleSiteDataProvider({ children }: { children: ReactNode }) { groups, defaultGroupId, channelKeyOf: (t) => t.channel, + accentOf: NO_ACCENT, }), [summariesState, subsManifest, channels, groups, defaultGroupId], ); @@ -331,6 +346,17 @@ export function MultiSiteDataProvider({ [mergedManifest, summaries, loadedPages, pageDescriptors, summariesReady], ); + // origin → accent lookup for provenance in results. + const accentByOrigin = useMemo(() => { + const m = new Map<string, string>(); + for (const s of sites) if (s.accent) m.set(s.origin, s.accent); + return m; + }, [sites]); + const accentOf = useCallback( + (origin: string) => accentByOrigin.get(origin), + [accentByOrigin], + ); + const value = useMemo<SearchDataValue>( () => ({ summariesState, @@ -341,8 +367,9 @@ export function MultiSiteDataProvider({ // channelSlug was rewritten to the origin-qualified key above, so it IS // the selection key (matches ChannelOption.key). channelKeyOf: (t) => t.channelSlug, + accentOf, }), - [summariesState, subsManifest, channels, groups, defaultGroupId], + [summariesState, subsManifest, channels, groups, defaultGroupId, accentOf], ); return ( diff --git a/common/components/TranscriptSearch.tsx b/common/components/TranscriptSearch.tsx @@ -75,6 +75,9 @@ type ResultGroup = { isLivestream: boolean; ageRestricted: boolean; hits: LayerHit[]; + // Provenance accent of the source origin (hub mode only); undefined + // single-site, so no marker renders. + accent?: string; }; const DEFAULT_MAX_HITS = 500; @@ -262,6 +265,7 @@ export default function TranscriptSearch() { channels: channelModel, groups: manifestGroups, channelKeyOf, + accentOf, } = useSearchData(); const { summaries, loadedPages, pageCount, summariesReady } = summariesState; @@ -561,6 +565,7 @@ export default function TranscriptSearch() { isLivestream: t.isLivestream, ageRestricted: t.ageRestricted, hits: [], + accent: accentOf(splitId(t.slug).origin), }); } return out; @@ -579,10 +584,11 @@ export default function TranscriptSearch() { isLivestream: t.isLivestream, ageRestricted: t.ageRestricted, hits: hitsBySlug.get(t.slug) ?? [], + accent: accentOf(splitId(t.slug).origin), }); } return out; - }, [hasActiveQuery, transcripts, treeProgress, passesFilter]); + }, [hasActiveQuery, transcripts, treeProgress, passesFilter, accentOf]); const leafStates = treeProgress?.leafStates ?? new Map<string, LeafState>(); const groupStates = @@ -1982,6 +1988,12 @@ function ResultCard({ data-result-slug={group.slug} data-card-header="" className="border border-border rounded-lg overflow-hidden bg-card" + // Provenance: a left edge in the source archive's accent (hub mode only). + style={ + group.accent + ? { borderLeftColor: group.accent, borderLeftWidth: "3px" } + : undefined + } > <Button type="button"