[object Object]

← back to Ventura Corridor

feat(coverage): "tonight on the corridor" glance footer

e4ee322a324ea1e2d643114cd25116b5fa6e4b2b · 2026-05-08 01:12:23 -0700 · SteveStudio2

One-line session summary at the bottom of /coverage.html showing
commits / insertion lines / news articles+biz / ideas accepted/total.
Reads /api/session-stats which aggregates git log + news_items +
ideas.jsonl in a single endpoint (60s cache).

Visible at the dramatic black-and-gold footer panel below all the
existing analytics sections so Steve sees the run scorecard at the
end of the page without scrolling out into other tools.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit e4ee322a324ea1e2d643114cd25116b5fa6e4b2b
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Fri May 8 01:12:23 2026 -0700

    feat(coverage): "tonight on the corridor" glance footer
    
    One-line session summary at the bottom of /coverage.html showing
    commits / insertion lines / news articles+biz / ideas accepted/total.
    Reads /api/session-stats which aggregates git log + news_items +
    ideas.jsonl in a single endpoint (60s cache).
    
    Visible at the dramatic black-and-gold footer panel below all the
    existing analytics sections so Steve sees the run scorecard at the
    end of the page without scrolling out into other tools.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 public/coverage.html | 17 ++++++++++++++++
 src/server/index.ts  | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 73 insertions(+)

diff --git a/public/coverage.html b/public/coverage.html
index d58b097..b86b5da 100644
--- a/public/coverage.html
+++ b/public/coverage.html
@@ -119,6 +119,11 @@ main{padding:32px;max-width:1200px;margin:0 auto}
   <div id="news-board"></div>
 </div>
 
+<div id="session-glance" style="margin-top:48px;padding:18px 22px;background:linear-gradient(180deg,var(--noir-rise),#0a0a0c);border:2px solid var(--metal);border-radius:6px;font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--cream);text-align:center;letter-spacing:.06em">
+  <span style="color:var(--metal);font-size:10px;letter-spacing:.32em;text-transform:uppercase">— tonight on the corridor —</span>
+  <div id="glance-line" style="margin-top:10px;color:var(--metal-glow);font-size:14px">loading…</div>
+</div>
+
 </main>
 <script>
 function escHtml(s) { return String(s ?? '').replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c])); }
@@ -238,6 +243,18 @@ async function load() {
     }
   } catch (e) { /* skip */ }
 
+  // Session at a glance — one-liner summary for the night
+  try {
+    const s = await fetch('/api/session-stats').then(r => r.json());
+    const parts = [
+      `${s.commits} commits`,
+      `${s.insertions.toLocaleString()} lines`,
+      `${s.news_total} articles · ${s.news_biz} biz`,
+      `${s.ideas_accepted}/${s.ideas_accepted + s.ideas_rejected} ideas accepted`
+    ];
+    document.getElementById('glance-line').innerHTML = parts.join(' · ');
+  } catch(e) {}
+
   // News scrape calendar — 90-day heatmap above the heatmap section
   try {
     const cal = await fetch('/api/news/by-day').then(r => r.json());
diff --git a/src/server/index.ts b/src/server/index.ts
index bfdf3bb..78ef0d2 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -5146,6 +5146,62 @@ app.get('/api/news/archive', async (_req, res) => {
 // entries. Pure read; idea-loop owns its own writes.
 // Idea-loop stats: accept/reject totals + breakdown by type. Useful to spot
 // "is the panel too generous" (>50% accept) or "too harsh" (<5% accept).
+// Session-at-a-glance — single endpoint that aggregates "what was tonight"
+// for the coverage page footer. Reads git log for commit count + LoC,
+// news_items for content state, idea-loop jsonl for creative state.
+app.get('/api/session-stats', async (_req, res) => {
+  try {
+    const fs = await import('node:fs/promises');
+    const path = await import('node:path');
+    const { exec } = await import('node:child_process');
+    const execP = (cmd: string) => new Promise<string>((resolve) => {
+      exec(cmd, { cwd: process.cwd(), encoding: 'utf8', timeout: 4000 }, (_e, out) => resolve(out || ''));
+    });
+
+    // Tonight = since the news pipeline started (7594bb3, ~6 hours ago)
+    const SESSION_BASE = '7594bb3';
+    const commitCount = parseInt((await execP(`git rev-list --count ${SESSION_BASE}..HEAD 2>/dev/null`)).trim(), 10) || 0;
+    const shortStat = (await execP(`git diff ${SESSION_BASE}..HEAD --shortstat 2>/dev/null`)).trim();
+    const insMatch = shortStat.match(/(\d+) insertion/);
+    const insertions = insMatch ? parseInt(insMatch[1], 10) : 0;
+
+    // News + biz counts
+    const newsR = await query(`
+      SELECT
+        COUNT(*)::int AS news_total,
+        COUNT(DISTINCT business_id)::int AS news_biz,
+        COUNT(*) FILTER (WHERE summary IS NOT NULL)::int AS summarized
+      FROM news_items
+    `);
+
+    // Ideas
+    const home = process.env.HOME || require('os').homedir() || '';
+    let ideasAccepted = 0, ideasRejected = 0;
+    try {
+      const a = await fs.readFile(path.join(home, '.claude', 'skills', 'idea-loop', 'data', 'ideas.jsonl'), 'utf8');
+      ideasAccepted = a.split(/\r?\n/).filter(Boolean).length;
+    } catch {}
+    try {
+      const r = await fs.readFile(path.join(home, '.claude', 'skills', 'idea-loop', 'data', 'rejects.jsonl'), 'utf8');
+      ideasRejected = r.split(/\r?\n/).filter(Boolean).length;
+    } catch {}
+
+    res.set('Cache-Control', 'public, max-age=60');
+    res.json({
+      commits: commitCount,
+      insertions,
+      news_total: newsR.rows[0]?.news_total || 0,
+      news_biz: newsR.rows[0]?.news_biz || 0,
+      summarized: newsR.rows[0]?.summarized || 0,
+      ideas_accepted: ideasAccepted,
+      ideas_rejected: ideasRejected,
+      session_base: SESSION_BASE
+    });
+  } catch (e: any) {
+    res.status(500).json({ error: e.message });
+  }
+});
+
 app.get('/api/ideas/stats', async (_req, res) => {
   try {
     const fs = await import('node:fs/promises');

← be002e1 fix(coverage): wire JS for rejects drawer (was empty since t  ·  back to Ventura Corridor  ·  feat(coverage): expose ideas_rejected on homepage strip alon b552dfe →