← back to AbramsEgo
scoreboard: at-a-glance game-state hero panel (self-funding, wins, canary record, fleet, sessions)
27e1f69867f6f34686d2ba64236ac34ecc61a11c · 2026-07-08 09:02:27 -0700 · Steve
Files touched
Diff
commit 27e1f69867f6f34686d2ba64236ac34ecc61a11c
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Jul 8 09:02:27 2026 -0700
scoreboard: at-a-glance game-state hero panel (self-funding, wins, canary record, fleet, sessions)
---
public/index.html | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 80 insertions(+)
diff --git a/public/index.html b/public/index.html
index 2bba8ce..f095d1d 100644
--- a/public/index.html
+++ b/public/index.html
@@ -44,6 +44,26 @@
.span12{grid-column:span 12}.span8{grid-column:span 8}.span7{grid-column:span 7}.span6{grid-column:span 6}
.span5{grid-column:span 5}.span4{grid-column:span 4}.span3{grid-column:span 3}
@media(max-width:1000px){.span8,.span7,.span6,.span5,.span4,.span3{grid-column:span 12}}
+ /* ---- SCOREBOARD (the at-a-glance game-state hero) ---- */
+ .scoreboard{background:linear-gradient(135deg,#0e1826,#0a1018 60%,#0d141d);
+ border:1px solid #263a52;box-shadow:0 0 0 1px #4d9fff10, inset 0 0 60px #4d9fff08}
+ .sb-head{display:flex;align-items:center;gap:14px;flex-wrap:wrap;margin-bottom:14px}
+ .sb-status{font-size:13px;font-weight:700;letter-spacing:2px;text-transform:uppercase;
+ padding:5px 14px;border-radius:8px;border:1px solid var(--line)}
+ .sb-status.win{color:var(--good);border-color:#3fd18b55;background:#3fd18b12;text-shadow:0 0 14px #3fd18b55}
+ .sb-status.hold{color:var(--warn);border-color:#ffb45455;background:#ffb45412;text-shadow:0 0 14px #ffb45455}
+ .sb-status.down{color:var(--bad);border-color:#ff5d6c55;background:#ff5d6c12;text-shadow:0 0 14px #ff5d6c55}
+ .sb-clock{margin-left:auto;font-size:11px;color:var(--dim)}
+ .sb-tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px}
+ .sb-tile{background:#0b1421;border:1px solid var(--line);border-radius:12px;padding:14px 16px;position:relative;overflow:hidden}
+ .sb-tile::after{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--dim);opacity:.6}
+ .sb-tile.good::after{background:var(--good)} .sb-tile.warn::after{background:var(--warn)}
+ .sb-tile.bad::after{background:var(--bad)} .sb-tile.gold::after{background:var(--gold)} .sb-tile.accent::after{background:var(--accent)}
+ .sb-tile .sk{font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:1px}
+ .sb-tile .sv{font-size:38px;font-weight:800;line-height:1.05;margin-top:4px;font-variant-numeric:tabular-nums}
+ .sb-tile .su{font-size:15px;font-weight:600;color:var(--dim);margin-left:4px}
+ .sb-tile .ss{font-size:10.5px;color:var(--dim);margin-top:5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
+ .sb-tile.hero .sv{font-size:44px}
.big{font-size:30px;font-weight:700}
.good{color:var(--good)}.warn{color:var(--warn)}.bad{color:var(--bad)}.gold{color:var(--gold)}.dim{color:var(--dim)}
.row{display:flex;justify-content:space-between;gap:10px;padding:4px 0;border-bottom:1px dashed #ffffff08}
@@ -113,6 +133,16 @@
<main id="main">
<div class="alert" id="alert"></div>
+ <!-- SCOREBOARD — at-a-glance game-state of the whole operation (all fields from /api/data) -->
+ <section class="card span12 scoreboard">
+ <div class="sb-head">
+ <h2 style="margin:0">🏟 Scoreboard</h2>
+ <span class="sb-status" id="sb-status">—</span>
+ <span class="sb-clock" id="sb-clock"></span>
+ </div>
+ <div class="sb-tiles" id="sb-tiles"></div>
+ </section>
+
<!-- SELF-FUNDING P&L (the money panel) -->
<section class="card span8">
<h2>💰 Self-Funding P&L <span class="muted" id="pnlwin">· today</span></h2>
@@ -381,6 +411,9 @@ async function load(){
const ss=(d.sessions&&d.sessions.recent)||[];
$('sessions').innerHTML=ss.map(x=>lrow(esc(x.title||x.cwd||'session'),`${x.msgCount!=null?x.msgCount+' msg':''}`,whenChip(x.lastTs,x.mtime,x.firstTs))).join('')||'<span class="muted">no sessions</span>';
+ // ---- scoreboard (the game-state hero; reuses everything already in d) ----
+ renderScoreboard(d);
+
// ---- alert banner ----
const alerts=[];
if((s.diskUsedPct||0)>=90) alerts.push('disk '+s.diskUsedPct+'%');
@@ -560,6 +593,53 @@ function renderCrons(d){
});
feedGrid('crons', jobs);
}
+// ---- SCOREBOARD ------------------------------------------------------------
+// The at-a-glance "game state" hero. Every number is pulled straight from the
+// live /api/data snapshot (P&L, wins, canaries, fleet, sessions) — nothing is
+// invented. Headline STATUS is a rolled-up verdict; tiles are the big scores.
+function renderScoreboard(d){
+ const p=(d.pnl&&d.pnl.today)||{}, w=d.wins||{};
+ const cans=(d.canaries&&d.canaries.canaries)||[];
+ const k=d.kamatera||{}, l=d.localFleet||{}, us=d.usageSummary||{};
+ // canary W-L record: wins = PASS/UP/HEALTHY/OK, losses = FAIL/DOWN/CRIT, warns split out
+ const rank=(v)=>{v=(v||'').toUpperCase(); return /FAIL|DOWN|CRIT/.test(v)?'L':/WARN|DEGRAD/.test(v)?'W?':/PASS|UP|HEALTH|OK/.test(v)?'W':'?';};
+ const rec={W:0,'W?':0,L:0,'?':0}; cans.forEach(c=>rec[rank(c.verdict||c.status)]++);
+ const canFail=rec.L, canWarn=rec['W?'], canTotal=cans.length;
+ const fleetUp=(k.up||0)+(l.up||0), fleetTotal=(k.total||0)+(l.total||0);
+ const fleetDown=fleetTotal-fleetUp;
+ const selfPct=p.selfFundingPct||0;
+ const net=p.net||0;
+
+ // headline STATUS — the "who's winning" call. DOWN if a canary is failing or a
+ // proc is down; HOLD if warns or not self-funding; WIN only when all-green.
+ let status='WIN', scls='win';
+ if(canFail>0 || fleetDown>0){ status='DEFENSE'; scls='down'; }
+ else if(canWarn>0 || selfPct<100){ status='HOLDING'; scls='hold'; }
+ $('sb-status').textContent=status; $('sb-status').className='sb-status '+scls;
+ $('sb-clock').innerHTML=whenChip(d.builtAt)+' · live';
+
+ // tiles — big legible scores. self-funding is the headline "score".
+ const fundCls=selfPct>=100?'good':selfPct>0?'warn':'bad';
+ const canCls=canFail>0?'bad':canWarn>0?'warn':'good';
+ const fleetCls=fleetDown>0?'bad':'good';
+ const tiles=[
+ {cls:'hero '+fundCls, k:'Self-funding', v:selfPct, u:'%',
+ s:`${usd(p.revenue)} rev − ${usd(p.cost)} cost = <b class="${net>=0?'good':'bad'}">${usd(net)}</b>`},
+ {cls:'gold', k:'Wins today', v:(w.today||0), u:'',
+ s:`${(w.count||0).toLocaleString()} all-time`},
+ {cls:canCls, k:'Canary record', v:`${rec.W}–${canFail}`, u:'',
+ s:`${canTotal} checks${canWarn?` · ${canWarn} warn`:''}`},
+ {cls:fleetCls, k:'Fleet online', v:fleetUp, u:'/'+fleetTotal,
+ s:`Mac2 ${l.up||0}/${l.total||0} · Kam ${k.up||0}/${k.total||0}${k.stale?' (stale)':''}`},
+ {cls:'accent', k:'Live sessions', v:(us.activeSessions||0), u:'',
+ s:`${(us.subagentRuns||0).toLocaleString()} sub-agent runs`},
+ ];
+ $('sb-tiles').innerHTML=tiles.map(t=>
+ `<div class="sb-tile ${t.cls}"><div class="sk">${esc(t.k)}</div>`+
+ `<div class="sv">${t.v}${t.u?`<span class="su">${esc(t.u)}</span>`:''}</div>`+
+ `<div class="ss">${t.s}</div></div>`).join('');
+}
+
// canary board — each canary is a card with a created/scanned date+time; grid
// gets sort + density + infinite scroll (verdict/staleness/name modes).
function renderCanaries(cans){
← 1b0ece9 auto-save: 2026-07-08T08:34:49 (1 files) — data/local-fleet.
·
back to AbramsEgo
·
auto-save: 2026-07-08T09:04:55 (1 files) — data/local-fleet. d4825b6 →