← back to Ventura Corridor
feat(coverage): idea-loop feed bridge on /coverage.html
dab658539e9a106137eb776040e4fce29292a711 · 2026-05-07 22:23:05 -0700 · SteveStudio2
Read-only bridge to the idea-loop skill so accepted ideas surface in
the corridor coverage page alongside the news analytics. Read from
~/.claude/skills/idea-loop/data/ideas.jsonl on demand, sorted by
score_total DESC, top 20.
Routes:
GET /api/ideas/recent {count, rows[]} from ideas.jsonl. 60s cache.
Graceful "idea-loop not yet running" if file
missing.
UI:
/coverage.html new "Idea-loop" section above News scrape calendar.
3-column grid: score / TYPE (color-coded by CONNECTION/PRODUCT/etc.) /
title + body excerpt. Header links to the standalone viewer at :9920.
Both autonomous loops now have a single visual home — coverage.html
shows news pipeline analytics + creative idea pipeline side by side.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Files touched
M public/coverage.htmlM src/server/index.ts
Diff
commit dab658539e9a106137eb776040e4fce29292a711
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Thu May 7 22:23:05 2026 -0700
feat(coverage): idea-loop feed bridge on /coverage.html
Read-only bridge to the idea-loop skill so accepted ideas surface in
the corridor coverage page alongside the news analytics. Read from
~/.claude/skills/idea-loop/data/ideas.jsonl on demand, sorted by
score_total DESC, top 20.
Routes:
GET /api/ideas/recent {count, rows[]} from ideas.jsonl. 60s cache.
Graceful "idea-loop not yet running" if file
missing.
UI:
/coverage.html new "Idea-loop" section above News scrape calendar.
3-column grid: score / TYPE (color-coded by CONNECTION/PRODUCT/etc.) /
title + body excerpt. Header links to the standalone viewer at :9920.
Both autonomous loops now have a single visual home — coverage.html
shows news pipeline analytics + creative idea pipeline side by side.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
public/coverage.html | 32 ++++++++++++++++++++++++++++++++
src/server/index.ts | 23 +++++++++++++++++++++++
2 files changed, 55 insertions(+)
diff --git a/public/coverage.html b/public/coverage.html
index 777a006..93e7537 100644
--- a/public/coverage.html
+++ b/public/coverage.html
@@ -84,6 +84,12 @@ main{padding:32px;max-width:1200px;margin:0 auto}
<div id="city"></div>
</div>
+<div class="bar-section">
+ <h3>Idea-loop · accepted ideas (top by score)</h3>
+ <p style="font-family:'Cormorant Garamond',serif;font-style:italic;font-size:13px;color:var(--ink-mute);margin:0 0 14px">Surviving ideas from the autonomous idea-loop debate panel (qwen3 + gemma3 + deepseek-r1). Full viewer at <a href="http://127.0.0.1:9920/" target="_blank" rel="noopener" style="color:var(--metal)">:9920</a>.</p>
+ <div id="idea-feed"></div>
+</div>
+
<div class="bar-section">
<h3>News scrape calendar · last 90 days</h3>
<p style="font-family:'Cormorant Garamond',serif;font-style:italic;font-size:13px;color:var(--ink-mute);margin:0 0 14px">Daily count of articles landed by the news scraper. Quiet stretches mean the nightly 03:15 scrape didn't find new posts; bright days mean fresh content.</p>
@@ -154,6 +160,32 @@ async function load() {
renderBars(d.by_vertical, document.getElementById('vert'));
renderBars(d.by_city, document.getElementById('city'));
+ // Idea-loop feed
+ try {
+ const ideas = await fetch('/api/ideas/recent').then(r => r.json());
+ const elI = document.getElementById('idea-feed');
+ const rows = ideas.rows || [];
+ if (!rows.length) {
+ elI.innerHTML = '<p style="color:var(--ink-mute);font-style:italic;font-family:Cormorant Garamond,serif">No accepted ideas yet — idea-loop will land them as they pass debate panel ≥28/40.</p>';
+ } else {
+ elI.innerHTML = rows.map(r => {
+ const score = r.score_total || 0;
+ const type = (r.type || 'idea').toUpperCase();
+ const tColor = { CONNECTION: 'var(--metal-glow)', PRODUCT: '#7ab8a3', AUTOMATION: '#7b9bc4', RENAME: '#c4a07b', KILL: '#c47b7b' }[type] || 'var(--metal)';
+ return `<div style="padding:10px 0;border-bottom:1px dotted var(--rule);display:grid;grid-template-columns:46px 80px 1fr;gap:14px;align-items:start">
+ <span style="font-family:var(--mono);font-size:14px;color:var(--metal-glow)">${score}</span>
+ <span style="font-family:var(--mono);font-size:9px;letter-spacing:.2em;color:${tColor};padding-top:4px">${escHtml(type)}</span>
+ <div>
+ <div style="font-family:var(--serif);font-size:15px;font-style:italic">${escHtml(r.title || '(untitled)')}</div>
+ ${r.body ? `<div style="font-size:11px;color:var(--ink-mute);margin-top:4px;line-height:1.5">${escHtml(String(r.body).slice(0,260))}${String(r.body).length>260?'…':''}</div>` : ''}
+ </div>
+ </div>`;
+ }).join('');
+ }
+ } catch(e) {
+ document.getElementById('idea-feed').innerHTML = '<p style="color:#c47b7b">idea-loop bridge unavailable</p>';
+ }
+
// 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 53cdf3e..070c535 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -5126,6 +5126,29 @@ app.get('/api/news/archive', async (_req, res) => {
}
});
+// Bridge to the idea-loop skill at ~/.claude/skills/idea-loop/data/ideas.jsonl.
+// Reads the JSONL on demand (not large, ~kb scale), returns last N parsed
+// entries. Pure read; idea-loop owns its own writes.
+app.get('/api/ideas/recent', async (_req, res) => {
+ try {
+ const fs = await import('node:fs/promises');
+ const path = await import('node:path');
+ const home = process.env.HOME || '/Users/stevestudio2';
+ const jsonlPath = path.join(home, '.claude', 'skills', 'idea-loop', 'data', 'ideas.jsonl');
+ let raw = '';
+ try { raw = await fs.readFile(jsonlPath, 'utf8'); }
+ catch { return res.json({ count: 0, rows: [], note: 'idea-loop not yet running' }); }
+ const ideas = raw.split(/\r?\n/).filter(Boolean).map((line) => {
+ try { return JSON.parse(line); } catch { return null; }
+ }).filter(Boolean);
+ ideas.sort((a, b) => (b.score_total ?? 0) - (a.score_total ?? 0));
+ res.set('Cache-Control', 'public, max-age=60');
+ res.json({ count: ideas.length, rows: ideas.slice(0, 20) });
+ } catch (e: any) {
+ res.status(500).json({ error: e.message });
+ }
+});
+
// 90-day daily news-fetch heatmap. /coverage.html renders it as a GitHub-
// style grid so editors see "is the scraper actually landing fresh stuff?"
app.get('/api/news/by-day', async (_req, res) => {
← f9ee539 feat(news): /api/news/by-day + 90-day calendar heatmap on /c
·
back to Ventura Corridor
·
feat(news): news counts in homepage build-status strip b8a21eb →