[object Object]

โ† back to Ventura Corridor

iter 74: bulk-generate 50 features now + nightly magazine launchd โ€” kicked off background nohup npm run magazine:gen 50 (PID 58664, ~25min for 50 features at ~25-31s/each on Mac1 Ollama qwen3:14b); com.steve.ventura-corridor-magazine-gen launchd loaded fires daily 1:32am for 20 fresh features (so the magazine grows organically overnight); /api/magazine/stats endpoint returns total/draft/reviewed/published/spiked/gen_24h + corridor_total for coverage %; /today.html grows a ๐Ÿ“– 'The Boulevard ยท corridor magazine' progress card with stat strip + horizontal coverage bar (currently 6/17,462 = 0.03%) auto-refreshing every 30s; ๐Ÿ“– magazine link added to /today.html nav with metal-glow color

78b1bea840db0231044f18a01c1606fcce70e250 ยท 2026-05-06 16:05:49 -0700 ยท SteveStudio2

Files touched

Diff

commit 78b1bea840db0231044f18a01c1606fcce70e250
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date:   Wed May 6 16:05:49 2026 -0700

    iter 74: bulk-generate 50 features now + nightly magazine launchd โ€” kicked off background nohup npm run magazine:gen 50 (PID 58664, ~25min for 50 features at ~25-31s/each on Mac1 Ollama qwen3:14b); com.steve.ventura-corridor-magazine-gen launchd loaded fires daily 1:32am for 20 fresh features (so the magazine grows organically overnight); /api/magazine/stats endpoint returns total/draft/reviewed/published/spiked/gen_24h + corridor_total for coverage %; /today.html grows a ๐Ÿ“– 'The Boulevard ยท corridor magazine' progress card with stat strip + horizontal coverage bar (currently 6/17,462 = 0.03%) auto-refreshing every 30s; ๐Ÿ“– magazine link added to /today.html nav with metal-glow color
---
 public/today.html   | 31 +++++++++++++++++++++++++++++++
 src/server/index.ts | 20 ++++++++++++++++++++
 2 files changed, 51 insertions(+)

diff --git a/public/today.html b/public/today.html
index 2722e59..8ce1a26 100644
--- a/public/today.html
+++ b/public/today.html
@@ -117,6 +117,7 @@
     <a href="/responses.html">responses</a>
     <a href="/postcards.html">postcards</a>
     <a href="/buildings.html">buildings</a>
+    <a href="/magazine.html" style="color:var(--metal-glow)">๐Ÿ“– magazine</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>
@@ -143,6 +144,17 @@
   <div id="prio-strip" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:8px"></div>
 </section>
 
+<section id="magazine-section" style="padding:20px 32px;border-bottom:1px solid var(--rule);background:rgba(184,153,104,0.04)">
+  <div style="display:flex;justify-content:space-between;align-items:baseline;flex-wrap:wrap;gap:10px">
+    <h3 style="font-family:var(--serif);font-style:italic;font-weight:400;font-size:22px;color:var(--metal-glow);margin:0">๐Ÿ“– The Boulevard <span style="font-size:11px;letter-spacing:.18em;color:var(--ink-mute);margin-left:8px">corridor magazine</span></h3>
+    <a href="/magazine.html" style="color:var(--metal);text-decoration:none;font-size:10px;letter-spacing:.18em;text-transform:uppercase;border:1px solid var(--rule);padding:4px 10px">Open issue โ†—</a>
+  </div>
+  <div id="magazine-stats" style="margin-top:10px;font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--ink-mute)">โ€”</div>
+  <div id="magazine-progress" style="margin-top:8px;height:6px;background:var(--noir);border:1px solid var(--rule);overflow:hidden">
+    <div id="magazine-bar" style="height:100%;background:var(--metal-glow);width:0%;transition:width 0.6s"></div>
+  </div>
+</section>
+
 <section id="stale-section" style="display:none;padding:20px 32px;border-bottom:1px solid var(--rule);background:rgba(182,101,101,0.05)">
   <div style="display:flex;justify-content:space-between;align-items:baseline;margin-bottom:12px;flex-wrap:wrap;gap:10px">
     <h3 style="font-family:var(--serif);font-style:italic;font-weight:400;font-size:22px;color:#c47b7b;margin:0">Cold leads ยท re-touch or close</h3>
@@ -568,6 +580,25 @@ async function loadStale() {
 }
 loadStale();
 setInterval(loadStale, 5 * 60_000);
+
+// โ”€โ”€โ”€ Magazine progress (iter 74) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
+async function loadMagazineStats() {
+  try {
+    const d = await fetch('/api/magazine/stats').then(r => r.json());
+    const total = Number(d.total || 0);
+    const corr = Number(d.corridor_total || 1);
+    const pct = Math.min(100, (100 * total / corr));
+    document.getElementById('magazine-stats').innerHTML =
+      `<b style="color:var(--metal-glow);font-family:var(--mono);font-size:13px">${total.toLocaleString()}</b> features written ยท ` +
+      `<b style="color:var(--metal);font-family:var(--mono)">${d.draft || 0}</b> drafts ยท ` +
+      `<b style="color:var(--green,#6a9b73);font-family:var(--mono)">${d.published || 0}</b> published ยท ` +
+      `<b style="color:var(--metal);font-family:var(--mono)">${d.gen_24h || 0}</b> generated last 24h ยท ` +
+      `coverage <b style="color:var(--metal-glow);font-family:var(--mono)">${pct.toFixed(1)}%</b> of ${corr.toLocaleString()} businesses`;
+    document.getElementById('magazine-bar').style.width = pct + '%';
+  } catch {}
+}
+loadMagazineStats();
+setInterval(loadMagazineStats, 30_000);
 </script>
 </body>
 </html>
diff --git a/src/server/index.ts b/src/server/index.ts
index 851ad0c..5dc8f61 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1742,6 +1742,26 @@ app.get('/api/data-freshness', async (_req, res) => {
   }
 });
 
+app.get('/api/magazine/stats', async (_req, res) => {
+  try {
+    const r = await query(`
+      SELECT
+        count(*)                                            AS total,
+        count(*) FILTER (WHERE status = 'draft')            AS draft,
+        count(*) FILTER (WHERE status = 'reviewed')         AS reviewed,
+        count(*) FILTER (WHERE status = 'published')        AS published,
+        count(*) FILTER (WHERE status = 'spiked')           AS spiked,
+        count(*) FILTER (WHERE generated_at >= NOW() - INTERVAL '24 hours') AS gen_24h,
+        max(generated_at)                                   AS last_generated_at
+      FROM magazine_features
+    `);
+    const corr = await query(`SELECT count(*) AS n FROM businesses WHERE on_corridor AND merged_into IS NULL`);
+    res.json({ ...r.rows[0], corridor_total: Number(corr.rows[0].n) });
+  } catch (e: any) {
+    res.status(500).json({ error: e.message });
+  }
+});
+
 // โ”€โ”€โ”€ Magazine: AI-generated features for corridor businesses โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
 app.get('/api/magazine', async (req, res) => {
   try {

โ† 1daefa6 iter 73: PIVOT to corridor magazine MVP โ€” migration 016 adds  ยท  back to Ventura Corridor  ยท  iter 75: feature regen-via-API endpoint โ€” POST /api/magazine e31d8bb โ†’