import { useState, useEffect, useRef } from 'react'; // Inline SVG icons (Lucide-style paths). The artifact carries no external // module dependency — the sandbox runtime doesn't register lucide-react, so // importing it crashes the whole tree. Self-contained SVGs sidestep that. function Svg({ size = 16, className = '', children }) { return ( {children} ); } const GitFork = (p) => ; const GitBranch = (p) => ; const Sparkles = (p) => ; const Boxes = (p) => ; const Bell = (p) => ; const Check = (p) => ; const RotateCcw = (p) => ; const ChevronRight = (p) => ; const Cpu = (p) => ; const Zap = (p) => ; const Play = (p) => ; const CircleDot = (p) => ; const Database = (p) => ; const SkipForward = (p) => ; // Category palette — mirrors forkhub's signal categories (models.py). const CAT = { feature: { chip: 'bg-emerald-500/15 text-emerald-300 border-emerald-500/30', bar: 'bg-emerald-400', dot: 'bg-emerald-400' }, fix: { chip: 'bg-sky-500/15 text-sky-300 border-sky-500/30', bar: 'bg-sky-400', dot: 'bg-sky-400' }, refactor: { chip: 'bg-violet-500/15 text-violet-300 border-violet-500/30', bar: 'bg-violet-400', dot: 'bg-violet-400' }, dependency: { chip: 'bg-zinc-500/15 text-zinc-300 border-zinc-500/30', bar: 'bg-zinc-400', dot: 'bg-zinc-400' }, }; // The upstream repo and its fork constellation. All data is inline — // the sandbox blocks network calls, so the "GitHub API" is faked here. const UPSTREAM = { name: 'tldr-pages/tldr', head: 'a1f3c9d', stars: '52.1k' }; const FORKS = [ { id: 'alice', head: '7b2e004', changed: true, commits: ['feat: add dark-mode theme', 'style: dim ANSI palette'], files: ['themes/dark.css', 'src/config.js'], signal: { category: 'feature', sig: 8, summary: 'Adds a dark-mode color theme with a dimmed ANSI palette' }, cluster: 'dark-mode', }, { id: 'carol', head: 'c40aa12', changed: true, commits: ['add dark theme support'], files: ['themes/dark.css'], signal: { category: 'feature', sig: 7, summary: 'Adds dark theme support for terminal output' }, cluster: 'dark-mode', }, { id: 'bob', head: 'd91f7a3', changed: true, commits: ['fix: unicode width on CJK glyphs'], files: ['src/render.py'], signal: { category: 'fix', sig: 5, summary: 'Fixes column alignment for wide CJK characters' }, }, { id: 'frank', head: 'f2b0c55', changed: true, commits: ['refactor: extract parser module'], files: ['src/parser.py', 'src/index.py'], signal: { category: 'refactor', sig: 4, summary: 'Extracts the page parser into its own module' }, }, { id: 'dave', head: '5e7c118', changed: true, commits: ['chore: bump deps'], files: ['package.json', 'package-lock.json'], signal: { category: 'dependency', sig: 2, summary: 'Routine dependency version bumps' }, }, { id: 'erin', head: 'a1f3c9d', changed: false, // HEAD == last sync → skipped commits: [], files: [], }, ]; const STAGES = [ { key: 'discover', label: 'Discover', icon: GitFork }, { key: 'sync', label: 'Sync', icon: GitBranch }, { key: 'analyze', label: 'Analyze', icon: Sparkles }, { key: 'cluster', label: 'Cluster', icon: Boxes }, { key: 'digest', label: 'Digest', icon: Bell }, ]; const CAPTIONS = { discover: { title: 'Discover the forks', body: 'ForkHub asks the GitHub API for every fork of the upstream repo. Conditional requests with ETags keep this cheap against the rate limit — unchanged pages return 304 with no body.', }, sync: { title: 'Sync — skip what hasn’t moved', body: 'Each fork’s HEAD SHA is compared to what was stored last run. erin/tldr still points at the upstream SHA, so it’s skipped — no diffs fetched, no tokens spent. Five forks actually diverged.', }, analyze: { title: 'Analyze with an agent', body: 'A Claude Agent SDK coordinator dispatches a diff-analyst subagent (Sonnet) per changed fork. It starts with file lists + commit messages (cheap), pulls full diffs only when a file looks interesting, then calls store_signal with a category and a 1–10 significance score. A budget cap halts runaway cost.', }, cluster: { title: 'Cluster the divergences', body: 'Each signal’s summary is embedded into a vector. sqlite-vec finds signals with high cosine similarity. alice and carol independently built dark mode — their signals collapse into one cluster, the kind of cross-fork convergence ForkHub exists to surface.', }, digest: { title: 'Compose the digest', body: 'A digest-writer subagent (Haiku) writes the notification. A significance filter drops low-signal noise — dave’s dependency bump (2) and frank’s refactor (4) fall below the threshold. You get the interesting divergences, not every commit.', }, }; const SIG_THRESHOLD = 5; const COST_PER_ANALYST = 0.012; const MAX_BUDGET = 0.5; function Avatar({ id, dim }) { return (
{id[0].toUpperCase()}
); } function SigBar({ value, category }) { const c = CAT[category]; return (
{Array.from({ length: 10 }).map((_, i) => ( ))}
{value}/10
); } function CategoryChip({ category }) { return ( {category} ); } function ForkCard({ fork, stage }) { const idx = STAGES.findIndex((s) => s.key === stage); const synced = idx >= 1; const analyzed = idx >= 2 && fork.changed; const clustered = idx >= 3 && fork.cluster; const digested = idx >= 4 && fork.changed && fork.signal.sig >= SIG_THRESHOLD; const dim = synced && !fork.changed; return (
{clustered && ( ⛓ {fork.cluster} )}
{fork.id}/tldr
#{fork.head}
{synced && !fork.changed && ( 304 cached )} {synced && fork.changed && idx < 2 && ( diverged )}
{fork.commits.length > 0 && idx < 2 && ( )} {analyzed && (

{fork.signal.summary}

{digested && (
in digest
)} {idx >= 4 && !digested && (
below significance threshold — filtered
)}
)}
); } function StatPill({ icon: Icon, label, value }) { return (
{label} {value}
); } const LOG_LINES = [ { icon: Cpu, text: 'coordinator › list_forks() → 5 changed, 1 skipped' }, { icon: Sparkles, text: 'diff-analyst(alice) › get_file_diff(themes/dark.css) › store_signal(feature, 8)' }, { icon: Sparkles, text: 'diff-analyst(carol) › store_signal(feature, 7)' }, { icon: Sparkles, text: 'diff-analyst(bob) › get_file_diff(src/render.py) › store_signal(fix, 5)' }, { icon: Sparkles, text: 'diff-analyst(frank) › store_signal(refactor, 4)' }, { icon: Sparkles, text: 'diff-analyst(dave) › store_signal(dependency, 2)' }, ]; function App() { const [step, setStep] = useState(0); const [running, setRunning] = useState(false); const timer = useRef(null); const stage = STAGES[step].key; const next = () => setStep((s) => Math.min(s + 1, STAGES.length - 1)); const reset = () => { setRunning(false); setStep(0); }; // "Run all" walks the pipeline end-to-end with a pause between stages. useEffect(() => { if (!running) return; if (step >= STAGES.length - 1) { setRunning(false); return; } timer.current = setTimeout(() => setStep((s) => s + 1), 1300); return () => clearTimeout(timer.current); }, [running, step]); const changed = FORKS.filter((f) => f.changed).length; const signals = step >= 2 ? changed : 0; const clusters = step >= 3 ? 1 : 0; const cost = step >= 2 ? (changed * COST_PER_ANALYST).toFixed(3) : '0.000'; const digestForks = FORKS.filter((f) => f.changed && f.signal.sig >= SIG_THRESHOLD); return (
{/* Header */}

ForkHub

Watch the fork constellation. Surface what diverged, and why.

{UPSTREAM.name} ★ {UPSTREAM.stars}
{/* Stepper */}
{STAGES.map((s, i) => { const Icon = s.icon; const active = i === step; const done = i < step; return (
{i < STAGES.length - 1 && }
); })}
{/* Stats */}
= 1 ? changed : '—'} /> = 3 ? clusters : '—'} />
{/* Fork constellation */}
{FORKS.map((f) => )}
{/* Context rail */}
{/* Controls */}
A faithful sim of the sync → analyze → digest pipeline.
); } export default App;