import type { SourceHistoryItem } from "@dci/core"; import { ExternalLink } from "lucide-react"; import type { ReactNode } from "react"; import { fmtDate, fmtValue } from "@/lib/format"; import { SOURCE_KIND_LABEL } from "@/lib/labels"; import { cn } from "./cn"; export interface TimelineEntry { date: string; title: ReactNode; description?: ReactNode; meta?: ReactNode; href?: string | null; tone?: "neutral" | "accent" | "ok" | "warn" | "danger"; } const toneDot: Record, string> = { neutral: "bg-ink-4", accent: "bg-accent", ok: "bg-ok", warn: "bg-warn", danger: "bg-danger", }; /** Vertical timeline with hairline rail; dates in mono. */ export function TimelineList({ items, className, dense }: { items: TimelineEntry[]; className?: string; dense?: boolean }) { return (
    {items.map((it, i) => (
  1. {it.title}
    {it.description &&
    {it.description}
    } {(it.meta || it.href) && (
    {it.meta} {it.href && ( source )}
    )}
  2. ))}
); } /** Adapter for SourceHistoryItem[] (entity pages). */ export function SourceHistory({ items, className }: { items: SourceHistoryItem[]; className?: string }) { const entries: TimelineEntry[] = items.map((s) => ({ date: s.date, title: prettyDescription(s.description), meta: ( {s.sourceName} · {SOURCE_KIND_LABEL[s.sourceKind]} · {s.kind} ), href: s.url, tone: s.kind === "changed" ? "accent" : s.kind === "verified" ? "ok" : "neutral", })); return ; } /** "field observed: {json}" → "field observed: human value" (structured payloads are otherwise one unbreakable token). */ function prettyDescription(d: string): string { const m = /^(.*?:\s*)([[{].*[\]}])\s*$/s.exec(d); if (!m) return d; try { return `${m[1]}${fmtValue(JSON.parse(m[2]!))}`; } catch { return d; } }