'use client'; import { createContext, type ReactNode, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import type { ProvenanceEntry } from '@/lib/types'; /** What the drawer needs to explain one displayed value. `fallback` = the inline provenance the caller already has. */ export interface EvidenceTarget { slug: string; property: string; /** The displayed value (raw) — formatted in the drawer with `fmtValue(value, property)` unless `display` is given. */ value?: unknown; display?: string; unit?: string | null; label?: string; fallback?: ProvenanceEntry | null; entity?: { name?: string; entity_type?: string } | null; } interface Ctx { target: EvidenceTarget | null; open: (t: EvidenceTarget) => void; close: () => void; } const EvidenceCtx = createContext({ target: null, open: () => undefined, close: () => undefined }); const HASH_RE = /^#evidence=([^:]+):(.+)$/; /** Mounted once in the root layout. Mirrors the open target to `#evidence=:` so a drawer can be linked. */ export function EvidenceProvider({ children }: { children: ReactNode }) { const [target, setTarget] = useState(null); const open = useCallback((t: EvidenceTarget) => { setTarget(t); try { history.replaceState(null, '', `#evidence=${encodeURIComponent(t.slug)}:${encodeURIComponent(t.property)}`); } catch { /* ignore */ } }, []); const close = useCallback(() => { setTarget(null); try { if (HASH_RE.test(location.hash)) history.replaceState(null, '', location.pathname + location.search); } catch { /* ignore */ } }, []); useEffect(() => { const m = HASH_RE.exec(location.hash); if (m) setTarget({ slug: decodeURIComponent(m[1] as string), property: decodeURIComponent(m[2] as string) }); }, []); const value = useMemo(() => ({ target, open, close }), [target, open, close]); return {children}; } export function useEvidence(): Ctx { return useContext(EvidenceCtx); }