← back to Trending Dw
trending: fix infinite-scroll stall (fill while sentinel visible) + default sort to imaged-first
c96132fd8c2313b66cab4e619f7fae015157af82 · 2026-07-07 08:42:36 -0700 · Steve
Files touched
M public/index.htmlM server.js
Diff
commit c96132fd8c2313b66cab4e619f7fae015157af82
Author: Steve <steve@designerwallcoverings.com>
Date: Tue Jul 7 08:42:36 2026 -0700
trending: fix infinite-scroll stall (fill while sentinel visible) + default sort to imaged-first
---
public/index.html | 10 +++++++++-
server.js | 3 ++-
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/public/index.html b/public/index.html
index 20d006f..3f9855f 100644
--- a/public/index.html
+++ b/public/index.html
@@ -108,6 +108,7 @@
<button class="queuebtn" id="queueBtn" onclick="openQueue()">🛍 Shopify queue <b id="qn">0</b></button>
<div><label>Sort</label>
<select id="sort">
+ <option value="have">Imaged first</option>
<option value="signal">Bestseller signal</option>
<option value="gap">Gaps first</option>
<option value="newest">Newest</option>
@@ -129,7 +130,7 @@
const store = k => { try{ return JSON.parse(localStorage.getItem(k)); }catch(e){ return null; } };
const state = {
filters: store('tdw_filters2') || {}, // {dim: [values]}
- q:'', sort: localStorage.getItem('tdw_sort')||'signal', view: localStorage.getItem('tdw_view')||'grid',
+ q:'', sort: localStorage.getItem('tdw_sort')||'have', view: localStorage.getItem('tdw_view')||'grid',
page:1, size:12, loading:false, done:false
};
const DIMS = [['ourCoverage','Coverage'],['marketplace','Marketplace'],['company','Company'],['style','Style'],['color','Color'],['priceBand','Price']];
@@ -213,6 +214,13 @@ async function fetchPage(){
d.items.forEach(it=>document.getElementById('grid').appendChild(card(it)));
if(state.page*state.size>=d.total) state.done=true;
state.page++; state.loading=false; document.getElementById('loading').style.display='none';
+ // If the loaded cards didn't fill the viewport, the IntersectionObserver won't
+ // re-fire (it only fires on an intersection *transition*) and the board stalls on
+ // page 1. Keep loading while the sentinel is still within (viewport + 400px).
+ if(!state.done){
+ const s=document.getElementById('sentinel'), r=s&&s.getBoundingClientRect();
+ if(r && r.top < (window.innerHeight||document.documentElement.clientHeight)+400) return fetchPage();
+ }
}
async function renderLanes(){
const d=await (await fetch('/api/lanes?'+qs())).json();
diff --git a/server.js b/server.js
index 050efce..16e8121 100644
--- a/server.js
+++ b/server.js
@@ -179,8 +179,9 @@ const server = http.createServer((req,res)=>{
if (u.pathname === '/api/items'){
let items = applyFilters(loadItems(), u.searchParams);
- const sort = u.searchParams.get('sort') || 'signal';
+ const sort = u.searchParams.get('sort') || 'have';
const cmp = {
+ have: (a,b)=> ((b.image?1:0)-(a.image?1:0)) || (b.signalRank||0)-(a.signalRank||0),
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||''),
← 7ee3b0f trending: expand seed to ~50 bestsellers/trending PER PLATFO
·
back to Trending Dw
·
gapgen: data-driven style-keyed briefs from live gap items ( bedb55d →