// Run view — Compose, Launch, and Watch a brief pipeline run. // Layout: 3 columns — recent runs sidebar (left), compose form (center), live console (right). // On launch, the form collapses and the console expands to fill. const { useState: useStateR, useEffect: useEffectR, useRef: useRefR, useMemo: useMemoR } = React; /** * Single definition for Compose strip + live console (synthetic until API exposes real steps). * Each step: strip label === console stage name (≤11 chars for column), same startSec for active strip. */ const BRIEF_RUN_STEPS = [ { id: "setup", label: "Setup", startSec: 0, events: [ { minSec: 0, ts: "00.0", msg: (c) => `Run queued for ${c.entity.name}.` }, { minSec: 1, ts: "01.0", msg: (c) => `Company context loaded from the knowledge graph.` }, ], }, { id: "search", label: "Search", startSec: 3, events: [ { minSec: 3, ts: "03.0", msg: (c) => `Wide search across sources: ${c.sources.join(", ")}.` }, { minSec: 8, ts: "08.0", msg: () => `Thematic queries running in parallel.` }, ], }, { id: "draft", label: "Draft", startSec: 15, events: [{ minSec: 15, ts: "15.0", msg: () => `Drafting candidate bullet points.` }], }, { id: "facts", label: "Fact check", startSec: 25, events: [{ minSec: 25, ts: "25.0", msg: () => `Checking citations and quotes against the sources.` }], }, { id: "similar", label: "Similarity", startSec: 35, events: [{ minSec: 35, ts: "35.0", msg: () => `Comparing to recent work so obvious repeats are caught early.` }], }, { id: "web", label: "Web check", startSec: 45, events: [ { minSec: 45, ts: "45.0", msg: () => `Fresh web evidence — planning queries and fetching results.` }, { minSec: 52, ts: "52.0", msg: () => `Deciding what to keep, tighten, or drop from that evidence.` }, ], }, { id: "finish", label: "Wrap up", startSec: 58, events: [{ minSec: 58, ts: "58.0", msg: () => `Merging themes and building the report.` }], }, ]; function briefRunStripLabels() { return BRIEF_RUN_STEPS.map((s) => s.label); } function briefRunActiveStripIndex(elapsedSec, mode) { const n = BRIEF_RUN_STEPS.length; if (mode === "done") return n; let idx = 0; for (let i = n - 1; i >= 0; i--) { if (elapsedSec >= BRIEF_RUN_STEPS[i].startSec) { idx = i; break; } } return idx; } /** Console stage column: fixed width 11. */ function briefRunStageColumn(label) { const s = label.length > 11 ? label.slice(0, 11) : label; return s.padEnd(11, " "); } /** Matches POST /api/frontend/run: incremental vs explicit reporting dates. */ function composeWindowSummary(cfg) { if (cfg.window === "custom") return `${cfg.customStart} → ${cfg.customEnd}`; return "Today / since last run"; } /** * Optional `window.RUN_DATA.composeEstimates` from `/api/frontend/run-data.json`: * - costDisplay, costFoot (strings) * - latencyDisplay (string, e.g. "4–5m") OR latencyMin + latencyMax (numbers, minutes) * - latencyFoot (string) * When fields are missing, Compose falls back to model + window heuristics. */ function resolveComposeEstimates(RD, model, config, cost, minTime, maxTime) { const allEst = RD.composeEstimates && typeof RD.composeEstimates === "object" ? RD.composeEstimates : {}; const entityId = config && config.entity && config.entity.id; // allEst is keyed by entity_id; fall back to allEst itself for backward compat const est = (entityId && allEst[entityId] && typeof allEst[entityId] === "object") ? allEst[entityId] : (allEst.costDisplay != null ? allEst : {}); const displayCost = est.costDisplay != null && String(est.costDisplay).trim() !== "" ? String(est.costDisplay) : `$${cost}`; const displayCostFoot = est.costFoot != null && String(est.costFoot).trim() !== "" ? String(est.costFoot) : `${model.label.toLowerCase()} · ${config.sources.length} src`; const displayLatencyFoot = est.latencyFoot != null && String(est.latencyFoot).trim() !== "" ? String(est.latencyFoot) : composeWindowSummary(config); let latencyMainForHero = null; let latencyLaunchStr = null; if (est.latencyDisplay != null && String(est.latencyDisplay).trim() !== "") { const ld = String(est.latencyDisplay); latencyMainForHero =
{ld}
; latencyLaunchStr = ld; } else if (est.latencyMin != null && est.latencyMax != null) { const lmin = Math.round(Number(est.latencyMin)); const lmax = Math.round(Number(est.latencyMax)); if (!Number.isNaN(lmin) && !Number.isNaN(lmax)) { latencyMainForHero = (
{lmin} {lmax} m
); latencyLaunchStr = `${lmin}–${lmax}m`; } } if (!latencyMainForHero) { latencyMainForHero = (
1 2 m
); latencyLaunchStr = "1–2m"; } return { displayCost, displayCostFoot, displayLatencyFoot, latencyMainForHero, latencyLaunchStr, }; } function buildBriefRunConsoleLines(now, mode, config, result, error) { const l = []; if (mode === "compose") return l; for (const step of BRIEF_RUN_STEPS) { for (const ev of step.events) { if (now >= ev.minSec) { l.push({ ts: ev.ts, stage: step.id, stageLabel: step.label, msg: typeof ev.msg === "function" ? ev.msg(config) : ev.msg, }); } } } if (result) { l.push({ ts: "--.-", stage: "brief", stageLabel: "Brief", msg: `Brief saved · ${result.bullets_saved} bullet${result.bullets_saved === 1 ? "" : "s"}.`, }); l.push({ ts: "--.-", stage: "story", stageLabel: "Story", msg: result.narrative ? `Opening story line · ${result.narrative.split(" ").length} words.` : "Opening story line skipped (no bullets).", }); l.push({ ts: "--.-", stage: "done", stageLabel: "Done", msg: `Finished in ${result.duration_sec}s.`, }); } if (error) { l.push({ ts: "--.-", stage: "error", stageLabel: "Error", msg: error }); } return l; } // ── Mode tabs ───────────────────────────────────────────────────── function RunView({ tweaks }) { const RD = window.RUN_DATA && typeof window.RUN_DATA === "object" ? window.RUN_DATA : {}; const composePicklist = useMemoR(() => { const d = window.DATA && typeof window.DATA === "object" ? window.DATA : {}; if (Array.isArray(d.composeEntities)) return d.composeEntities; return Array.isArray(d.companies) ? d.companies : []; }, []); const firstEntity = composePicklist[0] || { id: "", name: "No companies loaded", ticker: "—", industry: "", country: "", }; const defaultSources = (Array.isArray(RD.sources) ? RD.sources : []).filter(s => s.checked).map(s => s.id); const sources0 = defaultSources.length > 0 ? defaultSources : ["news"]; const modelsList = Array.isArray(RD.models) ? RD.models : []; const defaultModelId = modelsList.find(m => m.id === "balanced")?.id || modelsList[0]?.id || "balanced"; const today = new Date().toISOString().slice(0, 10); const [mode, setMode] = useStateR("compose"); // compose | running | done const [config, setConfig] = useStateR({ entity: firstEntity, model: defaultModelId, themes: [], themesAuto: true, window: "24h", customStart: today, customEnd: today, sources: sources0, novelty: window.DATA?.noveltyDays ?? 30, }); const [runId, setRunId] = useStateR(null); const [runResult, setRunResult] = useStateR(null); const [runError, setRunError] = useStateR(null); const [now, setNow] = useStateR(0); // elapsed seconds (for log animation) const pollRef = useRefR(null); // Elapsed timer while running useEffectR(() => { if (mode !== "running") { setNow(0); return; } const t0 = performance.now(); const iv = setInterval(() => setNow((performance.now() - t0) / 1000), 500); return () => clearInterval(iv); }, [mode]); // Poll run status every 2s useEffectR(() => { if (!runId || mode !== "running") return; pollRef.current = setInterval(() => { fetch(`/api/frontend/run/${runId}`) .then(r => r.json()) .then(data => { if (data.status === "succeeded" || data.status === "no_data") { clearInterval(pollRef.current); setRunResult(data); setMode("done"); } else if (data.status === "failed") { clearInterval(pollRef.current); setRunError(data.error || "Run failed"); setMode("done"); } // "running" or "queued" → keep polling }) .catch(() => {}); }, 2000); return () => clearInterval(pollRef.current); }, [runId, mode]); const launch = () => { setRunId(null); setRunResult(null); setRunError(null); setMode("running"); fetch("/api/frontend/run", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ entity_id: config.entity.id, window: config.window, custom_start: config.window === "custom" ? config.customStart : undefined, custom_end: config.window === "custom" ? config.customEnd : undefined, source_categories: config.sources, }), }) .then(r => r.json()) .then(data => { if (data.run_id) setRunId(data.run_id); }) .catch(err => { setRunError(String(err)); setMode("done"); }); }; const reset = () => { setMode("compose"); setRunId(null); setRunResult(null); setRunError(null); }; if (!composePicklist.length) { return (

