[object Object]

← back to Ventura Corridor

iter 118+119: /magazine.html admin meta strip + /buildings/:bldg/issue per-building mini-issue — /api/magazine SELECT now includes mf.taps + mf.taps_breakdown so cards can show outbound-click counts; /magazine.html cards add a JetBrains Mono meta strip showing ⏱ generated date + 👁 views (metal) + ↗ taps (metal-glow) + Nc editorial length + model name (visible only when values > 0); GET /buildings/:bldg/issue renders a centered Cormorant 54px italic landing page for one building w/ all its featured tenants (cat tag · suite · headline-link · subhead · biz meta) above an 'Also at this address · not yet featured' tail listing un-featured tenants w/ suite numbers; /magazine/:id reader's building-mates section now includes 'Building issue ↗' link

e2338c1e1f1098c80f56fee1190112137da5953b · 2026-05-06 18:07:10 -0700 · SteveStudio2

Files touched

Diff

commit e2338c1e1f1098c80f56fee1190112137da5953b
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date:   Wed May 6 18:07:10 2026 -0700

    iter 118+119: /magazine.html admin meta strip + /buildings/:bldg/issue per-building mini-issue — /api/magazine SELECT now includes mf.taps + mf.taps_breakdown so cards can show outbound-click counts; /magazine.html cards add a JetBrains Mono meta strip showing ⏱ generated date + 👁 views (metal) + ↗ taps (metal-glow) + Nc editorial length + model name (visible only when values > 0); GET /buildings/:bldg/issue renders a centered Cormorant 54px italic landing page for one building w/ all its featured tenants (cat tag · suite · headline-link · subhead · biz meta) above an 'Also at this address · not yet featured' tail listing un-featured tenants w/ suite numbers; /magazine/:id reader's building-mates section now includes 'Building issue ↗' link
---
 public/magazine.html |  7 ++++
 src/server/index.ts  | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 96 insertions(+), 2 deletions(-)

