teleo-codex/ops/diagnostics/dashboard-v2.html
m3taversal 05d74d5e32 sync: import all VPS pipeline + diagnostics code as baseline
Imports 67 files from VPS (/opt/teleo-eval/) into repo as the single source
of truth. Previously only 8 of 67 files existed in repo — the rest were
deployed directly to VPS via SCP, causing massive drift.

Includes:
- pipeline/lib/: 33 Python modules (daemon core, extraction, evaluation, merge, cascade, cross-domain, costs, attribution, etc.)
- pipeline/: main daemon (teleo-pipeline.py), reweave.py, batch-extract-50.sh
- diagnostics/: 19 files (4-page dashboard, alerting, daily digest, review queue, tier1 metrics)
- agent-state/: bootstrap, lib-state, cascade inbox processor, schema
- systemd/: service unit files for reference
- deploy.sh: rsync-based deploy with --dry-run, syntax checks, dirty-tree gate
- research-session.sh: updated with Step 8.5 digest + cascade inbox processing

No new code written — all files are exact copies from VPS as of 2026-04-06.
From this point forward: edit in repo, commit, then deploy.sh.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 00:00:00 +01:00

1424 lines
69 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Teleo Codex — Live Terminal</title>
<style>
:root {
--brand: #6E46E5;
--brand-dim: rgba(110, 70, 229, 0.15);
--brand-glow: rgba(110, 70, 229, 0.3);
--bg: #0D1117;
--surface: #161B22;
--border: #21262D;
--text: #E6EDF3;
--text-dim: #8B949E;
--text-muted: #484F58;
--status-healthy: #3FB950;
--status-warning: #D29922;
--status-critical: #F85149;
/* Domain colors (Clay spec — separate from operations) */
--d-entertainment: #6E46E5;
--d-technology: #58A6FF;
--d-finance: #3FB950;
--d-internet-finance: #39D353;
--d-collective-agents: #BC8CFF;
--d-ai-alignment: #58A6FF;
--d-health: #F97583;
--d-space-development: #FFA657;
--d-grand-strategy: #6E46E5;
--d-general: #8B949E;
--d-energy: #3FB950;
--d-living-agents: #79C0FF;
--d-collective-intelligence: #D29922;
--d-critical-systems: #F85149;
/* Agent colors */
--a-leo: #D29922;
--a-rio: #2DD4BF;
--a-vida: #3FB950;
--a-theseus: #F97316;
--a-clay: #58A6FF;
--a-astra: #BC8CFF;
--a-epimetheus: #8B949E;
--a-ganymede: #E5534B;
--a-argus: #F0883E;
--a-oberon: #6E46E5;
--font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'SF Mono', monospace;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: var(--font-mono);
background: var(--bg);
color: var(--text);
min-height: 100vh;
font-size: 12px;
line-height: 1.2;
overflow: hidden;
}
/* HEADER */
.header {
padding: 0 16px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: 12px;
height: 40px;
background: var(--surface);
}
.header-title {
font-size: 13px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-dim);
white-space: nowrap;
}
.header-title span { color: var(--brand); }
.header-stats {
margin-left: auto;
display: flex;
gap: 20px;
font-size: 11px;
color: var(--text-muted);
align-items: center;
}
.header-stats strong {
color: var(--text);
font-size: 12px;
}
.live-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--status-healthy);
display: inline-block;
animation: pulse 2s ease-in-out infinite;
margin-right: 4px;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
/* TAB BAR */
.tab-bar {
display: flex;
border-bottom: 1px solid var(--border);
background: var(--bg);
}
.tab {
padding: 6px 16px;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-muted);
cursor: pointer;
border: none;
border-bottom: 2px solid transparent;
background: none;
font-family: var(--font-mono);
transition: color 0.15s;
}
.tab:hover { color: var(--text-dim); }
.tab.active { color: var(--text); border-bottom-color: var(--brand); }
/* MAIN LAYOUT */
.main {
height: calc(100vh - 40px - 30px);
overflow: hidden;
}
/* === DASHBOARD VIEW === */
.view { display: none; height: 100%; }
.view.active { display: flex; flex-direction: column; }
.dash-top {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.vital {
padding: 4px 8px;
border-right: 1px solid var(--border);
}
.vital:last-child { border-right: none; }
.vital-value {
font-size: 16px;
font-weight: 700;
line-height: 1.1;
display: inline;
}
.vital-label {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-muted);
display: inline;
margin-left: 4px;
}
.vital-sub {
font-size: 9px;
color: var(--text-muted);
display: inline;
margin-left: 4px;
}
.vital-spark { display: block; margin-top: 2px; }
.v-healthy { color: var(--status-healthy); }
.v-warning { color: var(--status-warning); }
.v-critical { color: var(--status-critical); }
.v-brand { color: var(--brand); }
.v-text { color: var(--text); }
/* Middle row: 3 panels */
.dash-mid {
display: grid;
grid-template-columns: 38% 32% 30%;
flex: 1;
min-height: 0;
overflow: hidden;
}
.panel {
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
overflow: hidden;
}
.panel:last-child { border-right: none; }
.panel-hdr {
padding: 4px 8px 3px;
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-muted);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
display: flex;
align-items: center;
gap: 8px;
}
.panel-hdr .badge {
font-size: 10px;
padding: 1px 6px;
border-radius: 2px;
background: var(--brand-dim);
color: var(--brand);
font-weight: 600;
}
.panel-body {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
}
/* Timeline feed */
.tl-row {
display: grid;
grid-template-columns: 44px 52px 6px 1fr;
gap: 4px;
align-items: center;
padding: 2px 8px;
border-bottom: 1px solid rgba(33, 38, 45, 0.4);
font-size: 11px;
}
.tl-row:hover { background: rgba(255, 255, 255, 0.015); }
.tl-ts { color: var(--text-muted); font-size: 10px; white-space: nowrap; }
.tl-agent { font-size: 10px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tl-dot { width: 6px; height: 6px; border-radius: 50%; }
.tl-desc { color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 11px; }
/* Domain bars */
.domain-row {
display: grid;
grid-template-columns: 100px 1fr 36px;
gap: 4px;
align-items: center;
padding: 2px 8px;
border-bottom: 1px solid rgba(33, 38, 45, 0.4);
}
.domain-name { font-size: 11px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.domain-bar-wrap {
height: 12px;
background: rgba(255, 255, 255, 0.03);
border-radius: 1px;
overflow: hidden;
}
.domain-bar {
height: 100%;
border-radius: 1px;
transition: width 0.5s ease;
}
.domain-count { font-size: 10px; color: var(--text-dim); text-align: right; }
/* Agent + Breaker panel */
.agent-row {
display: grid;
grid-template-columns: 56px 1fr 44px 40px;
gap: 4px;
align-items: center;
padding: 2px 8px;
border-bottom: 1px solid rgba(33, 38, 45, 0.4);
}
.agent-name { font-size: 11px; font-weight: 600; }
.spark-wrap {
height: 16px;
display: flex;
align-items: flex-end;
gap: 1px;
}
.spark-bar {
flex: 1;
min-width: 2px;
border-radius: 1px 1px 0 0;
transition: height 0.3s;
}
.agent-count { font-size: 10px; color: var(--text-dim); text-align: right; }
.breaker-section {
border-top: 1px solid var(--border);
padding: 4px 8px;
flex-shrink: 0;
}
.breaker-title {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-muted);
margin-bottom: 4px;
}
.breaker-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 4px;
}
.breaker {
display: flex;
align-items: center;
gap: 4px;
font-size: 10px;
}
.breaker-dot {
width: 6px;
height: 6px;
border-radius: 50%;
}
.breaker-label { color: var(--text-dim); }
/* Bottom bar: funnel */
.dash-bottom {
border-top: 1px solid var(--border);
flex-shrink: 0;
padding: 3px 8px;
display: flex;
align-items: center;
gap: 4px;
font-size: 10px;
color: var(--text-muted);
background: var(--surface);
}
.funnel-step {
display: flex;
align-items: center;
gap: 4px;
}
.funnel-val { color: var(--text); font-weight: 600; font-size: 11px; }
.funnel-arrow { color: var(--text-muted); margin: 0 4px; }
.funnel-label { font-size: 10px; }
.funnel-rate {
margin-left: auto;
color: var(--brand);
font-weight: 600;
font-size: 11px;
}
/* === CONTRIBUTORS VIEW === */
.lb-wrap { flex: 1; overflow-y: auto; }
.lb-row {
display: grid;
grid-template-columns: 36px 130px repeat(3, 60px) 70px 60px;
gap: 4px;
padding: 5px 14px;
font-size: 11px;
align-items: center;
border-bottom: 1px solid rgba(33, 38, 45, 0.4);
}
.lb-row.lb-hdr {
color: var(--text-muted);
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.05em;
position: sticky;
top: 0;
background: var(--bg);
z-index: 1;
padding: 8px 14px;
border-bottom: 1px solid var(--border);
}
.lb-rank { color: var(--text-muted); text-align: center; }
.lb-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lb-cell { text-align: right; color: var(--text-dim); }
.lb-score { font-weight: 700; color: var(--brand); text-align: right; }
.lb-tier {
font-size: 9px;
text-transform: uppercase;
letter-spacing: 0.05em;
padding: 1px 4px;
border-radius: 2px;
}
.tier-veteran { background: rgba(210, 153, 34, 0.15); color: #D29922; }
.tier-contributor { background: rgba(139, 148, 158, 0.15); color: #8B949E; }
/* === DOMAINS VIEW === */
.domains-view-inner {
display: flex;
gap: 0;
flex: 1;
overflow: hidden;
}
.domains-left {
flex: 1;
overflow-y: auto;
border-right: 1px solid var(--border);
}
.domains-right {
width: 320px;
overflow-y: auto;
padding: 16px;
flex-shrink: 0;
}
.domain-row-big {
display: grid;
grid-template-columns: 180px 1fr 80px 80px 60px;
align-items: center;
padding: 10px 16px;
border-bottom: 1px solid var(--border);
cursor: default;
transition: background 0.15s;
}
.domain-row-big:hover {
background: rgba(255,255,255,0.02);
}
.domain-row-big.stagnant {
opacity: 0.45;
}
.domain-row-big .name {
font-size: 12px;
font-weight: 700;
display: flex;
align-items: center;
gap: 8px;
white-space: nowrap;
}
.domain-row-big .name .dot {
width: 10px;
height: 10px;
border-radius: 50%;
flex-shrink: 0;
}
.domain-row-big .bar-cell {
padding: 0 12px;
}
.domain-bar-outer {
height: 18px;
background: rgba(255,255,255,0.03);
border-radius: 2px;
position: relative;
overflow: hidden;
}
.domain-bar-inner {
height: 100%;
border-radius: 2px;
position: absolute;
left: 0;
top: 0;
transition: width 0.4s ease;
}
.domain-bar-knowledge {
height: 100%;
border-radius: 2px;
position: absolute;
left: 0;
top: 0;
opacity: 0.35;
}
.domain-row-big .num {
font-size: 12px;
text-align: right;
font-variant-numeric: tabular-nums;
}
.domain-row-big .num strong {
color: var(--text-bright);
}
.domain-row-big .num small {
color: var(--text-muted);
font-size: 10px;
}
.domain-row-big .badge {
text-align: center;
}
.domain-row-big .badge span {
font-size: 9px;
padding: 2px 6px;
border-radius: 3px;
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 600;
}
.domains-summary-card {
background: rgba(255,255,255,0.02);
border: 1px solid var(--border);
border-radius: 4px;
padding: 14px;
margin-bottom: 12px;
}
.domains-summary-card h4 {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-muted);
margin: 0 0 10px 0;
}
.domains-summary-row {
display: flex;
justify-content: space-between;
font-size: 11px;
padding: 3px 0;
color: var(--text-dim);
}
.domains-summary-row strong {
color: var(--text-bright);
margin-top: 2px;
}
.domain-card-stat strong { color: var(--text); }
.domain-card.stagnant {
border-left: 3px solid var(--status-warning);
}
/* Rejection reasons mini-bar */
.reject-section {
border-top: 1px solid var(--border);
padding: 4px 8px;
flex-shrink: 0;
}
.reject-row {
display: flex;
align-items: center;
gap: 6px;
font-size: 10px;
padding: 1px 0;
}
.reject-tag { color: var(--text-dim); min-width: 140px; }
.reject-bar {
height: 6px;
background: var(--status-critical);
border-radius: 1px;
opacity: 0.6;
}
.reject-count { color: var(--text-muted); min-width: 20px; text-align: right; }
/* Skeleton */
.skeleton {
background: linear-gradient(90deg, var(--surface) 25%, rgba(255,255,255,0.03) 50%, var(--surface) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
height: 12px;
border-radius: 1px;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* Scrollbar */
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
/* Responsive */
@media (max-width: 900px) {
.dash-mid { grid-template-columns: 1fr; }
.dash-top { grid-template-columns: 1fr 1fr 1fr; }
}
</style>
</head>
<body>
<!-- HEADER -->
<div class="header">
<div class="header-title">TELEO <span>CODEX</span></div>
<div class="header-stats">
<span><span class="live-dot" id="live-dot"></span> <span id="live-label">LIVE</span></span>
<span>MERGED <strong id="h-merged">--</strong></span>
<span>APPROVAL <strong id="h-approval">--</strong></span>
<span>TTM <strong id="h-ttm">--</strong></span>
<span id="h-updated" style="color:var(--text-muted)"></span>
<a href="/" style="color:var(--text-muted);font-size:10px;text-decoration:none;border:1px solid var(--border);padding:2px 8px;border-radius:3px;margin-left:8px" title="v1 — Pipeline Operations">&larr; v1 Pipeline Ops</a>
</div>
</div>
<!-- TABS -->
<div class="tab-bar">
<button class="tab active" data-view="dash">DASHBOARD</button>
<button class="tab" data-view="contributors">CONTRIBUTORS</button>
<button class="tab" data-view="domains">DOMAINS</button>
</div>
<div class="main">
<!-- ======== DASHBOARD ======== -->
<div class="view active" id="v-dash">
<!-- Vital signs row -->
<div class="dash-top" id="vitals">
<div class="vital">
<div class="vital-value v-text" id="vt-claims">--</div>
<div class="vital-label">TOTAL CLAIMS</div>
<div class="vital-sub" id="vt-claims-sub"></div>
<canvas class="vital-spark" id="spark-claims" width="120" height="28"></canvas>
</div>
<div class="vital">
<div class="vital-value v-brand" id="vt-approval">--</div>
<div class="vital-label">APPROVAL RATE</div>
<div class="vital-sub" id="vt-approval-sub"></div>
<canvas class="vital-spark" id="spark-approval" width="120" height="28"></canvas>
</div>
<div class="vital">
<div class="vital-value" id="vt-orphan">--</div>
<div class="vital-label">ORPHAN RATIO</div>
<div class="vital-sub" id="vt-orphan-sub"></div>
<canvas class="vital-spark" id="spark-orphan" width="120" height="28"></canvas>
</div>
<div class="vital">
<div class="vital-value" id="vt-fresh">--</div>
<div class="vital-label">EVIDENCE AGE</div>
<div class="vital-sub" id="vt-fresh-sub"></div>
<canvas class="vital-spark" id="spark-fresh" width="120" height="28"></canvas>
</div>
<div class="vital">
<div class="vital-value" id="vt-linkage">--</div>
<div class="vital-label">CROSS-DOMAIN</div>
<div class="vital-sub" id="vt-linkage-sub"></div>
<canvas class="vital-spark" id="spark-linkage" width="120" height="28"></canvas>
</div>
<div class="vital">
<div class="vital-value" id="vt-backlog">--</div>
<div class="vital-label">REVIEW BACKLOG</div>
<div class="vital-sub" id="vt-backlog-sub"></div>
<canvas class="vital-spark" id="spark-backlog" width="120" height="28"></canvas>
</div>
</div>
<!-- Three panels -->
<div class="dash-mid">
<!-- LEFT: Pipeline timeline -->
<div class="panel">
<div class="panel-hdr">ACTIVITY FEED <span class="badge" id="feed-badge">--</span></div>
<div class="panel-body" id="timeline-feed"></div>
<div class="reject-section" id="reject-section" style="display:none">
<div style="font-size:10px;text-transform:uppercase;letter-spacing:0.06em;color:var(--text-muted);margin-bottom:3px">TOP REJECTIONS</div>
<div id="reject-list"></div>
</div>
</div>
<!-- CENTER: Domains -->
<div class="panel">
<div class="panel-hdr">DOMAIN ACTIVITY <span class="badge">7D</span></div>
<div class="panel-body" id="domain-bars"></div>
</div>
<!-- RIGHT: Agents + Breakers -->
<div class="panel">
<div class="panel-hdr">AGENTS</div>
<div class="panel-body" id="agent-list"></div>
<div class="breaker-section">
<div class="breaker-title">CIRCUIT BREAKERS</div>
<div class="breaker-grid" id="breakers"></div>
</div>
</div>
</div>
<!-- Funnel bar -->
<div class="dash-bottom" id="funnel-bar">
<span style="color:var(--text-muted)">FUNNEL</span>
</div>
</div>
<!-- ======== CONTRIBUTORS ======== -->
<div class="view" id="v-contributors">
<div class="panel-hdr" style="display:flex;gap:12px">
CONTRIBUTORS
<button class="tab active" data-cview="principal" style="padding:2px 8px;font-size:10px">PRINCIPAL</button>
<button class="tab" data-cview="agent" style="padding:2px 8px;font-size:10px">AGENT</button>
</div>
<div class="lb-wrap" id="lb-wrap">
<div class="lb-row lb-hdr">
<div>#</div><div>HANDLE</div><div>MERGED</div><div>TIER</div><div>DOMAINS</div><div>CI SCORE</div><div>LAST</div>
</div>
</div>
</div>
<!-- ======== DOMAINS ======== -->
<div class="view" id="v-domains">
<div class="domains-view-inner">
<div class="domains-left">
<div class="domain-row-big" style="border-bottom:1px solid var(--border);opacity:0.6">
<div class="name" style="font-size:10px;text-transform:uppercase;letter-spacing:0.06em;color:var(--text-muted);font-weight:400">DOMAIN</div>
<div class="bar-cell" style="font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.06em">VOLUME</div>
<div class="num" style="font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.06em">TOTAL</div>
<div class="num" style="font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.06em">7D</div>
<div class="badge" style="font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.06em">STATUS</div>
</div>
<div id="domains-rows"></div>
</div>
<div class="domains-right">
<div id="domains-summary"></div>
</div>
</div>
</div>
</div><!-- /main -->
<script>
// ============================================
// CONFIG
// ============================================
const API = 'http://77.42.65.182:8081';
const REFRESH_MS = 60000;
const AGENT_COLORS = {
leo:'#D29922', rio:'#2DD4BF', vida:'#3FB950', theseus:'#F97316',
clay:'#58A6FF', astra:'#BC8CFF'
};
// Infra agents — tracked but excluded from knowledge contribution displays
const INFRA_AGENTS = {
epimetheus:'#8B949E', ganymede:'#E5534B', argus:'#F0883E', oberon:'#6E46E5', rhea:'#8B949E'
};
const ALL_AGENT_COLORS = {...AGENT_COLORS, ...INFRA_AGENTS, 'auto-extract':'#8B949E'};
const DOMAIN_COLORS = {
'entertainment':'#6E46E5', 'technology':'#58A6FF', 'finance':'#3FB950',
'internet-finance':'#39D353', 'collective-agents':'#BC8CFF',
'ai-alignment':'#58A6FF', 'health':'#F97583', 'space-development':'#FFA657',
'grand-strategy':'#6E46E5', 'general':'#8B949E', 'energy':'#3FB950',
'living-agents':'#79C0FF', 'collective-intelligence':'#D29922',
'critical-systems':'#F85149', 'unknown':'#484F58', 'null':'#484F58'
};
const OP_COLORS = {
extract:'#58A6FF', ingest:'#58A6FF', new:'#3FB950', enrich:'#D29922',
challenge:'#F85149', decision:'#BC8CFF', infra:'#8B949E',
validate:'#D29922', evaluate:'#BC8CFF', merge:'#3FB950',
fix:'#F0883E', snapshot:'#8B949E', watchdog:'#484F58'
};
// ============================================
// EMBEDDED FALLBACK DATA (snapshot 2026-03-28)
// Used when CORS blocks API calls (e.g. file:// protocol)
// ============================================
const FALLBACK = {
'/api/metrics': {"throughput_1h":0,"approval_rate":0.683,"evaluated_24h":60,"approved_24h":41,"status_map":{"closed":663,"merged":667,"open":1},"source_map":{"enrichment":70,"extracted":682,"extracting":4,"null_result":315,"unprocessed":250},"rejection_reasons":[{"tag":"near_duplicate","count":10,"unique_prs":10},{"tag":"frontmatter_schema","count":8,"unique_prs":8},{"tag":"factual_discrepancy","count":5,"unique_prs":5},{"tag":"confidence_miscalibration","count":1,"unique_prs":1},{"tag":"broken_wiki_links","count":1,"unique_prs":1}],"fix_rate":0.227,"fix_attempted":586,"fix_succeeded":133,"median_ttm_minutes":1.6,"domains":{"unknown":{"closed":68,"merged":5},"ai-alignment":{"closed":62,"merged":59},"collective-intelligence":{"closed":1,"merged":1},"critical-systems":{"closed":1},"energy":{"merged":2},"entertainment":{"closed":41,"merged":34},"general":{"closed":113,"merged":255},"grand-strategy":{"closed":2,"merged":17,"open":1},"health":{"closed":96,"merged":70},"internet-finance":{"closed":238,"merged":166},"living-agents":{"merged":1},"space-development":{"closed":41,"merged":57}},"breakers":{"ingest":{"state":"closed","failures":1,"age_s":16369},"validate":{"state":"closed","failures":0,"age_s":8424},"evaluate":{"state":"closed","failures":0,"age_s":8418},"merge":{"state":"closed","failures":0,"age_s":8412},"fix":{"state":"closed","failures":0,"age_s":16350},"snapshot":{"state":"closed","failures":0,"age_s":405},"watchdog":{"state":"closed","failures":0,"age_s":45}}},
'/api/vital-signs': {"claim_index_status":"live","review_throughput":{"backlog":2,"open_prs":2,"approved_waiting":0,"conflict_prs":0,"conflict_permanent_prs":0,"reviewing_prs":0,"oldest_open_hours":43.8,"status":"healthy"},"orphan_ratio":{"ratio":0.319,"count":212,"total":664,"status":"critical"},"linkage_density":{"avg_outgoing_links":7.31,"cross_domain_links":1517,"cross_domain_ratio":0.313},"confidence_distribution":{"likely":333,"proven":98,"experimental":196,"speculative":36,"plausible":1},"evidence_freshness":{"avg_age_days":39,"median_age_days":23,"fresh_30d_count":399,"fresh_30d_pct":60.1},"domain_activity":{"active":[{"domain":null,"prs_7d":10,"latest":"2026-03-26 18:06:37"},{"domain":"ai-alignment","prs_7d":47,"latest":"2026-03-28 00:36:47"},{"domain":"energy","prs_7d":1,"latest":"2026-03-28 06:20:58"},{"domain":"general","prs_7d":190,"latest":"2026-03-28 09:31:03"},{"domain":"grand-strategy","prs_7d":12,"latest":"2026-03-28 08:09:48"},{"domain":"health","prs_7d":28,"latest":"2026-03-28 04:13:49"},{"domain":"internet-finance","prs_7d":136,"latest":"2026-03-28 07:18:21"},{"domain":"space-development","prs_7d":44,"latest":"2026-03-28 08:44:08"}],"stagnant":["collective-intelligence","critical-systems","entertainment","living-agents"],"status":"warning"},"funnel":{"sources_total":1321,"sources_queued":250,"sources_extracted":682,"prs_total":1331,"prs_merged":667,"conversion_rate":0.501}},
'/api/activity?limit=100': {"events":[{"timestamp":"2026-03-28 09:31:05","agent":null,"operation":"new","target":"2026-03-24-x-research-vibhu-tweet","domain":"general","description":"Merged PR #2062","status":"merged","pr_number":2062},{"timestamp":"2026-03-28 08:44:09","agent":null,"operation":"new","target":"2026-03-28-keeptrack-starship-v3-april-2026","domain":"space-development","description":"Merged PR #2061","status":"merged","pr_number":2061},{"timestamp":"2026-03-28 08:10:18","agent":null,"operation":"new","target":"research-2026-03-28","domain":"grand-strategy","description":"Merged PR #2060","status":"merged","pr_number":2060},{"timestamp":"2026-03-28 07:18:24","agent":null,"operation":"new","target":"2026-03-27-tg-source-m3taversal-jussy-world","domain":"internet-finance","description":"Merged PR #2059","status":"merged","pr_number":2059},{"timestamp":"2026-03-28 07:17:49","agent":null,"operation":"new","target":"2026-03-27-tg-source-m3taversal-01resolved","domain":"general","description":"Merged PR #2058","status":"merged","pr_number":2058},{"timestamp":"2026-03-28 07:15:15","agent":null,"operation":"infra","target":"2026-03-24-x-research-vibhu-tweet","domain":"general","description":"Closed PR #2057","status":"closed","pr_number":2057},{"timestamp":"2026-03-28 07:04:08","agent":null,"operation":"new","target":"2026-03-28-payloadspace-vast-haven1-delay-2027","domain":"general","description":"Merged PR #2056","status":"merged","pr_number":2056},{"timestamp":"2026-03-28 06:22:12","agent":null,"operation":"new","target":"2026-03-28-spglobal-hyperscaler-power","domain":"general","description":"Merged PR #2055","status":"merged","pr_number":2055},{"timestamp":"2026-03-28 06:21:36","agent":null,"operation":"new","target":"2026-03-28-nasaspaceflight-new-glenn","domain":"space-development","description":"Merged PR #2054","status":"merged","pr_number":2054},{"timestamp":"2026-03-28 06:21:01","agent":null,"operation":"new","target":"2026-03-28-mintz-nuclear-renaissance","domain":"energy","description":"Merged PR #2053","status":"merged","pr_number":2053},{"timestamp":"2026-03-28 06:18:55","agent":null,"operation":"new","target":"2026-03-28-introl-google-intersect-power","domain":"general","description":"Merged PR #2051","status":"merged","pr_number":2051},{"timestamp":"2026-03-28 06:18:23","agent":null,"operation":"infra","target":"2026-03-28-keeptrack-starship-v3","domain":"space-development","description":"Closed PR #2052","status":"closed","pr_number":2052},{"timestamp":"2026-03-28 06:10:50","agent":null,"operation":"new","target":"research-2026-03-28","domain":"space-development","description":"Merged PR #2050","status":"merged","pr_number":2050},{"timestamp":"2026-03-28 05:02:42","agent":null,"operation":"infra","target":"2026-03-27-tg-source","domain":"general","description":"Closed PR #2049","status":"closed","pr_number":2049},{"timestamp":"2026-03-28 05:00:40","agent":null,"operation":"infra","target":"2026-03-24-x-research-vibhu","domain":"general","description":"Closed PR #2048","status":"closed","pr_number":2048},{"timestamp":"2026-03-28 04:15:51","agent":null,"operation":"new","target":"research-2026-03-28","domain":"health","description":"Merged PR #2047","status":"merged","pr_number":2047},{"timestamp":"2026-03-28 03:02:10","agent":null,"operation":"infra","target":"2026-03-27-tg-source","domain":"general","description":"Closed PR #2046","status":"closed","pr_number":2046},{"timestamp":"2026-03-28 02:45:27","agent":null,"operation":"infra","target":"2026-03-24-x-research-vibhu","domain":"general","description":"Closed PR #2045","status":"closed","pr_number":2045},{"timestamp":"2026-03-28 01:22:47","agent":null,"operation":"new","target":"2026-03-06-oxford-pentagon","domain":"general","description":"Merged PR #2044","status":"merged","pr_number":2044},{"timestamp":"2026-03-28 01:00:20","agent":null,"operation":"new","target":"2026-03-08-intercept-openai","domain":"general","description":"Merged PR #2039","status":"merged","pr_number":2039},{"timestamp":"2026-03-28 00:58:13","agent":null,"operation":"new","target":"2026-02-27-cnn-openai-pentagon","domain":"general","description":"Merged PR #2035","status":"merged","pr_number":2035},{"timestamp":"2026-03-28 00:54:34","agent":null,"operation":"new","target":"2026-03-28-cnbc-anthropic-dod","domain":"general","description":"Merged PR #2043","status":"merged","pr_number":2043},{"timestamp":"2026-03-28 00:52:27","agent":null,"operation":"new","target":"2026-03-25-aljazeera-anthropic","domain":"general","description":"Merged PR #2041","status":"merged","pr_number":2041},{"timestamp":"2026-03-28 00:52:24","agent":null,"operation":"infra","target":"2026-03-27-tg-source","domain":"general","description":"Closed PR #2042","status":"closed","pr_number":2042},{"timestamp":"2026-03-28 00:51:21","agent":null,"operation":"new","target":"2026-03-17-slotkin-ai-guardrails","domain":"general","description":"Merged PR #2040","status":"merged","pr_number":2040},{"timestamp":"2026-03-28 00:50:47","agent":null,"operation":"new","target":"2026-03-06-oxford-pentagon","domain":"general","description":"Merged PR #2038","status":"merged","pr_number":2038},{"timestamp":"2026-03-28 00:48:41","agent":null,"operation":"new","target":"2026-03-02-axios-senate-dems","domain":"general","description":"Merged PR #2037","status":"merged","pr_number":2037},{"timestamp":"2026-03-28 00:48:06","agent":null,"operation":"new","target":"2026-02-28-govai-rsp-v3","domain":"general","description":"Merged PR #2036","status":"merged","pr_number":2036},{"timestamp":"2026-03-28 00:46:28","agent":null,"operation":"new","target":"2026-02-24-cnn-hegseth-anthropic","domain":"general","description":"Merged PR #2034","status":"merged","pr_number":2034},{"timestamp":"2026-03-28 00:36:49","agent":null,"operation":"new","target":"research-2026-03-28","domain":"ai-alignment","description":"Merged PR #2032","status":"merged","pr_number":2032},{"timestamp":"2026-03-28 00:30:41","agent":null,"operation":"infra","target":"2026-03-24-x-research-vibhu","domain":"general","description":"Closed PR #2033","status":"closed","pr_number":2033},{"timestamp":"2026-03-27 22:46:12","agent":null,"operation":"infra","target":"2026-03-27-tg-source","domain":"general","description":"Closed PR #2031","status":"closed","pr_number":2031},{"timestamp":"2026-03-27 22:15:27","agent":null,"operation":"infra","target":"2026-03-24-x-research-vibhu","domain":"general","description":"Closed PR #2030","status":"closed","pr_number":2030},{"timestamp":"2026-03-27 20:31:35","agent":null,"operation":"infra","target":"2026-03-27-tg-source","domain":"general","description":"Closed PR #2029","status":"closed","pr_number":2029},{"timestamp":"2026-03-27 20:00:15","agent":null,"operation":"infra","target":"2026-03-24-x-research-vibhu","domain":"general","description":"Closed PR #2028","status":"closed","pr_number":2028},{"timestamp":"2026-03-27 18:16:22","agent":null,"operation":"infra","target":"2026-03-27-tg-source","domain":"general","description":"Closed PR #2027","status":"closed","pr_number":2027},{"timestamp":"2026-03-27 18:00:14","agent":null,"operation":"new","target":"stigmergic-coordination-claims","domain":"grand-strategy","description":"Merged PR #2025","status":"merged","pr_number":2025},{"timestamp":"2026-03-27 17:45:31","agent":null,"operation":"infra","target":"2026-03-24-x-research-vibhu","domain":"general","description":"Closed PR #2026","status":"closed","pr_number":2026},{"timestamp":"2026-03-27 16:10:27","agent":null,"operation":"new","target":"archive-seed-sources","domain":"space-development","description":"Merged PR #2024","status":"merged","pr_number":2024},{"timestamp":"2026-03-27 16:05:46","agent":null,"operation":"infra","target":"energy-beyond-fusion","domain":"space-development","description":"Closed PR #2023","status":"closed","pr_number":2023},{"timestamp":"2026-03-27 16:03:13","agent":null,"operation":"new","target":"2026-03-27-tg-shared-01resolved","domain":"general","description":"Merged PR #2021","status":"merged","pr_number":2021},{"timestamp":"2026-03-27 16:02:37","agent":null,"operation":"new","target":"2026-03-27-tg-shared-01resolved","domain":"internet-finance","description":"Merged PR #2020","status":"merged","pr_number":2020},{"timestamp":"2026-03-27 16:02:03","agent":null,"operation":"infra","target":"2026-03-27-tg-source","domain":"general","description":"Closed PR #2022","status":"closed","pr_number":2022},{"timestamp":"2026-03-27 15:30:16","agent":null,"operation":"infra","target":"2026-03-24-x-research-vibhu","domain":"general","description":"Closed PR #2019","status":"closed","pr_number":2019},{"timestamp":"2026-03-27 13:28:36","agent":null,"operation":"new","target":"asteroid-isru-resubmit","domain":"space-development","description":"Merged PR #2018","status":"merged","pr_number":2018},{"timestamp":"2026-03-27 13:26:27","agent":null,"operation":"new","target":"batch9-governance-energy","domain":"space-development","description":"Merged PR #2016","status":"merged","pr_number":2016},{"timestamp":"2026-03-27 13:15:32","agent":null,"operation":"infra","target":"2026-03-24-x-research-vibhu","domain":"general","description":"Closed PR #2017","status":"closed","pr_number":2017},{"timestamp":"2026-03-27 13:14:01","agent":null,"operation":"new","target":"batch6-orbital-compute","domain":"space-development","description":"Merged PR #2013","status":"merged","pr_number":2013},{"timestamp":"2026-03-27 13:12:27","agent":null,"operation":"new","target":"batch8-settlement-power","domain":"space-development","description":"Merged PR #2015","status":"merged","pr_number":2015},{"timestamp":"2026-03-27 13:07:47","agent":null,"operation":"new","target":"batch7-space-manufacturing","domain":"space-development","description":"Merged PR #2014","status":"merged","pr_number":2014}],"limit":100,"cursor":"2026-03-27 13:07:47","has_more":true},
'/api/contributors?view=agent': {"contributors":[{"handle":"m3taversal","tier":"veteran","claims_merged":493,"ci":73.95,"domains":[],"last_contribution":"2026-03-12","principal":null},{"handle":"rio","tier":"contributor","claims_merged":17,"ci":7.4,"domains":[],"last_contribution":"2026-03-18","principal":"m3taversal"},{"handle":"leo","tier":"contributor","claims_merged":5,"ci":6.9,"domains":[],"last_contribution":"2026-03-19","principal":"m3taversal"},{"handle":"vida","tier":"contributor","claims_merged":9,"ci":3.35,"domains":[],"last_contribution":"2026-03-19","principal":"m3taversal"},{"handle":"theseus","tier":"contributor","claims_merged":13,"ci":2.75,"domains":[],"last_contribution":"2026-03-19","principal":"m3taversal"},{"handle":"astra","tier":"contributor","claims_merged":10,"ci":2.1,"domains":[],"last_contribution":"2026-03-19","principal":"m3taversal"},{"handle":"clay","tier":"contributor","claims_merged":2,"ci":1.7,"domains":[],"last_contribution":"2026-03-18","principal":"m3taversal"}],"view":"agent"},
'/api/contributors?view=principal': {"contributors":[{"handle":"m3taversal","tier":"veteran","claims_merged":549,"ci":98.15,"domains":[],"last_contribution":"2026-03-19","agents":["rio","theseus","astra","vida","leo","clay"]},{"handle":"heavey","tier":"veteran","claims_merged":10,"ci":1.5,"domains":[],"last_contribution":"2026-02-16","agents":[]},{"handle":"bostrom","tier":"contributor","claims_merged":9,"ci":1.35,"domains":[],"last_contribution":"2026-02-17","agents":[]},{"handle":"doug-shapiro","tier":"contributor","claims_merged":8,"ci":1.2,"domains":[],"last_contribution":"2026-03-01","agents":[]},{"handle":"conitzer","tier":"contributor","claims_merged":7,"ci":1.05,"domains":[],"last_contribution":"2026-03-26","agents":[]},{"handle":"hanson","tier":"contributor","claims_merged":6,"ci":0.9,"domains":[],"last_contribution":"2026-02-16","agents":[]},{"handle":"noah-smith","tier":"contributor","claims_merged":5,"ci":0.75,"domains":[],"last_contribution":"2026-03-06","agents":[]},{"handle":"bak","tier":"contributor","claims_merged":5,"ci":0.75,"domains":[],"last_contribution":"2026-02-16","agents":[]},{"handle":"aquino-michaels","tier":"contributor","claims_merged":5,"ci":0.75,"domains":[],"last_contribution":"2026-03-26","agents":[]},{"handle":"knuth","tier":"contributor","claims_merged":4,"ci":0.6,"domains":[],"last_contribution":"2026-03-26","agents":[]},{"handle":"kaufmann","tier":"contributor","claims_merged":3,"ci":0.45,"domains":[],"last_contribution":"2026-03-11","agents":[]},{"handle":"ward-whitt","tier":"contributor","claims_merged":3,"ci":0.45,"domains":[],"last_contribution":"2026-03-11","agents":[]},{"handle":"karpathy","tier":"contributor","claims_merged":3,"ci":0.45,"domains":[],"last_contribution":"2026-03-11","agents":[]},{"handle":"hidalgo","tier":"contributor","claims_merged":3,"ci":0.45,"domains":[],"last_contribution":"2026-02-16","agents":[]},{"handle":"blackmore","tier":"contributor","claims_merged":3,"ci":0.45,"domains":[],"last_contribution":"2026-02-16","agents":[]},{"handle":"areal","tier":"contributor","claims_merged":3,"ci":0.45,"domains":[],"last_contribution":"2026-03-26","agents":[]},{"handle":"friston","tier":"contributor","claims_merged":3,"ci":0.45,"domains":[],"last_contribution":"2026-03-26","agents":[]},{"handle":"ramstead","tier":"contributor","claims_merged":2,"ci":0.3,"domains":[],"last_contribution":"2026-03-11","agents":[]},{"handle":"dario-amodei","tier":"contributor","claims_merged":2,"ci":0.3,"domains":[],"last_contribution":"2026-03-07","agents":[]},{"handle":"centola","tier":"contributor","claims_merged":2,"ci":0.3,"domains":[],"last_contribution":"2026-02-28","agents":[]},{"handle":"hayek","tier":"contributor","claims_merged":2,"ci":0.3,"domains":[],"last_contribution":"2026-03-08","agents":[]},{"handle":"dhrumil","tier":"contributor","claims_merged":2,"ci":0.3,"domains":[],"last_contribution":"2026-03-26","agents":[]},{"handle":"larsson","tier":"contributor","claims_merged":1,"ci":0.15,"domains":[],"last_contribution":"2026-02-17","agents":[]},{"handle":"anthropic","tier":"contributor","claims_merged":1,"ci":0.15,"domains":[],"last_contribution":"2026-02-17","agents":[]},{"handle":"coase","tier":"contributor","claims_merged":1,"ci":0.15,"domains":[],"last_contribution":"2026-03-08","agents":[]},{"handle":"juarrero","tier":"contributor","claims_merged":1,"ci":0.15,"domains":[],"last_contribution":"2026-02-17","agents":[]},{"handle":"ostrom","tier":"contributor","claims_merged":1,"ci":0.15,"domains":[],"last_contribution":"2026-02-17","agents":[]},{"handle":"futard-io","tier":"contributor","claims_merged":1,"ci":0.15,"domains":[],"last_contribution":"2026-03-26","agents":[]},{"handle":"ganymede","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-03-16","agents":[]},{"handle":"metaproph3t","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-03-09","agents":[]},{"handle":"areal-dao","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-03-11","agents":[]},{"handle":"dan-slimmon","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-03-11","agents":[]},{"handle":"metanallok","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-03-09","agents":[]},{"handle":"mmdhrumil","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-03-11","agents":[]},{"handle":"theiaresearch","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-03-09","agents":[]},{"handle":"corless","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-03-11","agents":[]},{"handle":"oxranga","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-03-09","agents":[]},{"handle":"vlahakis","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-03-11","agents":[]},{"handle":"ceterispar1bus","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-03-05","agents":[]},{"handle":"futard.io","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2024-07-18","agents":[]},{"handle":"numerai","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-02-28","agents":[]},{"handle":"van-leeuwaarden","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-03-11","agents":[]},{"handle":"dagster","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-03-11","agents":[]},{"handle":"adams","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-03-07","agents":[]},{"handle":"tubefilter","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-03-11","agents":[]},{"handle":"swyx","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-03-09","agents":[]},{"handle":"spizzirri","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-02-17","agents":[]},{"handle":"simonw","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-03-09","agents":[]},{"handle":"tamim-ansary","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-02-21","agents":[]},{"handle":"clayton-christensen","tier":"contributor","claims_merged":0,"ci":0.0,"domains":[],"last_contribution":"2026-02-21","agents":[]}],"view":"principal"},
'/api/domains': {"domains":{"general":{"total_prs":255,"knowledge_prs":133,"contributors":[{"handle":"unknown","claims":133}]},"internet-finance":{"total_prs":166,"knowledge_prs":155,"contributors":[{"handle":"unknown","claims":155}]},"health":{"total_prs":70,"knowledge_prs":68,"contributors":[{"handle":"unknown","claims":68}]},"ai-alignment":{"total_prs":59,"knowledge_prs":55,"contributors":[{"handle":"unknown","claims":55}]},"space-development":{"total_prs":57,"knowledge_prs":54,"contributors":[{"handle":"unknown","claims":54}]},"entertainment":{"total_prs":34,"knowledge_prs":34,"contributors":[{"handle":"unknown","claims":34}]},"grand-strategy":{"total_prs":17,"knowledge_prs":7,"contributors":[{"handle":"unknown","claims":7}]},"energy":{"total_prs":2,"knowledge_prs":2,"contributors":[{"handle":"unknown","claims":2}]},"living-agents":{"total_prs":1,"knowledge_prs":1,"contributors":[{"handle":"unknown","claims":1}]},"collective-intelligence":{"total_prs":1,"knowledge_prs":1,"contributors":[{"handle":"unknown","claims":1}]}}}
};
// ============================================
// API CLIENT (with fallback)
// ============================================
let usingFallback = false;
async function apiGet(path) {
try {
const r = await fetch(`${API}${path}`);
if (!r.ok) throw new Error(r.status);
return await r.json();
} catch(e) {
console.warn(`API ${path}: falling back to snapshot`, e.message || e);
usingFallback = true;
// Exact match first, then fuzzy match (without query params) as last resort
if (FALLBACK[path]) return FALLBACK[path];
const key = Object.keys(FALLBACK).find(k => k.split('?')[0] === path.split('?')[0]);
if (key) return FALLBACK[key];
return null;
}
}
// ============================================
// ESCAPE
// ============================================
function esc(s) {
if (!s) return '';
return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
// ============================================
// RENDER: HEADER
// ============================================
function renderHeader(m) {
if (!m) return;
const merged = m.status_map?.merged || 0;
const rate = m.approval_rate;
const ttm = m.median_ttm_minutes;
document.getElementById('h-merged').textContent = merged;
document.getElementById('h-approval').textContent =
rate != null ? (rate < 1 ? (rate*100).toFixed(1)+'%' : rate.toFixed(1)+'%') : '--';
document.getElementById('h-ttm').textContent =
ttm != null ? ttm.toFixed(1)+'m' : '--';
const timeStr = new Date().toLocaleTimeString('en-US',{hour:'2-digit',minute:'2-digit',hour12:false});
document.getElementById('h-updated').textContent = usingFallback ? timeStr + ' (SNAPSHOT)' : timeStr;
}
// ============================================
// RENDER: VITALS
// ============================================
function renderVitals(m, vs) {
if (!vs) return;
// Claims
const merged = m?.status_map?.merged || 0;
const open = m?.status_map?.open || 0;
const el = (id) => document.getElementById(id);
el('vt-claims').textContent = merged;
el('vt-claims-sub').textContent = `${open} open`;
// Approval
const rate = m?.approval_rate;
if (rate != null) {
const pct = rate < 1 ? (rate*100).toFixed(1) : rate.toFixed(1);
el('vt-approval').textContent = pct + '%';
el('vt-approval-sub').textContent = `${m.approved_24h||0} approved 24h`;
}
// Orphan
const orph = vs.orphan_ratio;
if (orph) {
const pct = (orph.ratio * 100).toFixed(1);
el('vt-orphan').textContent = pct + '%';
el('vt-orphan').className = 'vital-value ' + statusCls(orph.status);
el('vt-orphan-sub').textContent = `${orph.count}/${orph.total}`;
}
// Evidence freshness
const fresh = vs.evidence_freshness;
if (fresh) {
el('vt-fresh').textContent = fresh.median_age_days + 'd';
el('vt-fresh').className = 'vital-value ' + (fresh.median_age_days <= 30 ? 'v-healthy' : 'v-warning');
el('vt-fresh-sub').textContent = `${fresh.fresh_30d_pct}% <30d`;
}
// Linkage
const link = vs.linkage_density;
if (link) {
el('vt-linkage').textContent = link.cross_domain_links;
el('vt-linkage').className = 'vital-value v-brand';
el('vt-linkage-sub').textContent = `${(link.cross_domain_ratio*100).toFixed(0)}% cross-domain`;
}
// Backlog
const rev = vs.review_throughput;
if (rev) {
el('vt-backlog').textContent = rev.backlog;
el('vt-backlog').className = 'vital-value ' + statusCls(rev.status);
el('vt-backlog-sub').textContent = `${rev.open_prs} open PRs`;
}
}
function statusCls(s) {
if (s === 'critical') return 'v-critical';
if (s === 'warning' || s === 'above_target') return 'v-warning';
return 'v-healthy';
}
// ============================================
// SPARKLINES from /api/snapshots
// ============================================
function drawSparkline(canvasId, data, color) {
const c = document.getElementById(canvasId);
if (!c || !data || data.length < 2) return;
const ctx = c.getContext('2d');
const w = c.width, h = c.height;
ctx.clearRect(0, 0, w, h);
const min = Math.min(...data);
const max = Math.max(...data);
const range = max - min || 1;
// Area fill
ctx.beginPath();
data.forEach((v, i) => {
const x = (i / (data.length - 1)) * w;
const y = h - ((v - min) / range) * (h - 4) - 2;
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
});
ctx.lineTo(w, h);
ctx.lineTo(0, h);
ctx.closePath();
ctx.fillStyle = (color || '#8B949E') + '15';
ctx.fill();
// Line
ctx.beginPath();
ctx.strokeStyle = color || '#8B949E';
ctx.lineWidth = 1.5;
data.forEach((v, i) => {
const x = (i / (data.length - 1)) * w;
const y = h - ((v - min) / range) * (h - 4) - 2;
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
});
ctx.stroke();
// Endpoint dot
const lastY = h - ((data[data.length-1] - min) / range) * (h - 4) - 2;
ctx.beginPath();
ctx.arc(w - 1, lastY, 2, 0, Math.PI * 2);
ctx.fillStyle = color || '#8B949E';
ctx.fill();
// Trend arrow (comparing first third avg to last third avg)
const third = Math.floor(data.length / 3);
const earlyAvg = data.slice(0, third).reduce((a,b)=>a+b,0) / third;
const lateAvg = data.slice(-third).reduce((a,b)=>a+b,0) / third;
const diff = lateAvg - earlyAvg;
const pctChange = earlyAvg !== 0 ? ((diff / Math.abs(earlyAvg)) * 100).toFixed(0) : '0';
// Store trend info on the canvas element for the vital sub to pick up
c._trend = { direction: diff > 0 ? 'up' : diff < 0 ? 'down' : 'flat', pct: pctChange };
}
async function renderSparklines() {
// 7-day window for meaningful trend visibility
const snaps = await apiGet('/api/snapshots?hours=168');
if (!snaps || !Array.isArray(snaps) || snaps.length < 2) return;
const extract = (field) => snaps.map(s => s[field]).filter(v => v != null);
drawSparkline('spark-claims', extract('total_prs'), '#8B949E');
drawSparkline('spark-approval', extract('approval_rate'), '#6E46E5');
drawSparkline('spark-orphan', extract('orphan_ratio'), '#D29922');
drawSparkline('spark-fresh', extract('evidence_age_median'), '#58A6FF');
drawSparkline('spark-linkage', extract('linkage_density'), '#BC8CFF');
drawSparkline('spark-backlog', extract('pending'), '#F85149');
// Update vital subs with 7d trend info
['claims','approval','orphan','fresh','linkage','backlog'].forEach(key => {
const canvas = document.getElementById('spark-' + key);
const sub = document.getElementById('vt-' + key + '-sub');
if (canvas?._trend && sub) {
const t = canvas._trend;
const arrow = t.direction === 'up' ? '↑' : t.direction === 'down' ? '↓' : '→';
const trendColor = t.direction === 'up' ? '#3FB950' : t.direction === 'down' ? '#F85149' : '#8B949E';
// For orphan ratio, down is good
const adjustedColor = key === 'orphan'
? (t.direction === 'down' ? '#3FB950' : t.direction === 'up' ? '#F85149' : '#8B949E')
: trendColor;
sub.innerHTML += ` <span style="color:${adjustedColor};font-size:9px" title="7d trend">${arrow}${Math.abs(t.pct)}%</span>`;
}
});
}
// ============================================
// RENDER: TIMELINE (real /api/activity data)
// ============================================
// Infer agent from domain when API returns null
const DOMAIN_AGENT = {
'internet-finance':'rio', 'entertainment':'clay', 'ai-alignment':'theseus',
'health':'vida', 'space-development':'astra', 'energy':'astra',
'manufacturing':'astra', 'robotics':'astra', 'grand-strategy':'leo',
'collective-intelligence':'theseus', 'critical-systems':'leo',
'living-agents':'theseus'
// 'general' intentionally omitted — unclassified domain, not attributable to any agent
};
const STATUS_COLORS = {
merged: '#3FB950',
closed: '#F85149',
open: '#D29922'
};
const DOMAIN_ABBREV = {
'entertainment':'ENT', 'technology':'TEC', 'finance':'FIN',
'internet-finance':'IFI', 'collective-agents':'COL', 'collective-intelligence':'COL',
'ai-alignment':'AIA', 'health':'HLT', 'space-development':'SPC',
'grand-strategy':'GRS', 'mechanisms':'MEC', 'living-capital':'LCP',
'teleohumanity':'TEL', 'critical-systems':'CRT', 'general':'GEN', 'unknown':'GEN'
};
async function renderTimeline() {
const feed = document.getElementById('timeline-feed');
const data = await apiGet('/api/activity?limit=100');
if (!data?.events?.length) {
feed.innerHTML = '<div style="padding:8px;color:var(--text-muted)">No activity data</div>';
return;
}
// Track most recent activity per agent for the agent panel
window._agentLastSeen = {};
data.events.forEach(e => {
const agent = e.agent || DOMAIN_AGENT[e.domain];
if (agent && AGENT_COLORS[agent] && e.timestamp) {
if (!window._agentLastSeen[agent] || e.timestamp > window._agentLastSeen[agent]) {
window._agentLastSeen[agent] = e.timestamp;
}
}
});
feed.innerHTML = data.events.map(e => {
const ts = e.timestamp || '';
const time = ts.slice(11, 16) || '--:--';
const agent = e.agent || DOMAIN_AGENT[e.domain] || 'auto-extract';
const agentColor = ALL_AGENT_COLORS[agent] || '#8B949E';
const dotColor = STATUS_COLORS[e.status] || '#8B949E';
// Compact format: DOMAIN_ABBREV · OP — description
const domAbbrev = DOMAIN_ABBREV[e.domain] || 'GEN';
const domColor = DOMAIN_COLORS[e.domain] || '#8B949E';
const op = (e.operation || '').toUpperCase();
const opColor = OP_COLORS[e.operation] || '#8B949E';
// Enrich bare "Merged PR #XXXX" / "Closed PR #XXXX" with domain + operation context
let desc = e.description || e.target || `PR #${e.pr_number || '?'}`;
const prMatch = desc.match(/^(Merged|Closed)\s+PR\s+#(\d+)$/);
if (prMatch) {
const action = prMatch[1];
const prNum = prMatch[2];
const domLabel = (!e.domain || e.domain === 'general') ? 'unclassified' : e.domain;
const opLabel = e.operation && e.operation !== 'new' ? e.operation : 'extraction';
desc = `${action} ${opLabel} in ${domLabel} (#${prNum})`;
}
const safe = esc(desc);
return `<div class="tl-row">
<span class="tl-ts">${time}</span>
<span class="tl-agent" style="color:${agentColor}">${agent}</span>
<span class="tl-dot" style="background:${dotColor}"></span>
<span class="tl-desc" title="${safe}"><span style="color:${domColor}">${domAbbrev}</span> · <span style="color:${opColor}">${op||'—'}</span> — ${safe}</span>
</div>`;
}).join('');
document.getElementById('feed-badge').textContent = data.events.length;
}
// ============================================
// RENDER: REJECTIONS
// ============================================
function renderRejections(m) {
if (!m?.rejection_reasons?.length) return;
const sec = document.getElementById('reject-section');
const list = document.getElementById('reject-list');
sec.style.display = 'block';
const maxCount = Math.max(...m.rejection_reasons.map(r=>r.count));
list.innerHTML = m.rejection_reasons.slice(0, 5).map(r => `
<div class="reject-row">
<span class="reject-tag">${esc(r.tag.replace(/_/g,' '))}</span>
<div class="reject-bar" style="width:${(r.count/maxCount)*80}px"></div>
<span class="reject-count">${r.count}</span>
</div>
`).join('');
}
// ============================================
// RENDER: DOMAIN BARS
// ============================================
function renderDomainBars(vs) {
const wrap = document.getElementById('domain-bars');
if (!vs?.domain_activity) { wrap.innerHTML = ''; return; }
const active = vs.domain_activity.active || [];
const stagnant = vs.domain_activity.stagnant || [];
// Sort by prs_7d descending
const sorted = [...active].sort((a,b) => (b.prs_7d||0) - (a.prs_7d||0));
const maxPrs = Math.max(...sorted.map(d => d.prs_7d||0), 1);
// Known canonical domains — filter out ghosts
const KNOWN_DOMAINS = new Set(['internet-finance','ai-alignment','health','entertainment',
'space-development','grand-strategy','energy','living-agents','collective-intelligence',
'critical-systems','general']);
let html = sorted.map(d => {
const name = d.domain || 'general';
const isUnclassified = !d.domain || name === 'general';
const color = DOMAIN_COLORS[name] || '#8B949E';
const pct = ((d.prs_7d||0)/maxPrs*100).toFixed(0);
const abbrev = DOMAIN_ABBREV[name] || 'GEN';
const rowStyle = isUnclassified ? 'opacity:0.5;font-style:italic' : '';
const barOpacity = isUnclassified ? '0.3' : '0.7';
return `<div class="domain-row" style="${rowStyle}">
<span class="domain-name" style="color:${color}">${abbrev} <span style="color:var(--text-muted);font-size:9px">${esc(isUnclassified ? 'unclassified' : name)}</span></span>
<div class="domain-bar-wrap"><div class="domain-bar" style="width:${pct}%;background:${color};opacity:${barOpacity}"></div></div>
<span class="domain-count">${d.prs_7d||0}</span>
</div>`;
}).join('');
// Show stagnant domains — only known canonical ones
// Distinguish intentionally paused (low-priority / pending agent) from unexpected inactivity
const PAUSED_DOMAINS = new Set(['collective-intelligence', 'critical-systems', 'living-agents']);
const validStagnant = stagnant.filter(n => KNOWN_DOMAINS.has(n));
if (validStagnant.length) {
html += `<div style="padding:4px 8px 2px;font-size:9px;text-transform:uppercase;color:var(--text-muted);letter-spacing:0.06em;border-top:1px solid var(--border);margin-top:2px">INACTIVE</div>`;
html += validStagnant.map(name => {
const abbrev = DOMAIN_ABBREV[name] || name.slice(0,3).toUpperCase();
const isPaused = PAUSED_DOMAINS.has(name);
const label = isPaused ? 'paused' : 'stagnant';
const labelColor = isPaused ? 'var(--text-muted)' : 'var(--status-warning)';
return `<div class="domain-row" style="opacity:0.4">
<span class="domain-name" style="color:${DOMAIN_COLORS[name]||'#484F58'}">${abbrev}</span>
<div class="domain-bar-wrap"><div class="domain-bar" style="width:0%"></div></div>
<span class="domain-count" style="color:${labelColor};font-size:9px;min-width:50px">${label}</span>
</div>`;
}).join('');
}
wrap.innerHTML = html;
}
// ============================================
// RENDER: AGENTS (real data from contributors API)
// ============================================
async function renderAgents() {
const list = document.getElementById('agent-list');
const data = await apiGet('/api/contributors?view=agent');
// Only show knowledge agents, not infra (epimetheus, ganymede, argus, oberon, rhea)
const agents = Object.entries(AGENT_COLORS);
// Build lookup from contributor data
const agentStats = {};
if (data?.contributors) {
data.contributors.forEach(c => {
const name = (c.handle || '').toLowerCase();
if (AGENT_COLORS[name]) agentStats[name] = c;
});
}
// Compute team CI average for context
const ciValues = agents.map(([name]) => agentStats[name]?.ci || 0);
const teamAvgCI = ciValues.length ? ciValues.reduce((a,b) => a+b, 0) / ciValues.length : 0;
const maxCI = Math.max(...ciValues, 1);
// CI formula tooltip
const ciTooltip = 'CI = sourcer×0.15 + extractor×0.05 + challenger×0.35 + synthesizer×0.25 + reviewer×0.20';
// Header row with CI context
let html = `<div class="agent-row" style="border-bottom:1px solid var(--border);padding-bottom:3px;margin-bottom:2px">
<span style="font-size:9px;text-transform:uppercase;letter-spacing:0.06em;color:var(--text-muted)">AGENT</span>
<span style="font-size:9px;text-transform:uppercase;letter-spacing:0.06em;color:var(--text-muted);text-align:right">MERGED</span>
<span style="font-size:9px;text-transform:uppercase;letter-spacing:0.06em;color:var(--text-muted);text-align:right;cursor:help" title="${ciTooltip}">CI ⓘ</span>
<span style="font-size:9px;text-transform:uppercase;letter-spacing:0.06em;color:var(--text-muted);text-align:right">LAST</span>
</div>`;
html += agents.map(([name, color]) => {
const stat = agentStats[name];
const total = stat?.claims_merged || 0;
const ci = stat?.ci || 0;
// Prefer activity-derived last-seen over stale contributor API date
const activityTs = window._agentLastSeen?.[name];
let lastDate = '--';
if (activityTs) {
lastDate = activityTs.slice(5, 10); // MM-DD from activity feed
} else if (stat?.last_contribution) {
lastDate = stat.last_contribution.slice(5);
}
const isRecent = activityTs && activityTs.slice(0, 10) >= new Date().toISOString().slice(0, 10);
const dateColor = isRecent ? 'var(--status-healthy)' : 'var(--text-muted)';
const ciColor = ci > teamAvgCI ? 'var(--status-healthy)' : ci > 0 ? 'var(--text-dim)' : 'var(--text-muted)';
return `<div class="agent-row">
<span class="agent-name" style="color:${color}">${name}</span>
<span style="color:var(--text-muted);font-size:10px;min-width:50px;text-align:right">${total}</span>
<span style="color:${ciColor};font-size:10px;min-width:40px;text-align:right" title="${ciTooltip}">${ci.toFixed(1)}</span>
<span style="color:${dateColor};font-size:10px;min-width:40px;text-align:right">${lastDate}</span>
</div>`;
}).join('');
// Team average footer
html += `<div style="padding:3px 8px;font-size:9px;color:var(--text-muted);border-top:1px solid var(--border);display:flex;justify-content:space-between">
<span>TEAM AVG</span>
<span style="color:var(--text-dim)">CI ${teamAvgCI.toFixed(1)}</span>
</div>`;
list.innerHTML = html;
}
// ============================================
// RENDER: CIRCUIT BREAKERS
// ============================================
function renderBreakers(m) {
const el = document.getElementById('breakers');
if (!m?.breakers) return;
const legend = `<div style="grid-column:1/-1;font-size:9px;color:var(--text-muted);margin-bottom:2px">
<span style="color:var(--status-healthy)">●</span> closed/0 fail
<span style="margin-left:6px;color:var(--status-warning)">●</span> open/tripped
<span style="margin-left:6px;color:var(--status-critical)">●</span> failures &gt;0
</div>`;
el.innerHTML = legend + Object.entries(m.breakers).map(([name, b]) => {
const color = b.state === 'closed' && b.failures === 0
? 'var(--status-healthy)'
: b.failures > 0
? 'var(--status-critical)'
: 'var(--status-warning)';
return `<div class="breaker">
<span class="breaker-dot" style="background:${color}"></span>
<span class="breaker-label">${name}</span>
</div>`;
}).join('');
}
// ============================================
// RENDER: FUNNEL
// ============================================
function renderFunnel(vs) {
const bar = document.getElementById('funnel-bar');
if (!vs?.funnel) return;
const f = vs.funnel;
bar.innerHTML = `
<span style="color:var(--text-muted)">FUNNEL</span>
<span class="funnel-val">${f.sources_total}</span><span class="funnel-label">sources</span>
<span class="funnel-arrow">&rarr;</span>
<span class="funnel-val">${f.sources_extracted}</span><span class="funnel-label">extracted</span>
<span class="funnel-arrow">&rarr;</span>
<span class="funnel-val">${f.prs_total}</span><span class="funnel-label">PRs</span>
<span class="funnel-arrow">&rarr;</span>
<span class="funnel-val">${f.prs_merged}</span><span class="funnel-label">merged</span>
<span class="funnel-rate">${(f.conversion_rate*100).toFixed(1)}% conversion</span>
`;
}
// ============================================
// RENDER: CONTRIBUTORS (lazy)
// ============================================
let contribLoaded = false;
async function loadContributors(view) {
const data = await apiGet(`/api/contributors?view=${view||'principal'}`);
if (!data?.contributors) return;
contribLoaded = true;
const wrap = document.getElementById('lb-wrap');
const sorted = [...data.contributors].sort((a,b) => (b.ci||0) - (a.ci||0));
const hdr = `<div class="lb-row lb-hdr">
<div>#</div><div>HANDLE</div><div>MERGED</div><div>TIER</div><div>DOMAINS</div><div>CI</div><div>LAST</div>
</div>`;
const rows = sorted.map((c, i) => {
const tierCls = c.tier === 'veteran' ? 'tier-veteran' : 'tier-contributor';
const last = c.last_contribution ? c.last_contribution.slice(5) : '--';
const domCount = c.domains?.length || 0;
return `<div class="lb-row">
<div class="lb-rank">${i+1}</div>
<div class="lb-name">${esc(c.handle)}</div>
<div class="lb-cell">${c.claims_merged||0}</div>
<div><span class="lb-tier ${tierCls}">${c.tier}</span></div>
<div class="lb-cell">${domCount}</div>
<div class="lb-score">${(c.ci||0).toFixed(2)}</div>
<div class="lb-cell">${last}</div>
</div>`;
}).join('');
wrap.innerHTML = hdr + rows;
}
// ============================================
// RENDER: DOMAINS (lazy)
// ============================================
let domainsLoaded = false;
async function loadDomains() {
const [domData, vsData] = await Promise.all([
apiGet('/api/domains'),
apiGet('/api/vital-signs')
]);
if (!domData?.domains) return;
domainsLoaded = true;
const stagnant = vsData?.domain_activity?.stagnant || [];
const activeMap = {};
(vsData?.domain_activity?.active || []).forEach(d => {
if (d.domain) activeMap[d.domain] = d;
});
// Sort domains by total_prs descending
const sorted = Object.entries(domData.domains).sort((a,b) => b[1].total_prs - a[1].total_prs);
const maxTotal = Math.max(...sorted.map(([,d]) => d.total_prs), 1);
// Compute summary stats
let totalPRs = 0, totalKnowledge = 0, total7d = 0;
let activeDomains = 0, stagnantDomains = stagnant.length;
const rows = document.getElementById('domains-rows');
rows.innerHTML = sorted.map(([name, d]) => {
const color = DOMAIN_COLORS[name] || '#8B949E';
const isStagnant = stagnant.includes(name);
const activity = activeMap[name];
const prs7d = activity?.prs_7d || 0;
const isUnclassified = !name || name === 'general';
const displayName = isUnclassified ? 'unclassified' : name;
const pctTotal = (d.total_prs / maxTotal * 100).toFixed(0);
const pctKnowledge = (d.knowledge_prs / maxTotal * 100).toFixed(0);
totalPRs += d.total_prs;
totalKnowledge += d.knowledge_prs;
total7d += prs7d;
if (!isStagnant && prs7d > 0) activeDomains++;
// Status badge
const PAUSED_SET = new Set(['collective-intelligence', 'critical-systems', 'living-agents']);
let badge = '';
if (isStagnant && PAUSED_SET.has(name)) {
badge = `<span style="background:rgba(139,148,158,0.15);color:#8B949E">PAUSED</span>`;
} else if (isStagnant) {
badge = `<span style="background:rgba(245,158,11,0.15);color:#F59E0B">STALE</span>`;
} else if (prs7d >= 20) {
badge = `<span style="background:rgba(63,185,80,0.15);color:#3FB950">HOT</span>`;
} else if (prs7d > 0) {
badge = `<span style="background:rgba(255,255,255,0.05);color:var(--text-dim)">ACTIVE</span>`;
} else {
badge = `<span style="background:rgba(255,255,255,0.03);color:var(--text-muted)">QUIET</span>`;
}
const rowClass = isStagnant ? ' stagnant' : '';
const nameStyle = name === 'general' ? 'font-style:italic;opacity:0.7' : '';
return `<div class="domain-row-big${rowClass}">
<div class="name" style="${nameStyle}">
<span class="dot" style="background:${color}"></span>
${esc(displayName)}
</div>
<div class="bar-cell">
<div class="domain-bar-outer">
<div class="domain-bar-knowledge" style="width:${pctTotal}%;background:${color}"></div>
<div class="domain-bar-inner" style="width:${pctKnowledge}%;background:${color}"></div>
</div>
</div>
<div class="num"><strong>${d.total_prs}</strong> <small>/ ${d.knowledge_prs}k</small></div>
<div class="num" style="color:${prs7d > 0 ? color : 'var(--text-muted)'}"><strong>${prs7d}</strong></div>
<div class="badge">${badge}</div>
</div>`;
}).join('');
// Render summary panel
const knowledgeRatio = totalPRs > 0 ? (totalKnowledge / totalPRs * 100).toFixed(1) : 0;
const summary = document.getElementById('domains-summary');
summary.innerHTML = `
<div class="domains-summary-card">
<h4>Overview</h4>
<div class="domains-summary-row"><span>Total domains</span><strong>${sorted.length}</strong></div>
<div class="domains-summary-row"><span>Active (7d)</span><strong style="color:var(--status-healthy)">${activeDomains}</strong></div>
<div class="domains-summary-row"><span>Stagnant</span><strong style="color:${stagnantDomains > 0 ? 'var(--status-warning)' : 'var(--text-dim)'}">${stagnantDomains}</strong></div>
<div class="domains-summary-row"><span>Total PRs</span><strong>${totalPRs.toLocaleString()}</strong></div>
<div class="domains-summary-row"><span>Knowledge PRs</span><strong>${totalKnowledge.toLocaleString()}</strong></div>
<div class="domains-summary-row"><span>Knowledge ratio</span><strong>${knowledgeRatio}%</strong></div>
<div class="domains-summary-row"><span>7d volume</span><strong>${total7d}</strong></div>
</div>
<div class="domains-summary-card">
<h4>Bar Legend</h4>
<div class="domains-summary-row" style="gap:8px">
<span style="display:flex;align-items:center;gap:4px">
<span style="display:inline-block;width:12px;height:8px;background:var(--brand);border-radius:1px"></span> Knowledge PRs
</span>
</div>
<div class="domains-summary-row" style="gap:8px">
<span style="display:flex;align-items:center;gap:4px">
<span style="display:inline-block;width:12px;height:8px;background:var(--brand);opacity:0.35;border-radius:1px"></span> Total PRs (incl. infra)
</span>
</div>
</div>
<div class="domains-summary-card">
<h4>Status Key</h4>
<div class="domains-summary-row"><span style="color:#3FB950">HOT</span><span>20+ PRs / 7d</span></div>
<div class="domains-summary-row"><span style="color:var(--text-dim)">ACTIVE</span><span>1-19 PRs / 7d</span></div>
<div class="domains-summary-row"><span style="color:var(--text-muted)">QUIET</span><span>0 PRs / 7d</span></div>
<div class="domains-summary-row"><span style="color:#F59E0B">STALE</span><span>No activity &gt;7d (unexpected)</span></div>
<div class="domains-summary-row"><span style="color:#8B949E">PAUSED</span><span>Intentionally inactive</span></div>
</div>
`;
}
// ============================================
// TABS
// ============================================
document.querySelectorAll('.tab-bar .tab').forEach(t => {
t.addEventListener('click', () => {
document.querySelectorAll('.tab-bar .tab').forEach(x => x.classList.remove('active'));
t.classList.add('active');
const v = t.dataset.view;
document.querySelectorAll('.view').forEach(x => x.classList.remove('active'));
document.getElementById('v-' + v).classList.add('active');
if (v === 'contributors' && !contribLoaded) loadContributors('principal');
if (v === 'domains' && !domainsLoaded) loadDomains();
});
});
document.querySelectorAll('[data-cview]').forEach(b => {
b.addEventListener('click', () => {
document.querySelectorAll('[data-cview]').forEach(x => x.classList.remove('active'));
b.classList.add('active');
loadContributors(b.dataset.cview);
});
});
// ============================================
// INIT
// ============================================
async function init() {
const [metrics, vitals] = await Promise.all([
apiGet('/api/metrics'),
apiGet('/api/vital-signs')
]);
renderHeader(metrics);
renderVitals(metrics, vitals);
renderSparklines();
await renderTimeline();
renderRejections(metrics);
renderDomainBars(vitals);
await renderAgents();
renderBreakers(metrics);
renderFunnel(vitals);
// Update live indicator if using fallback
if (usingFallback) {
const dot = document.getElementById('live-dot');
const label = document.getElementById('live-label');
if (dot) { dot.style.background = 'var(--status-warning)'; dot.style.animation = 'none'; }
if (label) label.textContent = 'SNAPSHOT';
}
// Auto-refresh
setInterval(async () => {
const [m, v] = await Promise.all([
apiGet('/api/metrics'),
apiGet('/api/vital-signs')
]);
renderHeader(m);
renderVitals(m, v);
renderSparklines();
renderBreakers(m);
renderFunnel(v);
renderDomainBars(v);
renderTimeline();
}, REFRESH_MS);
}
init();
</script>
</body>
</html>