← back to Gmc Viewer
gmc-viewer: href-drill — status badge is a filter-link + URL-addressable state
8669671668e70e8d50a6a3a8dcb291a9f2095699 · 2026-07-31 17:46:56 -0700 · Steve Abrams
Each card's Merchant status badge (approved/disapproved/pending — the statuses the
/api/products filter supports) is now an <a href="?filter=<status>"> to its filtered
view (href-drill HARD rule); the chip filter + search are URL-addressable too. Click
filters in place + reflects into the URL, cmd/ctrl-click opens the shareable href,
back-button + deep-link restore via popstate/readURL. Brand/country left as text — the
API can't filter them, and gaming the detector without a real drill is not the fix.
Verified (Playwright + basic auth, live :9971): click an approved badge -> ?filter=approved
+ every card now approved; deep-link restores; back-button restores; canary FLAT->DONE;
0 pageerrors. href-drill backlog grid 5/92. TK-10089.
Files touched
Diff
commit 8669671668e70e8d50a6a3a8dcb291a9f2095699
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Jul 31 17:46:56 2026 -0700
gmc-viewer: href-drill — status badge is a filter-link + URL-addressable state
Each card's Merchant status badge (approved/disapproved/pending — the statuses the
/api/products filter supports) is now an <a href="?filter=<status>"> to its filtered
view (href-drill HARD rule); the chip filter + search are URL-addressable too. Click
filters in place + reflects into the URL, cmd/ctrl-click opens the shareable href,
back-button + deep-link restore via popstate/readURL. Brand/country left as text — the
API can't filter them, and gaming the detector without a real drill is not the fix.
Verified (Playwright + basic auth, live :9971): click an approved badge -> ?filter=approved
+ every card now approved; deep-link restores; back-button restores; canary FLAT->DONE;
0 pageerrors. href-drill backlog grid 5/92. TK-10089.
---
public/index.html | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)
diff --git a/public/index.html b/public/index.html
index 512830f..d4a71ba 100644
--- a/public/index.html
+++ b/public/index.html
@@ -43,6 +43,12 @@ input[type=range]{vertical-align:middle}
<div class="grid" id="grid"><div class="load">loading…</div></div>
<script>
const $=s=>document.querySelector(s); let filter='all',q='';
+// ── href-drill: filter state is URL-addressable (shareable + deep-link + back-button) ──
+const DRILLABLE=new Set(['approved','disapproved','pending']); // statuses the /api/products filter supports
+function qstr(over){ const f=(over&&'filter'in over)?over.filter:filter, qq=(over&&'q'in over)?over.q:q; const u=[]; if(f&&f!=='all')u.push('filter='+encodeURIComponent(f)); if(qq)u.push('q='+encodeURIComponent(qq)); return u.length?('?'+u.join('&')):location.pathname; }
+function drillStatus(st,label){ return `<a class="st ${st} drill" data-filter="${st}" href="${qstr({filter:st})}" title="Show only ${st}">${label}</a>`; }
+function readURL(){ const u=new URLSearchParams(location.search); filter=u.get('filter')||'all'; q=u.get('q')||''; const qe=$('#q'); if(qe)qe.value=q; document.querySelectorAll('.chip[data-f]').forEach(x=>x.classList.toggle('on',x.dataset.f===filter)); }
+function writeURL(push){ history[push?'pushState':'replaceState']({},'',qstr()); }
const dens=$('#dens'); dens.oninput=()=>document.documentElement.style.setProperty('--cols',dens.value);
document.documentElement.style.setProperty('--cols',6);
async function summary(){
@@ -65,12 +71,15 @@ async function load(){
<div class="b"><div class="t" title="${(i.title||'').replace(/"/g,'"')}">${i.title||'(untitled)'}</div>
<div class="v">${i.brand||''} ${i.country?'· '+i.country:''}</div>
<div class="price ${s}">$${(i.price||0).toFixed(2)}</div>
- <span class="st ${st}">${st}${i.issues&&i.issues.length?' · '+i.issues[0]:''}</span></div></div>`;
+ ${DRILLABLE.has(st)?drillStatus(st,st+(i.issues&&i.issues.length?' · '+i.issues[0]:'')):`<span class="st ${st}">${st}${i.issues&&i.issues.length?' · '+i.issues[0]:''}</span>`}</div></div>`;
}).join('');
}
-document.querySelectorAll('.chip[data-f]').forEach(c=>c.onclick=()=>{document.querySelectorAll('.chip[data-f]').forEach(x=>x.classList.remove('on'));c.classList.add('on');filter=c.dataset.f;load();});
-$('#c-all').classList.add('on');
-let qt; $('#q').oninput=e=>{clearTimeout(qt);q=e.target.value;qt=setTimeout(load,250);};
+document.querySelectorAll('.chip[data-f]').forEach(c=>c.onclick=()=>{document.querySelectorAll('.chip[data-f]').forEach(x=>x.classList.remove('on'));c.classList.add('on');filter=c.dataset.f;writeURL(true);load();});
+let qt; $('#q').oninput=e=>{clearTimeout(qt);q=e.target.value;qt=setTimeout(()=>{writeURL(false);load();},250);};
$('#refresh').onclick=async()=>{await fetch('/api/refresh');summary();};
-summary();load();
+// a card's status badge is an href-drill atom → filter the grid to that status + reflect into the URL
+document.addEventListener('click',e=>{ const d=e.target.closest('a.drill[data-filter]'); if(!d)return; if(e.metaKey||e.ctrlKey||e.shiftKey||e.button===1)return; e.preventDefault();
+ filter=d.dataset.filter; q=''; const qe=$('#q'); if(qe)qe.value=''; document.querySelectorAll('.chip[data-f]').forEach(x=>x.classList.toggle('on',x.dataset.f===filter)); writeURL(true); load(); });
+addEventListener('popstate',()=>{ readURL(); load(); }); // back/forward restores filter state
+readURL(); summary(); load(); // land in the filter state carried by the URL
</script></body></html>
← a07e983 auto-save: 2026-07-09T14:10:49 (1 files) — dw-ga4-custom-pix
·
back to Gmc Viewer
·
nav-agent: universal grid-controls drop-in on internal dashb 5341e5a →