Compose needs at least one entity that appears in the Top US 100 universe (first nine in list order). Seed orchestration for those tickers, then reload.

); } return (
{/* ── Left: recent runs ── */} {/* ── Center: compose / running header ── */}
{mode === "compose" && ( )} {mode !== "compose" && ( )}
{/* ── Right: live console ── */}
); } // ── Compose form ────────────────────────────────────────────────── function formatComposeHeroDateline() { const now = new Date(); const day = now.getUTCDate(); const month = now.toLocaleDateString("en-US", { month: "long", timeZone: "UTC" }); const year = now.getUTCFullYear(); const timePart = now.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit", hour12: false, timeZone: "UTC", }); return `Compose · ${day} ${month} ${year} · ${timePart} UTC`; } function ComposeForm({ config, setConfig, onLaunch, entityPicklist }) { const RD = window.RUN_DATA && typeof window.RUN_DATA === "object" ? window.RUN_DATA : {}; const picks = Array.isArray(entityPicklist) ? entityPicklist : []; const [composeDateline, setComposeDateline] = useStateR(formatComposeHeroDateline); const [entitySearch, setEntitySearch] = useStateR(""); useEffectR(() => { const tick = () => setComposeDateline(formatComposeHeroDateline()); tick(); const id = setInterval(tick, 15_000); return () => clearInterval(id); }, []); const composeSearchIdSet = useMemoR(() => { const raw = window.DATA?.composeSearchEntityIds; if (!Array.isArray(raw) || raw.length === 0) return null; return new Set(raw); }, []); const filteredEntities = useMemoR(() => { const q = entitySearch.trim().toLowerCase(); const all = Array.isArray(window.DATA?.companies) ? window.DATA.companies : []; if (!q) return picks; const pool = composeSearchIdSet ? all.filter(c => composeSearchIdSet.has(c.id)) : all; return pool .filter( c => c.name.toLowerCase().includes(q) || (c.ticker && c.ticker.toLowerCase().includes(q)) ) .sort((a, b) => (a.name || "").localeCompare(b.name || "", undefined, { sensitivity: "base" })); }, [entitySearch, picks, composeSearchIdSet]); const models = Array.isArray(RD.models) ? RD.models : []; const model = models.find(m => m.id === config.model) || models[0] || { id: "balanced", label: "Balanced", desc: "", cost: 0.42, time: "4–5m", }; // Cost preview: depends on model + window + concurrency + sources const winMult = config.window === "custom" ? 5.5 : 1; const srcMult = 0.6 + 0.05 * (config.sources?.length || 0); const themeMult = config.themesAuto ? 1 : Math.max(0.4, (config.themes?.length || 0) / 5); const cost = (model.cost * winMult * srcMult * themeMult).toFixed(2); const timeStr = String(model.time || "4–5m"); const timeParts = timeStr.split(/[-–]/); const minT = parseInt(timeParts[0], 10) || 4; const maxT = parseInt(timeParts[1] || String(minT), 10) || minT; const minTime = Math.round(minT * winMult * themeMult); const maxTime = Math.round(maxT * winMult * themeMult); const { displayCost, displayCostFoot, displayLatencyFoot, latencyMainForHero, latencyLaunchStr, } = resolveComposeEstimates(RD, model, config, cost, minTime, maxTime); const toggleTheme = (t) => { setConfig(c => ({ ...c, themes: c.themes.includes(t) ? c.themes.filter(x => x !== t) : [...c.themes, t] })); }; const toggleSource = (id) => { setConfig(c => ({ ...c, sources: c.sources.includes(id) ? c.sources.filter(x => x !== id) : [...c.sources, id] })); }; return (
{composeDateline}

