[object Object]

← back to Rentv 2026

feat(closings-admin): sibling-drift fix — mirror the created date+time chip to closings.html (/api/closings joins registry first_seen; corpus rows use article date, date-only, no fabricated time). 100% card coverage

6033166b983a9280268b93d5da43180895716aab · 2026-08-06 08:29:40 -0700 · Steve

Files touched

Diff

commit 6033166b983a9280268b93d5da43180895716aab
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Aug 6 08:29:40 2026 -0700

    feat(closings-admin): sibling-drift fix — mirror the created date+time chip to closings.html (/api/closings joins registry first_seen; corpus rows use article date, date-only, no fabricated time). 100% card coverage
---
 public/closings.html | 9 +++++++++
 server.js            | 9 +++++++++
 2 files changed, 18 insertions(+)

diff --git a/public/closings.html b/public/closings.html
index 1388eb06..fc6a9f97 100644
--- a/public/closings.html
+++ b/public/closings.html
@@ -61,6 +61,7 @@
   .card h3{font-size:13px;color:var(--sub);font-weight:600;line-height:1.35}
   .card .meta{font-size:12px;color:var(--sub);font-weight:600;display:flex;flex-wrap:wrap;gap:4px 10px}
   .card .meta b{color:var(--ink)}
+  .card .when{font-size:11px;color:var(--sub);font-weight:600;letter-spacing:.02em}
   .card .meta.parties{font-size:11px;gap:3px 8px}
   .card .meta.parties span{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block}
   .card .meta.parties .party{cursor:pointer;border-radius:4px}.card .meta.parties .party:hover{text-decoration:underline;color:var(--red)}
@@ -196,6 +197,13 @@ function dateOf(d){
   const m=String(d.image||'').match(/([A-Z][a-z]+?)(\d{1,2})(\d{4})\.(?:jpg|jpeg|png|webp)/);if(!m)return null;const mo=MONTHS[m[1].slice(0,3)];if(mo==null)return null;return new Date(Date.UTC(+m[3],mo,+m[2]));
 }
 const fmtDate=dt=>dt?dt.toLocaleDateString(undefined,{month:'short',day:'numeric',year:'numeric'}):'';
+// Admin-card rule: show the RECORD's created date+time. A full ISO ts (feed deals, registry first_seen)
+// renders date+time; a DATE-ONLY value (corpus article date) renders date-only, LOCAL-parsed so a
+// UTC-midnight string doesn't slip to the prior evening, and with no fabricated clock.
+const fmtDateTime=v=>{if(!v)return'';const s=String(v);
+  const md=s.match(/^(\d{4})-(\d{2})-(\d{2})$/);
+  if(md)return new Date(+md[1],+md[2]-1,+md[3]).toLocaleDateString(undefined,{month:'short',day:'numeric',year:'numeric'});
+  const dt=new Date(s);return isNaN(dt)?'':dt.toLocaleString(undefined,{month:'short',day:'numeric',year:'numeric',hour:'numeric',minute:'2-digit'});};
 const isManual=d=>d._manual||String(d.id||'').startsWith('mc');
 
 function pills(){
@@ -244,6 +252,7 @@ function render(){
         ${loc?`<div class="meta"><span class="drill" data-filt="q" data-val="${esc(loc)}" title="Filter by ${esc(loc)}">📍 <b>${esc(loc)}</b></span>${d.size_label?`<span>📐 ${esc(d.size_label)}</span>`:''}${d.year_built?`<span>🏗 ${esc(d.year_built)}</span>`:''}</div>`:''}
         ${(d.buyer||d.seller||d.broker)?`<div class="meta parties">${d.buyer?`<span class="party" data-party="${esc(d.buyer)}" title="Buyer — click to see this party's deals">🟢 <b>${esc(d.buyer)}</b></span>`:''}${d.seller?`<span class="party" data-party="${esc(d.seller)}" title="Seller — click to filter">🔴 ${esc(d.seller)}</span>`:''}${d.broker?`<span class="party" data-party="${esc(d.broker)}" title="Broker — click to filter">🤝 ${esc(d.broker)}</span>`:''}</div>`:''}
         ${(d.address&&d.title)?`<h3>${esc(d.title)}</h3>`:''}
+        ${d.created_at?`<div class="when" title="${esc(d.created_at)}">🕓 Added ${esc(fmtDateTime(d.created_at))}</div>`:''}
         <div class="cardfoot">
           ${story}
           <div class="acts">
diff --git a/server.js b/server.js
index 963254ac..51185606 100644
--- a/server.js
+++ b/server.js
@@ -416,6 +416,9 @@ function corpusClosings() {
         amount: a.amount || null, amount_label: (amt > 0 && amt < 1_000_000) ? '' : (a.amount_label || ''),
         size_label: a.size_label || '', units: a.units || null, sqft: a.sqft || null,
         address: null, close_date: a.date || '', year_built: null,
+        // article date as the record's captured-on proxy (date-only; the chip renders it without a
+        // fabricated clock) — mirrors corpusDeals so closings admin cards satisfy the created-date rule.
+        created_at: a.date || null,
         buyer: a.buyer || '', seller: a.seller || '', broker: a.broker || '', summary: a.summary || '',
       };
     });
@@ -436,6 +439,10 @@ app.get('/api/closings', adminOnly, (req, r) => {
   const corpusById = new Map(allCorpus.map((a) => [String(a.id), a]));
   const corpusExtra = allCorpus.filter((a) => !feedIds.has(String(a.id)));
   const base = [...feedSales, ...corpusExtra];
+  // Registry first_seen = when RENTV first captured a feed deal → created_at, so the closings admin
+  // grid shows the record's created date+time (Steve's admin-card rule); corpus rows fall back to the
+  // article date set in corpusClosings above.
+  const regDeals = readJSON('deals-registry.json', { deals: {} }).deals || {};
   // The deals-pull feed carries images/price but NOT the LLM-extracted party fields — those live
   // only in the corpus. GAP-FILL each feed row from its matching corpus article (never overwrite
   // a field the feed already has) so buyer/seller/broker actually surface on feed closings.
@@ -448,6 +455,8 @@ app.get('/api/closings', adminOnly, (req, r) => {
     const row = e ? { ...d2, ...e, _edited: true } : { ...d2 };
     row._hidden = hides.has(String(d.id));
     row._corpus = feedIds.has(String(d.id)) ? undefined : true;
+    const reg = regDeals[String(d.id)];
+    row.created_at = row.created_at || (reg && reg.first_seen) || null; // when RENTV first captured it
     return row;
   }).filter((d) => includeHidden || !d._hidden);
   const adds = (ov.adds || []).map((a) => ({ ...a, _manual: true }));

← 5de160cc harden(deals-admin): Cody gate — every card now shows a crea  ·  back to Rentv 2026  ·  Refresh RENTV wordmark → 2026 liquid-glass logo from rentv.c 65458b63 →