SPB Git forge

spb/ai-atlas

Public
41commits 1branches 0releases
4.6 MBsize
maindefault branch
12 days agolast push
HTML 77.2% TypeScript 10.5% Python 9.6% JavaScript 2.5%
2.0 KB · 57 lines tsx
Raw Blame History
1'use client';2import { createContext, type ReactNode, useCallback, useContext, useEffect, useMemo, useState } from 'react';3import type { ProvenanceEntry } from '@/lib/types';45/** What the drawer needs to explain one displayed value. `fallback` = the inline provenance the caller already has. */6export interface EvidenceTarget {7  slug: string;8  property: string;9  /** The displayed value (raw) — formatted in the drawer with `fmtValue(value, property)` unless `display` is given. */10  value?: unknown;11  display?: string;12  unit?: string | null;13  label?: string;14  fallback?: ProvenanceEntry | null;15  entity?: { name?: string; entity_type?: string } | null;16}1718interface Ctx {19  target: EvidenceTarget | null;20  open: (t: EvidenceTarget) => void;21  close: () => void;22}23const EvidenceCtx = createContext<Ctx>({ target: null, open: () => undefined, close: () => undefined });2425const HASH_RE = /^#evidence=([^:]+):(.+)$/;2627/** Mounted once in the root layout. Mirrors the open target to `#evidence=<slug>:<property>` so a drawer can be linked. */28export function EvidenceProvider({ children }: { children: ReactNode }) {29  const [target, setTarget] = useState<EvidenceTarget | null>(null);30  const open = useCallback((t: EvidenceTarget) => {31    setTarget(t);32    try {33      history.replaceState(null, '', `#evidence=${encodeURIComponent(t.slug)}:${encodeURIComponent(t.property)}`);34    } catch {35      /* ignore */36    }37  }, []);38  const close = useCallback(() => {39    setTarget(null);40    try {41      if (HASH_RE.test(location.hash)) history.replaceState(null, '', location.pathname + location.search);42    } catch {43      /* ignore */44    }45  }, []);46  useEffect(() => {47    const m = HASH_RE.exec(location.hash);48    if (m) setTarget({ slug: decodeURIComponent(m[1] as string), property: decodeURIComponent(m[2] as string) });49  }, []);50  const value = useMemo(() => ({ target, open, close }), [target, open, close]);51  return <EvidenceCtx.Provider value={value}>{children}</EvidenceCtx.Provider>;52}5354export function useEvidence(): Ctx {55  return useContext(EvidenceCtx);56}57