New Brief.

Configure a single-entity run. It will search sources, draft bullets, check facts, compare to the last {config.novelty} days for repetition, verify on the web, then wrap up your morning note.

Est. cost
{displayCost}
{displayCostFoot}
Est. latency
{latencyMainForHero} {displayLatencyFoot}
{/* Entity */}
01

Entity

Pick a company to analyze.

setEntitySearch(e.target.value)} />
{filteredEntities.map(c => ( ))}
{/* Window */}
02

Window

Default follows today on the desk. Use custom dates when you want a specific day or range.

{[ { id: "24h", label: "Today / since last run", sub: "Automatic — the usual daily slice for this company", }, { id: "custom", label: "Custom dates", sub: "You pick the calendar days" }, ].map(o => ( ))}
{config.window === "custom" && (
)}
{/* Themes */} {/* Sources */}
03

Sources

Where the retriever should look. Off by default: social and blogs.

{(Array.isArray(RD.sources) ? RD.sources : []).map(s => ( ))}
{/* ── Sticky launch bar ── */}
Brief {config.entity.name} · {config.entity.ticker} · {composeWindowSummary(config)} · {config.sources.length} sources
Est. cost {displayCost}
Est. time {latencyLaunchStr}
); } // ── Run header (during/after run) ──────────────────────────────── function RunHeader({ config, now, mode, result, error, onReset }) { const stages = briefRunStripLabels(); const activeStage = briefRunActiveStripIndex(now, mode); const bullets = (result && result.bullets) || []; const narrative = result && result.narrative; return (
{mode === "running" ? "Running · live" : error ? "Run failed" : "Run complete"} {result && run-{result.run_id?.slice(0, 8)}}

