import { useEffect, useId, useRef, type CSSProperties, type ReactNode } from 'react'; export interface ProvenanceEvent { /** Stable id — used as React key. */ id: string; /** ISO timestamp string. Renders as locale time + relative offset. */ at: string; /** Free-form event kind label (`prompt`, `tool-call`, `model`, …). */ kind: string; /** Plain-language one-line description. */ summary: string; /** Optional inspector slot (JSON, code block, etc.). */ details?: ReactNode; } export interface ProvenanceDrawerProps { /** Whether the drawer is open. */ open: boolean; /** Called when the user hits Esc or clicks the overlay. */ onClose: () => void; /** Ordered list of events — newest LAST. */ events: ProvenanceEvent[]; /** Optional title (default 'Provenance'). */ title?: string; /** Drawer width in px. Default 420. */ width?: number; className?: string; style?: CSSProperties; } /** * `` — slide-in right drawer that lists every step the * model + tools took to produce the current response. * * Trust surface (Wave 13.C.4). Backs onto whatever event log the host * exposes (event-store, in-memory ring, etc.); the component is pure * presentation — passes the array straight through. * * Accessibility: role="dialog" + aria-modal, focus-traps with the * initial focus on the close button, returns focus to the trigger on * close (the host must manage the trigger ref). */ export function ProvenanceDrawer({ open, onClose, events, title = 'Provenance', width = 420, className, style, }: ProvenanceDrawerProps) { const titleId = useId(); const closeBtnRef = useRef(null); // Escape to close + initial focus on the close button. useEffect(() => { if (!open) return; const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') { e.preventDefault(); onClose(); } }; window.addEventListener('keydown', onKey); closeBtnRef.current?.focus(); return () => window.removeEventListener('keydown', onKey); }, [open, onClose]); // Body scroll lock while open. useEffect(() => { if (!open) return; const prev = document.body.style.overflow; document.body.style.overflow = 'hidden'; return () => { document.body.style.overflow = prev; }; }, [open]); if (!open) return null; return (
{/* Backdrop */}
{events.length === 0 ? (

No events recorded for this response.

) : ( events.map((ev) => ) )}
); } function ProvenanceRow({ ev }: { ev: ProvenanceEvent }) { let when = ev.at; try { const d = new Date(ev.at); if (!Number.isNaN(d.getTime())) { when = d.toLocaleTimeString(); } } catch { /* fall through */ } return (
{ev.kind}

{ev.summary}

{ev.details && (
Details
{ev.details}
)}
); }