← back to Trending Dw
chore: lint, refactor, v0.1.1 (session close)
eec6dc5458d19ea65fdd54694e487af754bf4d98 · 2026-07-07 05:57:53 -0700 · Steve Abrams
Lint (code-reviewer): esc() applied to 3 unescaped href URLs (card tier link + 2 matchup
modal links); null-guards on titleAZ/company/marketplace localeCompare sort comparators
(crash-safety if a legacy record lacks the field); removed dead 'Object.assign;' no-op in qs().
Refactor (javascript-pro): MS_PER_DAY constant; staleDays IIFE → plain if-block; localize-images
Map get-or-create de-cryptified; collapsed dead duplicate 'ok' branches in loadMeta tier.
No behavior changes — verified: node --check clean, mapper self-test PASS, page renders,
console clean, source links intact.
Known/pre-existing (not this session): hardcoded admin:DW2024! default is the fleet-wide
401-gate convention (env-overridable via BASIC_AUTH) — surfaced, not changed.
Files touched
M package.jsonM public/index.htmlM scripts/localize-images.jsM server.js
Diff
commit eec6dc5458d19ea65fdd54694e487af754bf4d98
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jul 7 05:57:53 2026 -0700
chore: lint, refactor, v0.1.1 (session close)
Lint (code-reviewer): esc() applied to 3 unescaped href URLs (card tier link + 2 matchup
modal links); null-guards on titleAZ/company/marketplace localeCompare sort comparators
(crash-safety if a legacy record lacks the field); removed dead 'Object.assign;' no-op in qs().
Refactor (javascript-pro): MS_PER_DAY constant; staleDays IIFE → plain if-block; localize-images
Map get-or-create de-cryptified; collapsed dead duplicate 'ok' branches in loadMeta tier.
No behavior changes — verified: node --check clean, mapper self-test PASS, page renders,
console clean, source links intact.
Known/pre-existing (not this session): hardcoded admin:DW2024! default is the fleet-wide
401-gate convention (env-overridable via BASIC_AUTH) — surfaced, not changed.
---
package.json | 2 +-
public/index.html | 10 +++++-----
scripts/localize-images.js | 5 ++++-
server.js | 20 ++++++++++++--------
4 files changed, 22 insertions(+), 15 deletions(-)
diff --git a/package.json b/package.json
index e43629a..f20a855 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "trending-dw",
- "version": "0.1.0",
+ "version": "0.1.1",
"private": true,
"description": "trending.designerwallcoverings.com — bestseller intelligence across Google + POD marketplaces",
"main": "server.js",
diff --git a/public/index.html b/public/index.html
index 0c9ae1e..20d006f 100644
--- a/public/index.html
+++ b/public/index.html
@@ -161,7 +161,7 @@ function chips(){
for(const [d,v] of entries){ const c=document.createElement('span'); c.className='fchip'; c.textContent='✕ '+(v==='gap'?'Gap':v==='covered'?'Covered':v); c.onclick=()=>{ if(d==='q'){state.q='';document.getElementById('search').value='';} else toggle(d,v); if(d==='q'){save();chips();refresh();} }; bar.appendChild(c); }
if(entries.length){ const c=document.createElement('span'); c.className='fchip'; c.style.background='var(--gap)'; c.textContent='Clear all'; c.onclick=()=>{ state.filters={};state.q='';document.getElementById('search').value='';save();loadFacets();chips();refresh(); }; bar.appendChild(c); }
}
-function qs(extra){ const p=new URLSearchParams(); for(const d in state.filters) p.set(d, state.filters[d].join(',')); if(state.q) p.set('q',state.q); p.set('sort',state.sort); Object.assign; for(const k in (extra||{})) p.set(k,extra[k]); return p.toString(); }
+function qs(extra){ const p=new URLSearchParams(); for(const d in state.filters) p.set(d, state.filters[d].join(',')); if(state.q) p.set('q',state.q); p.set('sort',state.sort); for(const k in (extra||{})) p.set(k,extra[k]); return p.toString(); }
function esc(s){ return String(s).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c])); }
function fmtSpotted(d){ try{ const dt=new Date(d+'T00:00:00'); return dt.toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric'}); }catch(e){ return d; } }
@@ -170,7 +170,7 @@ async function loadMeta(){
const m = await (await fetch('/api/meta')).json();
const el = document.getElementById('fresh'); if(!el) return;
const d = m.staleDays==null ? null : m.staleDays;
- const tier = d==null ? 'ok' : d<=1 ? 'ok' : d<=3 ? 'ok' : d<=7 ? 'warn' : 'stale';
+ const tier = d==null||d<=3 ? 'ok' : d<=7 ? 'warn' : 'stale';
el.className = 'fresh fresh-'+tier;
const age = d==null ? '—' : d===0 ? 'today' : d===1 ? '1 day ago' : d+' days ago';
const asOf = m.asOf ? fmtSpotted(m.asOf) : '—';
@@ -193,7 +193,7 @@ function card(it){
+'<div class="chips"><span class="chip mk">'+esc(it.marketplace)+'</span><span class="chip">'+esc(it.style)+'</span><span class="chip">'+esc(it.color)+'</span><span class="chip">'+esc(it.priceBand)+'</span></div>'
+(it.spottedAt?'<div class="spotted" title="Spotted '+esc(it.spottedAt)+'">🕓 spotted '+esc(fmtSpotted(it.spottedAt))+'</div>':'')
+'<div class="signal"><span class="dot">●</span> '+esc(it.signal)+'</div>'
- +'<div class="tier">DW tier → '+esc(it.dwTier)+(it.url?' · <a class="src" href="'+it.url+'" target="_blank" rel="noopener noreferrer">source ↗</a>':'')+'</div>'
+ +'<div class="tier">DW tier → '+esc(it.dwTier)+(it.url?' · <a class="src" href="'+esc(it.url)+'" target="_blank" rel="noopener noreferrer">source ↗</a>':'')+'</div>'
+(it.isOurOriginal?'<button class="shopbtn" data-id="'+esc(it.id)+'">+ Put on Shopify</button>':'')
+'</div>';
c.style.cursor="pointer"; c.title="Match against our catalog"; c.addEventListener("click",function(e){ if(!e.target.closest("a")&&!e.target.closest(".shopbtn")) openMatchup(it); });
@@ -295,13 +295,13 @@ function openMatchup(it){
document.getElementById('tmbox').innerHTML='<div style="padding:20px 24px;border-bottom:1px solid #eee;display:flex;gap:16px;align-items:center">'
+(it.image?'<img src="'+it.image+'" style="width:84px;height:84px;object-fit:cover;border-radius:10px">':'')
+'<div><div style="font-size:18px;font-weight:600">'+esc(it.title)+'</div>'
- +'<div style="color:#777;font-size:13px;margin-top:4px">'+esc(it.marketplace)+' · '+esc(it.style)+' · '+esc(it.color)+(it.url?' · <a href="'+it.url+'" target="_blank" rel="noopener noreferrer">source ↗</a>':'')+'</div></div>'
+ +'<div style="color:#777;font-size:13px;margin-top:4px">'+esc(it.marketplace)+' · '+esc(it.style)+' · '+esc(it.color)+(it.url?' · <a href="'+esc(it.url)+'" target="_blank" rel="noopener noreferrer">source ↗</a>':'')+'</div></div>'
+'<button onclick="document.getElementById(\'tmodal\').style.display=\'none\'" style="margin-left:auto;border:0;background:#f2efe9;border-radius:8px;padding:8px 12px;cursor:pointer;font-size:15px">✕</button></div>'
+'<div style="padding:16px 24px"><div style="font-weight:600;margin-bottom:10px">🎯 Matched in our catalog <span id="tmc" style="color:#888;font-weight:400;font-size:13px"></span></div><div id="tmgrid" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:12px">Loading…</div></div>';
document.getElementById('tmodal').style.display='flex';
fetch('/api/matchup?q='+encodeURIComponent(q)).then(function(r){return r.json();}).then(function(d){
document.getElementById('tmc').textContent='('+d.matches.length+' of '+(d.total||0)+' shown)';
- document.getElementById('tmgrid').innerHTML=(d.matches||[]).map(function(x){return '<a href="'+(x.url||'#')+'" target="_blank" rel="noopener noreferrer" style="text-decoration:none;color:inherit;border:1px solid #eee;border-radius:10px;overflow:hidden;display:block">'
+ document.getElementById('tmgrid').innerHTML=(d.matches||[]).map(function(x){return '<a href="'+esc(x.url||'#')+'" target="_blank" rel="noopener noreferrer" style="text-decoration:none;color:inherit;border:1px solid #eee;border-radius:10px;overflow:hidden;display:block">'
+(x.image?'<img src="'+x.image+'" style="width:100%;height:120px;object-fit:cover">':'<div style="height:120px;background:#f2efe9"></div>')
+'<div style="padding:8px"><div style="font-size:12px;font-weight:600;line-height:1.3">'+esc((x.title||"").slice(0,52))+'</div><div style="font-size:11px;color:#888;margin-top:3px">'+esc(x.vendor||"")+'</div><div style="font-size:11px;color:#b8860b;margin-top:4px">View on DW ↗</div></div></a>';}).join('')||'<div style="color:#999">No catalog matches — a GAP worth generating.</div>';
}).catch(function(){document.getElementById('tmgrid').innerHTML='<div style="color:#c00">Matchup unavailable.</div>';});
diff --git a/scripts/localize-images.js b/scripts/localize-images.js
index 679afab..fd4969d 100644
--- a/scripts/localize-images.js
+++ b/scripts/localize-images.js
@@ -30,7 +30,10 @@ function fetchBuf(url){
// collect distinct remote images (skip already-local /assets paths)
const remotes = new Map(); // url -> [items]
for (const it of data.items){
- if (it.image && /^https?:\/\//.test(it.image)) (remotes.get(it.image) || remotes.set(it.image, []).get(it.image)).push(it);
+ if (it.image && /^https?:\/\//.test(it.image)) {
+ if (!remotes.has(it.image)) remotes.set(it.image, []);
+ remotes.get(it.image).push(it);
+ }
}
if (!remotes.size) { console.log('no remote images — already fully local.'); return; }
diff --git a/server.js b/server.js
index def3d49..050efce 100644
--- a/server.js
+++ b/server.js
@@ -15,6 +15,7 @@ const DW_SERIES = 'DWPV'; // DW Pattern Vault — AI-original vault designs
const AUTH = process.env.BASIC_AUTH === undefined ? 'admin:DW2024!' : process.env.BASIC_AUTH; // BASIC_AUTH='' disables (public go-live is gated)
const MIME = { '.html':'text/html', '.js':'application/javascript', '.css':'text/css', '.json':'application/json', '.svg':'image/svg+xml', '.ico':'image/x-icon', '.webp':'image/webp', '.png':'image/png', '.jpg':'image/jpeg', '.jpeg':'image/jpeg', '.gif':'image/gif', '.avif':'image/avif' };
+const MS_PER_DAY = 86400000;
// Styles that are demonstrably selling but ABSENT/thin in our own catalog = the whitespace.
// Derived from the 2026-07-02 gap analysis against pattern-vault/data/catalog.json.
@@ -45,11 +46,14 @@ function loadMeta(){
// staleness must be identical whether this runs on Mac2 (Pacific) or Kamatera (UTC), so
// pin the reference to Steve's timezone and do pure date arithmetic (Date.UTC on both
// endpoints — no tz drift). spottedAt is a bare 'YYYY-MM-DD' with no tz of its own.
- const staleDays = asOf ? (()=>{ const p=String(asOf).split('-').map(Number);
- if(!p[0]||!p[1]||!p[2]) return null;
- const todayStr = new Date().toLocaleDateString('en-CA', { timeZone: 'America/Los_Angeles' }); // YYYY-MM-DD in PT
- const t = todayStr.split('-').map(Number);
- return Math.round((Date.UTC(t[0],t[1]-1,t[2]) - Date.UTC(p[0],p[1]-1,p[2])) / 86400000); })() : null;
+ let staleDays = null;
+ if (asOf) {
+ const p = String(asOf).split('-').map(Number);
+ if (p[0] && p[1] && p[2]) {
+ const t = new Date().toLocaleDateString('en-CA', { timeZone: 'America/Los_Angeles' }).split('-').map(Number); // YYYY-MM-DD in PT
+ staleDays = Math.round((Date.UTC(t[0],t[1]-1,t[2]) - Date.UTC(p[0],p[1]-1,p[2])) / MS_PER_DAY);
+ }
+ }
const withImage = items.filter(i=>i.image).length;
return { generatedAt, asOf, staleDays, count: items.length, imagesAttachedAt: d.imagesAttachedAt || null,
withImage, imageGap: items.length - withImage, source: d.source || null };
@@ -179,9 +183,9 @@ const server = http.createServer((req,res)=>{
const cmp = {
signal: (a,b)=> (b.signalRank||0)-(a.signalRank||0),
gap: (a,b)=> (a.ourCoverage===b.ourCoverage?0:(a.ourCoverage==='gap'?-1:1)) || (b.signalRank||0)-(a.signalRank||0),
- titleAZ: (a,b)=> a.title.localeCompare(b.title),
- company: (a,b)=> a.company.localeCompare(b.company),
- marketplace: (a,b)=> a.marketplace.localeCompare(b.marketplace),
+ titleAZ: (a,b)=> (a.title||'').localeCompare(b.title||''),
+ company: (a,b)=> (a.company||'').localeCompare(b.company||''),
+ marketplace: (a,b)=> (a.marketplace||'').localeCompare(b.marketplace||''),
newest: (a,b)=> (b.spottedAt||'').localeCompare(a.spottedAt||'')
}[sort] || (()=>0);
items = items.slice().sort(cmp);
← 00c19c0 trending: self-host all card images locally (scripts/localiz
·
back to Trending Dw
·
trending: expand seed to ~50 bestsellers/trending PER PLATFO 7ee3b0f →