[object Object]

← back to New Engine

New Arrivals engine for new.engine.designerwallcoverings.com — corvette-9797 pattern, live newest-first pull from dw_unified (port 9840)

f73dcfdb0d3e2726863086c57e97aa0c6ef87579 · 2026-07-28 09:31:27 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit f73dcfdb0d3e2726863086c57e97aa0c6ef87579
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jul 28 09:31:27 2026 -0700

    New Arrivals engine for new.engine.designerwallcoverings.com — corvette-9797 pattern, live newest-first pull from dw_unified (port 9840)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 .deploy.conf        |   6 +++
 .gitignore          |   9 +++++
 ecosystem.config.js |  14 +++++++
 public/index.html   | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 server.js           | 101 +++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 241 insertions(+)

diff --git a/.deploy.conf b/.deploy.conf
new file mode 100644
index 0000000..7a85691
--- /dev/null
+++ b/.deploy.conf
@@ -0,0 +1,6 @@
+# Deploy config for the New Arrivals engine → new.engine.designerwallcoverings.com
+# Consumed by ~/Projects/_shared/scripts/deploy.sh (the /deploy skill).
+# NOTE: live deploy + DNS for new.engine.designerwallcoverings.com are Steve-gated.
+PROJECT_NAME=new-engine
+DEPLOY_PATH=/root/Projects/new-engine
+HEALTH_URL=http://127.0.0.1:9840/healthz
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4ff7d89
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
+public/data.json
diff --git a/ecosystem.config.js b/ecosystem.config.js
new file mode 100644
index 0000000..51dc16e
--- /dev/null
+++ b/ecosystem.config.js
@@ -0,0 +1,14 @@
+// pm2 process definition for the New Arrivals engine (new.engine.designerwallcoverings.com).
+// Reboot-safe + version-controlled: `pm2 start ecosystem.config.js && pm2 save`.
+// server.js already defaults PORT→9840 and PGHOST→/tmp; pinned here so the process
+// definition is explicit and lives in git, not just pm2's dump.
+module.exports = {
+  apps: [{
+    name: 'new-engine',
+    script: 'server.js',
+    cwd: __dirname,
+    env: { PORT: 9840, PGHOST: '/tmp', PGDATABASE: 'dw_unified' },
+    autorestart: true,
+    max_restarts: 20,
+  }],
+};
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..3854a3b
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,111 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>New Arrivals · Designer Wallcoverings</title>
+<link rel="preconnect" href="https://fonts.googleapis.com">
+<link href="https://fonts.googleapis.com/css2?family=Cormorant+Garamond:ital,wght@0,400;0,600;1,400&family=Jost:wght@300;400;500&family=Share+Tech+Mono&display=swap" rel="stylesheet">
+<style>
+:root{--gold:#c8a96a;--gold-lit:#e6c98f;--ink:#e8e6e0;--muted:#8f8c84;--green:#5fbf6a}
+*{box-sizing:border-box;margin:0;padding:0}
+body{font-family:'Jost',sans-serif;color:var(--ink);
+  background:radial-gradient(120% 80% at 50% -10%,#22201d,#131211 45%,#070605);min-height:100vh;padding:22px 14px 60px;-webkit-font-smoothing:antialiased}
+.wrap{max-width:1080px;margin:0 auto}
+header{display:flex;align-items:center;justify-content:space-between;gap:14px;
+  background:linear-gradient(180deg,#1b1a17,#0d0c0b);border:1px solid #2b2925;border-radius:16px;
+  padding:18px 24px;box-shadow:inset 0 2px 0 rgba(255,255,255,.04),0 20px 50px rgba(0,0,0,.7);position:sticky;top:10px;z-index:5}
+.brand{display:flex;align-items:center;gap:12px}
+.brand .h1{font-family:'Cormorant Garamond',serif;font-size:26px;font-weight:600;letter-spacing:.5px;
+  background:linear-gradient(180deg,#f4efe4 0%,#c8a96a 55%,#8a6f3e);-webkit-background-clip:text;background-clip:text;color:var(--gold-lit)}
+.brand .sub-h{font-size:9px;letter-spacing:5px;color:var(--muted);text-transform:uppercase}
+.live{display:flex;align-items:center;gap:7px;font-family:'Share Tech Mono',monospace;font-size:11px;letter-spacing:2px;color:var(--green);margin-top:4px}
+.dot{width:8px;height:8px;border-radius:50%;background:var(--green);box-shadow:0 0 8px var(--green);animation:pulse 1.6s infinite}
+@keyframes pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(.7)}}
+.stats{text-align:right;font-family:'Share Tech Mono',monospace}
+.stats .big{font-size:26px;color:var(--gold-lit);text-shadow:0 0 12px rgba(200,169,106,.4);line-height:1}
+.stats .lbl{font-size:9px;letter-spacing:3px;color:var(--muted)}
+.subline{text-align:center;font-size:10px;letter-spacing:4px;color:#7d7a72;margin:14px 0 16px}
+.list{display:flex;flex-direction:column;gap:9px}
+.row{display:grid;grid-template-columns:30px 56px 1fr 200px;gap:13px;align-items:center;
+  background:linear-gradient(180deg,#191815,#100f0e);border:1px solid #2b2925;border-radius:12px;
+  padding:11px 14px;box-shadow:0 5px 14px rgba(0,0,0,.45);transition:box-shadow .15s}
+.thumb{width:56px;height:56px;border-radius:8px;object-fit:cover;background:#201f1c;border:1px solid #34322d;box-shadow:0 2px 6px rgba(0,0,0,.5)}
+.row:hover{box-shadow:0 8px 20px rgba(0,0,0,.7),0 0 0 1px rgba(200,169,106,.3)}
+.row.fresh{animation:land .8s ease}
+@keyframes land{0%{opacity:0;transform:translateY(-12px);box-shadow:0 0 0 2px var(--gold)}100%{opacity:1;transform:none}}
+.idx{font-family:'Share Tech Mono',monospace;font-size:15px;color:#5f5c54;text-align:center}
+.idx.top{color:var(--gold-lit);text-shadow:0 0 8px rgba(200,169,106,.6)}
+.main .t{font-size:16px;font-weight:400;line-height:1.15;font-family:'Cormorant Garamond',serif}
+.main .t a{color:var(--ink);text-decoration:none;border-bottom:1px dotted #4a4842}
+.main .t a:hover{color:var(--gold-lit);border-color:var(--gold)}
+.main .meta{font-size:10px;letter-spacing:1px;color:var(--muted);margin:3px 0 7px;text-transform:uppercase}
+.chips{display:flex;flex-wrap:wrap;gap:4px}
+.chip{font-size:9px;letter-spacing:.5px;padding:2px 8px;border-radius:20px;background:#242320;border:1px solid #37352f;color:#c3bfb4}
+.right{display:flex;flex-direction:column;gap:5px;align-items:flex-end}
+.pal{display:flex;width:100%;height:30px;border-radius:6px;overflow:hidden;border:1px solid #000;box-shadow:inset 0 0 0 1px rgba(255,255,255,.05)}
+.pal i{position:relative;display:flex;align-items:flex-end;justify-content:center}
+.pal i span{font-family:'Share Tech Mono',monospace;font-size:7px;color:rgba(255,255,255,.9);text-shadow:0 1px 2px #000;opacity:0;padding-bottom:1px}
+.pal:hover i span{opacity:1}
+.gidl{font-family:'Share Tech Mono',monospace;font-size:9px;color:#5f5c54;letter-spacing:1px}
+.hint{text-align:center;color:#5f5c54;font-size:11px;margin-top:20px;letter-spacing:2px}
+a.open{font-family:'Share Tech Mono',monospace;font-size:9px;color:var(--gold-lit);text-decoration:none;letter-spacing:1px}
+a.open:hover{text-shadow:0 0 8px rgba(200,169,106,.6)}
+</style>
+</head>
+<body>
+<div class="wrap">
+  <header>
+    <div class="brand">
+      <span style="font-size:22px">✦</span>
+      <div><div class="h1">New Arrivals</div>
+        <div class="sub-h">Designer Wallcoverings</div>
+        <div class="live"><span class="dot"></span><span id="clock">— live catalog feed —</span></div></div>
+    </div>
+    <div class="stats"><div class="big" id="total">0</div><div class="lbl">ACTIVE SKUS</div></div>
+  </header>
+  <div class="subline">N E W E S T &nbsp; P R O D U C T S &nbsp; · &nbsp; N E W E S T &nbsp; F I R S T &nbsp; · &nbsp; A U T O - R E F R E S H</div>
+  <div class="list" id="list"></div>
+  <div class="hint" id="hint">connecting…</div>
+</div>
+<script>
+let seen=new Set(), first=true;
+function esc(s){return (s||'').replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]));}
+// route product images through the same-origin proxy so a dead CDN asset degrades to a
+// transparent placeholder instead of a browser 404
+function proxied(url){ return url ? '/img?u='+encodeURIComponent(url) : ''; }
+async function tick(){
+  try{
+    const r=await fetch('/api/new-items?limit=60',{cache:'no-store'});
+    const d=await r.json();
+    document.getElementById('total').textContent=(d.total||0).toLocaleString();
+    document.getElementById('clock').textContent='updated '+new Date().toLocaleTimeString();
+    document.getElementById('hint').textContent=d.records.length?'▲ newest at top · live feed':'waiting for items to land…';
+    const list=document.getElementById('list'); list.innerHTML='';
+    d.records.forEach((p,i)=>{
+      const isNew=!seen.has(p.gid)&&!first;
+      const pal=(p.palette&&p.palette.length)?p.palette.map(c=>`<i style="flex:${c.pct||1};background:${esc(c.hex)}"><span>${esc(c.hex)}</span></i>`).join(''):'<i style="flex:1;background:#201f1c"></i>';
+      const href=p.handle?`https://designer-laboratory-sandbox.myshopify.com/products/${encodeURIComponent(p.handle)}`:null;
+      const title=href?`<a href="${href}" target="_blank" rel="noopener noreferrer">${esc(p.title)}</a>`:esc(p.title);
+      const chips=(p.tags||[]).map(t=>`<span class="chip">${esc(t)}</span>`).join('');
+      const img=proxied(p.image);
+      const thumb=img?`<img class="thumb" src="${img}" loading="lazy" alt="">`:`<div class="thumb"></div>`;
+      const row=document.createElement('div');
+      row.className='row'+(isNew?' fresh':'');
+      row.innerHTML=`<div class="idx ${i<3?'top':''}">${i+1}</div>
+        ${href?`<a href="${href}" target="_blank" rel="noopener noreferrer">${thumb}</a>`:thumb}
+        <div class="main"><div class="t">${title}</div>
+          <div class="meta">${esc(p.vendor)||'—'} · ${esc(p.sku)||'no sku'} · ${(p.palette||[]).length} colors</div>
+          <div class="chips">${chips}</div></div>
+        <div class="right"><div class="pal">${pal}</div>
+          <div class="gidl">#${esc(p.gid)}</div>
+          ${href?`<a class="open" href="${href}" target="_blank" rel="noopener noreferrer">OPEN PAGE ▸</a>`:''}</div>`;
+      list.appendChild(row);
+    });
+    seen=new Set(d.records.map(p=>p.gid)); first=false;
+  }catch(e){document.getElementById('hint').textContent='feed error: '+e.message;}
+}
+tick(); setInterval(tick,5000);
+</script>
+</body>
+</html>
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..a44c143
--- /dev/null
+++ b/server.js
@@ -0,0 +1,101 @@
+// new.engine.designerwallcoverings.com — "New Arrivals" engine.
+// Modeled on the '67 Corvette Console (127.0.0.1:9797): a zero-dependency Node http
+// server + a LIVE data endpoint that pulls the newest products straight from the local
+// dw_unified mirror. We shell out to psql and let the DB shape the JSON via json_agg,
+// so no `pg` module is needed. Unlike the corvette build (which read the transient
+// TK-135 apply-job logs), "newest" here is driven by the catalog itself —
+// shopify_products.created_at_shopify desc — so it stays correct forever.
+const http = require('http'), https = require('https'), fs = require('fs'), path = require('path'), { execFile } = require('child_process');
+const PORT = process.env.PORT || 9840;
+const ROOT = path.join(__dirname, 'public');
+const PGDB = process.env.PGDATABASE || 'dw_unified';
+const PGHOST = process.env.PGHOST || '/tmp';
+const MIME = { '.html': 'text/html', '.json': 'application/json', '.css': 'text/css', '.js': 'text/javascript', '.png': 'image/png', '.jpg': 'image/jpeg', '.svg': 'image/svg+xml' };
+
+// Read-only live pull of the newest ACTIVE, imaged products. `limit` is clamped to an
+// integer before interpolation (the only dynamic value in the SQL) so there's no
+// injection surface. Returns { records:[…newest-first…], total } where total is the
+// TRUE count of eligible products so the header never lies about the catalog size.
+// color_enrichment is LEFT-joined — a product without a palette still shows.
+function fetchNewest(limit, cb) {
+  const n = Math.max(1, Math.min(parseInt(limit, 10) || 60, 200));
+  const where = `p.status = 'ACTIVE' and p.created_at_shopify is not null and p.image_url is not null`;
+  const sql = `select json_build_object(
+    'records', (select coalesce(json_agg(row_to_json(t)),'[]') from (
+        select regexp_replace(p.shopify_id,'.*/','') as gid, p.title, p.vendor, p.handle, p.image_url as image,
+               coalesce(nullif(p.dw_sku,''),nullif(p.mfr_sku,''),nullif(p.variant_sku,'')) as sku,
+               to_char(p.created_at_shopify,'YYYY-MM-DD"T"HH24:MI:SS') as created,
+               (select json_agg(json_build_object('hex',c->>'hex','pct',(c->>'percentage')::numeric))
+                  from jsonb_array_elements(e.colors) c) as palette,
+               (select json_agg(v) from (select distinct trim(x) v
+                  from unnest(string_to_array(p.tags,',')) x
+                  where trim(x) !~ '[:/.{}"]' and trim(x) !~ '^[0-9]' and length(trim(x)) between 2 and 22
+                  limit 8) s) as tags
+        from shopify_products p
+        left join shopify_color_enrichment e on e.shopify_id = p.shopify_id
+        where ${where}
+        order by p.created_at_shopify desc
+        limit ${n}) t),
+    'total', (select count(*) from shopify_products p where ${where})
+  )`;
+  execFile('psql', ['-h', PGHOST, '-d', PGDB, '-tAc', sql], { maxBuffer: 64 * 1024 * 1024 }, (err, stdout) => {
+    if (err) return cb(err);
+    const out = (stdout || '').trim();
+    if (!out) return cb(new Error('empty'));
+    cb(null, out);
+  });
+}
+
+http.createServer((req, res) => {
+  const u = new URL(req.url, 'http://x');
+  let p = decodeURIComponent(u.pathname);
+
+  if (p === '/healthz') { res.writeHead(200); return res.end('ok'); }
+
+  // Live newest feed. On any DB failure, fall back to the frozen snapshot (wrapped to
+  // match the {records,total} contract) so the front end always gets valid data.
+  if (p === '/api/new-items') {
+    fetchNewest(u.searchParams.get('limit'), (err, json) => {
+      if (!err && json) { res.writeHead(200, { 'Content-Type': 'application/json', 'X-Data-Source': 'live', 'Cache-Control': 'no-store' }); return res.end(json); }
+      fs.readFile(path.join(ROOT, 'data.json'), (e, buf) => {
+        if (e) { res.writeHead(502); return res.end('no live db and no snapshot'); }
+        // Guard the parse: a corrupt snapshot must not throw in this async callback and
+        // crash the process — that would turn the safety net into an outage.
+        let obj;
+        try { obj = JSON.parse(buf); } catch (pe) { res.writeHead(502); return res.end('snapshot unreadable: ' + pe.message); }
+        if (Array.isArray(obj)) obj = { records: obj, total: obj.length };
+        res.writeHead(200, { 'Content-Type': 'application/json', 'X-Data-Source': 'snapshot' });
+        res.end(JSON.stringify(obj));
+      });
+    });
+    return;
+  }
+
+  // ---- image proxy ----
+  // Fetch the product image server-side so a dead CDN asset degrades to a transparent
+  // placeholder (same-origin 200) instead of a browser-console 404. SSRF-guarded:
+  // only https shopify.com CDN hosts are fetched; anything else → placeholder.
+  if (p === '/img') {
+    const PLACEHOLDER = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=', 'base64');
+    const placeholder = () => { if (res.writableEnded) return; res.writeHead(200, { 'Content-Type': 'image/png', 'Cache-Control': 'no-store' }); res.end(PLACEHOLDER); };
+    let tu; try { tu = new URL(u.searchParams.get('u') || ''); } catch { return placeholder(); }
+    if (tu.protocol !== 'https:' || !/(^|\.)shopify\.com$/.test(tu.hostname)) return placeholder();
+    const ir = https.get(tu.href, (pres) => {
+      if (pres.statusCode !== 200) { pres.resume(); return placeholder(); }
+      res.writeHead(200, { 'Content-Type': pres.headers['content-type'] || 'image/jpeg', 'Cache-Control': 'public,max-age=3600' });
+      pres.pipe(res);
+    });
+    ir.on('error', placeholder);
+    ir.setTimeout(6000, () => { ir.destroy(); placeholder(); });
+    return;
+  }
+
+  if (p === '/') p = '/index.html';
+  const fp = path.join(ROOT, path.normalize(p));
+  if (!fp.startsWith(ROOT)) { res.writeHead(403); return res.end('forbidden'); }
+  fs.readFile(fp, (e, buf) => {
+    if (e) { res.writeHead(404); return res.end('not found'); }
+    res.writeHead(200, { 'Content-Type': MIME[path.extname(fp)] || 'application/octet-stream' });
+    res.end(buf);
+  });
+}).listen(PORT, () => console.log(`New Arrivals engine → http://127.0.0.1:${PORT} (live /api/new-items ⋈ ${PGDB})`));

(oldest)  ·  back to New Engine  ·  Add HTTP Basic Auth (admin/admin, env-overridable via BASIC_ ff8a755 →