[object Object]

← back to Ventura Corridor

iter 161: /health.html — pipeline-wide one-page status w/ pipeline+top-tapped+growth panels, refresh 30s

b39e8cf520ba3f036aef9da2775c3ab637745b4f · 2026-05-06 19:41:36 -0700 · SteveStudio2

Files touched

Diff

commit b39e8cf520ba3f036aef9da2775c3ab637745b4f
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Wed May 6 19:41:36 2026 -0700

    iter 161: /health.html — pipeline-wide one-page status w/ pipeline+top-tapped+growth panels, refresh 30s
---
 public/health.html  | 146 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 public/today.html   |   1 +
 src/server/index.ts |   3 +-
 3 files changed, 149 insertions(+), 1 deletion(-)

diff --git a/public/health.html b/public/health.html
new file mode 100644
index 0000000..ef086c0
--- /dev/null
+++ b/public/health.html
@@ -0,0 +1,146 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<title>Health · The Corridor</title>
+<meta name="viewport" content="width=device-width,initial-scale=1">
+<style>
+@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;1,400;1,500&family=Inter:wght@200;300;400;500&family=JetBrains+Mono:wght@200;300&display=swap');
+:root{--noir:#0a0a0c;--noir-rise:#131316;--ink:#f0ece2;--ink-mute:#888475;--metal:#b89968;--metal-glow:#d4b683;--green:#6a9b73;--red:#b66565;--yellow:#c9a942;--rule:#2a2622}
+*{box-sizing:border-box}body{margin:0;background:var(--noir);color:var(--ink);font-family:Inter,sans-serif;font-weight:300}
+header{padding:30px 32px 16px;border-bottom:1px solid var(--rule)}
+header h1{font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:400;font-size:32px;margin:0}
+header h1 em{color:var(--metal)}
+header .sub{font-size:11px;letter-spacing:.26em;text-transform:uppercase;color:var(--ink-mute);margin-top:4px}
+nav{padding:10px 32px;border-bottom:1px solid var(--rule);display:flex;gap:12px;flex-wrap:wrap;font-size:9px;letter-spacing:.26em;text-transform:uppercase}
+nav a{color:var(--ink-mute);text-decoration:none;padding:5px 10px;border:1px solid var(--rule)}
+nav a:hover, nav a.active{color:var(--metal);border-color:var(--metal)}
+
+main{padding:32px;max-width:1200px;margin:0 auto;display:grid;grid-template-columns:repeat(auto-fit,minmax(360px,1fr));gap:24px}
+.panel{background:var(--noir-rise);border:1px solid var(--rule);padding:24px}
+.panel.ok{border-left:3px solid var(--green)}
+.panel.warn{border-left:3px solid var(--yellow)}
+.panel.err{border-left:3px solid var(--red)}
+.panel h3{font-family:'Cormorant Garamond',serif;font-style:italic;color:var(--metal);font-size:24px;margin:0 0 4px;font-weight:500}
+.panel .what{font-size:9px;letter-spacing:.32em;text-transform:uppercase;color:var(--ink-mute);margin-bottom:14px}
+.row{display:grid;grid-template-columns:1fr auto;gap:14px;padding:6px 0;border-bottom:1px dotted var(--rule);font-size:13px;font-family:'JetBrains Mono',monospace;align-items:baseline}
+.row .k{color:var(--ink)}
+.row .v{color:var(--metal-glow);font-weight:500}
+.row .v.ok::before{content:'✓ ';color:var(--green)}
+.row .v.warn::before{content:'⚠ ';color:var(--yellow)}
+.row .v.err::before{content:'✕ ';color:var(--red)}
+.summary{grid-column:1/-1;text-align:center;padding:18px;background:var(--noir-rise);border:1px solid var(--rule);font-family:'Cormorant Garamond',serif;font-style:italic;font-size:24px}
+.summary.ok{color:var(--green)}
+.summary.warn{color:var(--yellow)}
+.summary.err{color:var(--red)}
+.last-up{font-size:11px;color:var(--ink-mute);font-family:'JetBrains Mono',monospace;text-align:right;padding:0 32px 24px;font-style:normal}
+</style>
+</head>
+<body>
+<header>
+  <h1>Health <em>check</em></h1>
+  <div class="sub">whole-pipeline status · refreshed every 30s</div>
+</header>
+<nav>
+  <a href="/today.html">today</a>
+  <a href="/magazine.html">magazine</a>
+  <a href="/issue">issue</a>
+  <a href="/coverage.html">coverage</a>
+  <a href="/health.html" class="active">health</a>
+</nav>
+<main id="root"><div class="summary">Loading…</div></main>
+<div class="last-up" id="last-up"></div>
+<script>
+function fmt(n) { return Number(n).toLocaleString('en-US'); }
+function escHtml(s) { return String(s ?? '').replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c])); }
+
+async function load() {
+  let q, c, h, gen, sb, fb, inq;
+  try {
+    [q, c, h, gen, sb, fb, inq] = await Promise.all([
+      fetch('/api/magazine/quality-flags').then(r => r.json()),
+      fetch('/api/coverage').then(r => r.json()),
+      fetch('/api/coverage/history').then(r => r.json()),
+      fetch('/api/magazine/gen-queue?limit=1').then(r => r.json()),
+      fetch('/api/magazine/scoreboard').then(r => r.json()),
+      fetch('/api/feedback').then(r => r.json()),
+      fetch('/api/sponsor/inquiries').then(r => r.json()),
+    ]);
+  } catch (e) {
+    document.getElementById('root').innerHTML = `<div class="summary err">cannot reach API: ${escHtml(e.message)}</div>`;
+    return;
+  }
+
+  const lastSnap = (h.snapshots || []).at(-1);
+  const sinceLastSnap = lastSnap ? ((Date.now() - new Date(lastSnap.taken_at).getTime()) / 3600000) : null;
+  const fbUnread = (fb.rows || []).filter(r => !r.reviewed).length;
+  const inqOpen = (inq.rows || []).filter(r => r.status === 'new').length;
+  const queueDepth = (gen.queue || []).length === 0 ? 'empty' : `${(gen.queue || []).length}+ waiting`;
+
+  const checks = [
+    { ok: c.totals.published >= 1, k: 'Published features', v: fmt(c.totals.published) },
+    { ok: c.totals.draft >= 0,     k: 'Draft features', v: fmt(c.totals.draft) },
+    { ok: c.totals.reviewed >= 0,  k: 'Reviewed (awaiting publish)', v: fmt(c.totals.reviewed) },
+    { ok: q.count === 0,           k: 'Quality flags', v: q.count === 0 ? 'none' : `${q.count} issues`, status: q.count === 0 ? 'ok' : 'warn' },
+    { ok: c.totals.total_biz >= 1000, k: 'Businesses indexed', v: fmt(c.totals.total_biz) },
+    { ok: queueDepth !== 'empty',  k: 'Gen queue', v: queueDepth, status: queueDepth === 'empty' ? 'warn' : 'ok' },
+    { ok: lastSnap && sinceLastSnap < 26, k: 'Last coverage snapshot', v: sinceLastSnap == null ? 'never' : `${sinceLastSnap.toFixed(1)}h ago`, status: sinceLastSnap == null ? 'warn' : sinceLastSnap < 26 ? 'ok' : 'warn' },
+    { ok: fbUnread === 0,          k: 'Reader feedback unread', v: fbUnread === 0 ? '0' : `${fbUnread} unread`, status: fbUnread === 0 ? 'ok' : 'warn' },
+    { ok: inqOpen === 0,           k: 'Sponsor inquiries open', v: inqOpen === 0 ? '0' : `${inqOpen} open`, status: inqOpen === 0 ? 'ok' : 'warn' },
+  ];
+
+  const overall = checks.every(x => (x.status || (x.ok ? 'ok' : 'err')) === 'ok')
+    ? 'ok' : checks.some(x => (x.status || (x.ok ? 'ok' : 'err')) === 'err') ? 'err' : 'warn';
+  const overallText = overall === 'ok' ? 'All systems normal.' : overall === 'warn' ? 'Some attention items.' : 'Something is broken.';
+
+  let html = `<div class="summary ${overall}">${escHtml(overallText)}</div>`;
+  html += `<div class="panel ${overall}">
+    <h3>Pipeline</h3>
+    <div class="what">core counts · queues · flags</div>
+    ${checks.map(c => {
+      const status = c.status || (c.ok ? 'ok' : 'err');
+      return `<div class="row"><span class="k">${escHtml(c.k)}</span><span class="v ${status}">${escHtml(c.v)}</span></div>`;
+    }).join('')}
+  </div>`;
+
+  // Top boards
+  const top = (sb.top_tapped || []).slice(0, 5);
+  html += `<div class="panel ok">
+    <h3>Top tapped this week</h3>
+    <div class="what">most outbound clicks</div>
+    ${top.length === 0 ? '<div style="font-style:italic;color:var(--ink-mute);font-family:Cormorant Garamond,serif">no taps yet</div>' :
+      top.map(t => `<div class="row"><span class="k"><a href="/magazine/${t.id}" style="color:var(--ink);text-decoration:none">${escHtml((t.headline || '').slice(0, 40))}</a></span><span class="v">${t.taps} taps</span></div>`).join('')}
+  </div>`;
+
+  // Coverage growth
+  const snaps = h.snapshots || [];
+  if (snaps.length >= 2) {
+    const first = snaps[0], last = snaps[snaps.length - 1];
+    const days = (new Date(last.taken_at) - new Date(first.taken_at)) / 86400000;
+    const grew = Number(last.total_features) - Number(first.total_features);
+    const rate = days > 0 ? (grew / days).toFixed(1) : '—';
+    html += `<div class="panel ok">
+      <h3>Growth</h3>
+      <div class="what">since first snapshot</div>
+      <div class="row"><span class="k">Days observed</span><span class="v">${days.toFixed(1)}</span></div>
+      <div class="row"><span class="k">Features added</span><span class="v">+${grew}</span></div>
+      <div class="row"><span class="k">Avg per day</span><span class="v">${rate}</span></div>
+      <div class="row"><span class="k">Snapshots taken</span><span class="v">${snaps.length}</span></div>
+    </div>`;
+  } else {
+    html += `<div class="panel warn">
+      <h3>Growth</h3>
+      <div class="what">need ≥2 snapshots for trend</div>
+      <div class="row"><span class="k">Snapshots taken</span><span class="v">${snaps.length}</span></div>
+      <div class="row"><span class="k">Next snapshot</span><span class="v">11:55 PM</span></div>
+    </div>`;
+  }
+
+  document.getElementById('root').innerHTML = html;
+  document.getElementById('last-up').textContent = `last refresh ${new Date().toLocaleTimeString()}`;
+}
+load();
+setInterval(load, 30_000);
+</script>
+</body>
+</html>
diff --git a/public/today.html b/public/today.html
index def2c6f..390daa1 100644
--- a/public/today.html
+++ b/public/today.html
@@ -126,6 +126,7 @@
     <a href="/cover-picker.html">⛧ cover</a>
     <a href="/coverage.html">▰ coverage</a>
     <a href="/magazine.html?quality=1" id="nav-quality" style="color:var(--ink-mute)">🔧 quality</a>
+    <a href="/health.html">♥ health</a>
     <a href="/crawl-derby.html">🏇 derby</a>
     <a href="/today.html" class="active">today</a>
     <span class="theme-toggle-host" style="display:inline-flex;align-items:center;margin-left:6px"></span>
diff --git a/src/server/index.ts b/src/server/index.ts
index 4756dc0..c785032 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -85,7 +85,8 @@ const ADMIN_PATHS = [
   /^\/words\/?$/i,
   /^\/quotes\/?$/i,
   /^\/coverage(\.html)?\/?$/i,
-  /^\/api\/coverage\/?$/i,
+  /^\/api\/coverage(\/.*)?$/i,
+  /^\/health(\.html)?\/?$/i,
   /^\/sitemap\.xml\/?$/i,
   /^\/robots\.txt\/?$/i,
   /^\/issue(\/[\w-]+)?\/feed\.xml\/?$/i,

← 4fc18b8 iter 160: /issue.txt — plain-text dump of full issue (50KB,  ·  back to Ventura Corridor  ·  iter 162: /api/magazine/:id/social-pack.json — programmatic fcadf64 →