← back to Ventura Corridor
iter 163: /api/magazine/draft-pick-of-the-night + 'Publish next' rail on today.html (heuristic-scored)
280b67995545dd52f7ee49584335373046c32cf0 · 2026-05-06 19:45:36 -0700 · SteveStudio2
Files touched
M public/today.htmlM src/server/index.ts
Diff
commit 280b67995545dd52f7ee49584335373046c32cf0
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Wed May 6 19:45:36 2026 -0700
iter 163: /api/magazine/draft-pick-of-the-night + 'Publish next' rail on today.html (heuristic-scored)
---
public/today.html | 22 ++++++++++++++++++++++
src/server/index.ts | 36 ++++++++++++++++++++++++++++++++++++
2 files changed, 58 insertions(+)
diff --git a/public/today.html b/public/today.html
index 390daa1..e733c4d 100644
--- a/public/today.html
+++ b/public/today.html
@@ -143,6 +143,12 @@
<span id="gen-queue-strip" style="font-family:var(--mono);font-size:11px;color:var(--ink-mute)">…</span>
</section>
+<section style="padding:14px 32px;border-bottom:1px solid var(--rule);background:rgba(212,182,131,0.05);display:none" id="draft-pick-rail">
+ <span style="font-family:var(--serif);font-style:italic;color:var(--metal-glow);font-size:14px;margin-right:14px">Publish next:</span>
+ <a id="draft-pick-link" href="#" style="font-family:var(--serif);font-style:italic;color:var(--ink);font-size:15px;text-decoration:none">…</a>
+ <span id="draft-pick-meta" style="font-family:var(--mono);font-size:10px;color:var(--ink-mute);margin-left:14px">…</span>
+</section>
+
<section class="day-hero">
<div class="date" id="today-date">—</div>
<h2>Three things, <em>in order</em>.</h2>
@@ -777,6 +783,22 @@ async function loadGenQueue() {
loadGenQueue();
setInterval(loadGenQueue, 5 * 60_000);
+async function loadDraftPick() {
+ try {
+ const d = await fetch('/api/magazine/draft-pick-of-the-night').then(r => r.json());
+ const rail = document.getElementById('draft-pick-rail');
+ if (!d.pick) { rail.style.display = 'none'; return; }
+ rail.style.display = 'block';
+ const link = document.getElementById('draft-pick-link');
+ link.textContent = d.pick.headline || d.pick.biz_name;
+ link.href = '/magazine/' + d.pick.id;
+ const meta = document.getElementById('draft-pick-meta');
+ meta.textContent = `${d.pick.biz_name} · ${d.pick.editorial_len}c · ${d.pick.category_tag || '—'} · score ${d.pick.score}`;
+ } catch {}
+}
+loadDraftPick();
+setInterval(loadDraftPick, 5 * 60_000);
+
async function updateFeedbackPill() {
try {
const d = await fetch('/api/feedback').then(r => r.json());
diff --git a/src/server/index.ts b/src/server/index.ts
index 288b08a..f4ee494 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1932,6 +1932,42 @@ app.get('/api/magazine/random', async (req, res) => {
}
});
+// Draft-pick-of-the-night — strongest unpublished draft from the last 24h,
+// scored by editorial length × pull-quote-presence × subhead × ad-signal × random tiebreak.
+// Used to seed the "Publish next" pill on /today.html and the morning email.
+app.get('/api/magazine/draft-pick-of-the-night', async (_req, res) => {
+ try {
+ const r = await query(`
+ SELECT mf.id, mf.headline, mf.subhead, mf.editorial, mf.pull_quote, mf.category_tag,
+ mf.generated_at, length(mf.editorial) AS editorial_len,
+ b.name AS biz_name, b.address AS biz_address, b.city,
+ COALESCE((be.ad_signals->>'paid_ads_count')::int, 0) AS paid_ads_count,
+ (length(mf.editorial) +
+ CASE WHEN mf.pull_quote IS NOT NULL THEN 100 ELSE 0 END +
+ CASE WHEN mf.subhead IS NOT NULL THEN 50 ELSE 0 END +
+ COALESCE((be.ad_signals->>'paid_ads_count')::int, 0) * 30) AS score
+ FROM magazine_features mf
+ JOIN businesses b ON b.id = mf.business_id
+ LEFT JOIN business_enrichment be ON be.business_id = mf.business_id
+ WHERE mf.status = 'draft'
+ AND mf.generated_at > NOW() - INTERVAL '24 hours'
+ AND length(mf.editorial) > 240
+ ORDER BY score DESC, mf.generated_at DESC
+ LIMIT 1
+ `);
+ if (r.rowCount === 0) return res.json({ pick: null });
+ const f = r.rows[0];
+ res.json({
+ pick: f,
+ permalink: `/magazine/${f.id}`,
+ ad_pack_url: `/magazine/${f.id}/ad-pack.html`,
+ review_url: `/magazine.html?status=draft#${f.id}`,
+ });
+ } catch (e: any) {
+ res.status(500).json({ error: e.message });
+ }
+});
+
// Quality-flag audit — surfaces features with fixable issues for the curation queue.
// Each flag is independent (a feature can have multiple). Used by /magazine.html
// for a "🔧 quality" chip + by the standup script to count cleanup opportunities.
← fcadf64 iter 162: /api/magazine/:id/social-pack.json — programmatic
·
back to Ventura Corridor
·
iter 164: morning_feature_email now includes 'Suggested next 48b3342 →