diff --git a/public/magazine.html b/public/magazine.html
index ae9e25b..b1d599b 100644
--- a/public/magazine.html
+++ b/public/magazine.html
@@ -381,6 +381,13 @@ async function load() {
         <div class="subhead">${escHtml(r.subhead || '')}</div>
         <p class="lede">${escHtml(r.editorial || '')}</p>
         ${r.pull_quote ? `<div class="pull">"${escHtml(r.pull_quote)}"</div>` : ''}
+        <div style="display:flex;gap:14px;font-family:'JetBrains Mono',monospace;font-size:9px;letter-spacing:.1em;color:var(--ink-mute);margin-top:10px;border-top:1px dotted var(--rule);padding-top:8px">
+          <span title="Generated">⏱ ${new Date(r.generated_at).toLocaleDateString('en-US',{month:'short',day:'numeric'})}</span>
+          ${r.views > 0 ? `<span title="Reader-page views" style="color:var(--metal)">👁 ${r.views}</span>` : ''}
+          ${r.taps > 0 ? `<span title="Outbound taps (share/sponsor/etc)" style="color:var(--metal-glow)">↗ ${r.taps}</span>` : ''}
+          ${r.editorial ? `<span title="Editorial length">${r.editorial.length}c</span>` : ''}
+          <span style="color:var(--ink-mute)">${escHtml(r.model || '')}</span>
+        </div>
         <footer>
           <div>
             <div class="biz">${escHtml(r.name)}</div>
diff --git a/src/server/index.ts b/src/server/index.ts
index 7f075e8..80b09c1 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -90,6 +90,7 @@ const ADMIN_PATHS = [
   /^\/api\/crawl(\/.*)?$/i,
   /^\/postcards(\.html)?\/?$/i,
   /^\/buildings(\.html)?\/?$/i,
+  /^\/buildings\/[^/]+\/issue\/?$/i,
   /^\/api\/buildings(\.csv|\/.*)?$/i,
   /^\/pitch\/\d+\/?$/i,
 ];
@@ -2015,7 +2016,7 @@ app.get('/api/magazine', async (req, res) => {
     const r = await query(
       `SELECT mf.id, mf.business_id, mf.headline, mf.subhead, mf.editorial, mf.pull_quote,
               mf.category_tag, mf.photo_url, mf.status, mf.model, mf.generated_at,
-              mf.reviewed_at, mf.published_at, mf.views, mf.notes,
+              mf.reviewed_at, mf.published_at, mf.views, mf.taps, mf.taps_breakdown, mf.notes,
               b.name, b.address, b.city, b.zip,
               b.raw->>'primary_naics_description' AS naics,
               be.ad_signals,
@@ -2443,6 +2444,89 @@ app.post('/api/magazine/review-drafts', express.json(), async (req, res) => {
 
 // /sponsor/:id — owner-claim form prefilled with the business + feature. Submits
 // via POST /api/sponsor/inquiry which emails Steve via George Gmail.
+// /buildings/:bldg/issue — mini-issue showing every feature for one building.
+// Powers a future "this building's tenants" landing page; permalinkable.
+app.get('/buildings/:bldg/issue', async (req, res) => {
+  try {
+    const bldg = decodeURIComponent(req.params.bldg);
+    const r = await query(
+      `SELECT mf.id, mf.headline, mf.subhead, mf.editorial, mf.pull_quote, mf.category_tag,
+              b.name AS biz_name, bc.suite, b.city
+       FROM v_building_canonical bc
+       JOIN businesses b ON b.id = bc.id
+       LEFT JOIN magazine_features mf ON mf.business_id = bc.id
+       WHERE bc.bldg_address = $1
+       ORDER BY mf.id IS NULL ASC, bc.suite NULLS LAST, b.name`,
+      [bldg]
+    );
+    const featured = r.rows.filter((x: any) => x.id);
+    const unfeatured = r.rows.filter((x: any) => !x.id);
+    const esc = (s: any) => String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]!));
+    res.setHeader('Content-Type', 'text/html; charset=utf-8');
+    res.send(`<!doctype html><html><head><meta charset="utf-8">
+<title>${esc(bldg)} — 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,300;0,400;0,500;1,400;1,500&family=Inter:wght@300;400;500&display=swap');
+:root{--paper:#faf6ee;--ink:#1a1815;--ink-mute:#6e6356;--metal:#8a6d3b;--accent:#6a3a1a;--rule:#d8cdb8}
+*{box-sizing:border-box}html,body{margin:0;background:var(--paper);color:var(--ink);font-family:'Inter',sans-serif;font-weight:300;line-height:1.55}
+header{text-align:center;padding:48px 24px 28px;border-bottom:4px double var(--ink)}
+header .kicker{font-size:9px;letter-spacing:.4em;text-transform:uppercase;color:var(--metal);font-weight:500}
+header h1{font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:500;font-size:54px;margin:8px 0 0;color:var(--ink);letter-spacing:-0.02em}
+header h1 em{font-style:normal;color:var(--metal)}
+header .deck{font-family:'Cormorant Garamond',serif;font-style:italic;font-size:16px;color:var(--ink-mute);margin-top:8px}
+main{max-width:780px;margin:0 auto;padding:36px 24px 80px}
+.feature{margin-bottom:36px;padding-bottom:24px;border-bottom:1px solid var(--rule)}
+.feature:last-of-type{border-bottom:none}
+.feature .cat{display:inline-block;background:var(--ink);color:var(--paper);padding:3px 10px;font-size:9px;letter-spacing:.32em;text-transform:uppercase;font-weight:500;margin-bottom:8px}
+.feature .suite{font-size:9px;letter-spacing:.32em;text-transform:uppercase;color:var(--metal);margin-left:8px}
+.feature h2{font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:500;font-size:28px;line-height:1.05;margin:0 0 6px}
+.feature h2 a{color:var(--ink);text-decoration:none}
+.feature .subhead{font-family:'Cormorant Garamond',serif;font-style:italic;font-size:15px;color:var(--ink-mute);margin:0 0 14px}
+.feature .biz{font-size:11px;letter-spacing:.06em;color:var(--ink-mute);font-family:'JetBrains Mono',monospace}
+.unfeatured{margin-top:36px;padding:18px;background:rgba(184,153,104,0.06);border:1px solid var(--rule);font-size:13px}
+.unfeatured h3{font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:500;font-size:18px;color:var(--metal);margin:0 0 10px}
+.unfeatured ul{list-style:none;padding:0;margin:0}
+.unfeatured li{padding:5px 0;border-top:1px dotted var(--rule);display:flex;justify-content:space-between;font-size:13px;color:var(--ink-mute)}
+.unfeatured li:first-child{border-top:none}
+.unfeatured li b{color:var(--ink);font-weight:500}
+.toolbar{position:fixed;top:14px;right:14px;display:flex;gap:6px;z-index:100}
+.toolbar a{font-size:9px;letter-spacing:.22em;text-transform:uppercase;color:var(--ink-mute);background:var(--paper);padding:5px 10px;border:1px solid var(--rule);text-decoration:none}
+.toolbar a:hover{color:var(--metal);border-color:var(--metal)}
+</style></head><body>
+<div class="toolbar">
+  <a href="/issue">All issues</a>
+  <a href="/buildings.html?bldg=${encodeURIComponent(bldg)}">Roster</a>
+</div>
+<header>
+  <div class="kicker">The Corridor · in this building</div>
+  <h1>${esc(bldg)}</h1>
+  <div class="deck">${featured.length} feature${featured.length === 1 ? '' : 's'} · ${r.rowCount} tenant${r.rowCount === 1 ? '' : 's'} on file</div>
+</header>
+<main>
+${featured.length === 0 ? `<p style="text-align:center;font-style:italic;color:var(--ink-mute);padding:60px 0">No features yet for this building.</p>` :
+featured.map((f: any) => `
+  <article class="feature">
+    <div>
+      <span class="cat">${esc(f.category_tag || 'feature')}</span>
+      ${f.suite ? `<span class="suite">Suite ${esc(f.suite)}</span>` : ''}
+    </div>
+    <h2><a href="/magazine/${f.id}">${esc(f.headline || f.biz_name)}</a></h2>
+    <div class="subhead">${esc(f.subhead || '')}</div>
+    <div class="biz">${esc(f.biz_name)}${f.suite ? ' · #' + esc(f.suite) : ''} · ${esc(f.city || '')}</div>
+  </article>
+`).join('')}
+${unfeatured.length > 0 ? `<div class="unfeatured">
+  <h3>Also at this address · not yet featured</h3>
+  <ul>${unfeatured.map((u: any) => `<li><b>${esc(u.biz_name)}</b><span>${u.suite ? '#' + esc(u.suite) : ''}</span></li>`).join('')}</ul>
+</div>` : ''}
+</main>
+</body></html>`);
+  } catch (e: any) {
+    res.status(500).send('error: ' + e.message);
+  }
+});
+
 app.get('/sponsor/:id', async (req, res) => {
   const id = parseInt(req.params.id, 10);
   if (!Number.isFinite(id)) return res.status(400).send('bad id');
@@ -2903,7 +2987,10 @@ footer{margin-top:48px;padding:24px 0;text-align:center;font-size:10px;letter-sp
     <div style="margin-top:8px;font-size:10px;color:var(--ink-mute);font-style:italic">From the DW outreach pipeline that produces the magazine — every walk, every reply, every status change.</div>
   </details>` : ''}
   ${mates.rows.length > 0 ? `<section style="margin-top:48px;padding-top:24px;border-top:1px solid var(--rule)">
-    <div style="font-size:9px;letter-spacing:.32em;text-transform:uppercase;color:var(--ink-mute);margin-bottom:14px">In the same building · ${esc(bldg)}</div>
+    <div style="display:flex;justify-content:space-between;align-items:baseline;font-size:9px;letter-spacing:.32em;text-transform:uppercase;color:var(--ink-mute);margin-bottom:14px">
+      <span>In the same building · ${esc(bldg)}</span>
+      <a href="/buildings/${encodeURIComponent(bldg)}/issue" style="color:var(--metal);text-decoration:none">Building issue ↗</a>
+    </div>
     <div style="display:flex;flex-direction:column;gap:6px">
       ${mates.rows.map((m: any) => m.id ? `<a href="/magazine/${m.id}" style="color:var(--ink);text-decoration:none;display:flex;justify-content:space-between;align-items:baseline;padding:6px 0;border-bottom:1px dotted var(--rule)">
         <span style="font-family:'Cormorant Garamond',serif;font-style:italic;font-size:16px">${esc(m.headline || m.biz_name)}</span>

← cfd5c5a iter 115+116+117: og/twitter/JSON-LD meta + /api/magazine.js  ·  back to Ventura Corridor  ·  iter 120: public /find — reader-facing finder by suite/stree 93a2f1a →