{config.entity.name} · {config.entity.ticker}

{mode === "running" && ( {now.toFixed(0)}s elapsed · pipeline running )} {mode === "done" && result && !error && ( {result.duration_sec}s · {result.bullets_saved} saved · {result.bullets_discarded} discarded )} {mode === "done" && error && ( ✕ {error} )}
{/* Stage progress strip */}
{stages.map((label, i) => { const state = mode === "done" && !error ? "done" : i < activeStage ? "done" : i === activeStage ? "running" : "pending"; return (
{String(i + 1).padStart(2, "0")}
{label}
); })}
{/* Narrative + bullets when done */}

{mode === "running" ? "Bullets · live" : "Results"}

{result && {result.bullets_saved} saved · {result.bullets_discarded} discarded}
{mode === "running" && (
Pipeline running

Results will appear here once the run completes.

)} {mode === "done" && narrative && (
Today's narrative

{narrative}

)} {mode === "done" && bullets.map((b, i) => (
{String(i + 1).padStart(2, "0")} {b.theme} {b.novelty === "rewritten" && rewritten}

{b.text}

{b.rewriteReason &&
note{b.rewriteReason}
}
))}
{/* Result CTA */} {mode === "done" && (
)}
); } // ── Live console (right column) ────────────────────────────────── function LiveConsole({ mode, now, config, result, error }) { const consoleRef = useRefR(null); const lines = useMemoR( () => buildBriefRunConsoleLines(now, mode, config, result, error), [ mode, Math.floor(now), result, error, config.entity.id, config.entity.name, config.sources, config.window, config.customStart, config.customEnd, ] ); const stageColor = { setup: "var(--ink-mute)", search: "var(--running)", draft: "var(--accent)", facts: "var(--rewrite)", similar: "var(--novel)", web: "var(--novel)", finish: "var(--ink)", brief: "var(--ink)", story: "var(--accent)", done: "var(--novel)", error: "var(--discard)", }; useEffectR(() => { if (consoleRef.current) consoleRef.current.scrollTop = consoleRef.current.scrollHeight; }, [lines.length]); if (mode === "compose") { return (
Console idle
{`
        ┌─────────────────────┐
        │  brief.run() ready  │
        └─────────────────────┘

  configure → launch when ready
`}
awaiting launch
); } return (
Console · {config.entity.ticker} {result ? `run-${result.run_id?.slice(0, 8)}` : "queued"}
{lines.map((e, i) => (
{e.ts} {briefRunStageColumn(e.stageLabel)} {e.msg}
))} {mode === "running" && (
{now.toFixed(0).padStart(4, "0")}
)}
{lines.length} events · {now.toFixed(0)}s {mode === "done" ? (error ? "exit 1 · failed" : "exit 0 · ok") : "live"}
); } // ── Recent runs sidebar ────────────────────────────────────────── function RecentRuns({ mode, liveElapsed, config, result }) { const recent = Array.isArray(window.RUN_DATA?.recent) ? window.RUN_DATA.recent : []; return (
Recent runs
{/* Currently composing/running placeholder */}
{mode === "compose" ? "Drafting" : mode === "running" ? "Live" : "Just finished"} {mode === "running" && } {mode === "done" && }
{config.entity.name}
{config.entity.ticker} · {composeWindowSummary(config)} {mode === "running" && · {liveElapsed.toFixed(0)}s} {mode === "done" && result && · {result.duration_sec}s · {result.bullets_saved} saved} {mode === "done" && !result && · done}
{recent.map(r => ( ))}
); } window.RunView = RunView;