[object Object]

← back to Vendor Agents Viewer

vendor-agents-viewer: href-drill — data points now filter-links (URL-addressable)

c4e0072e76bad84ec079d0d26b3b6674dca18e03 · 2026-07-31 14:51:04 -0700 · Steve Abrams

Every vendor + status data point is now an <a href="?vendor=…&status=…"> to its
filtered view (href-drill HARD rule). Inline primitives (qstr/drill/readURL/writeURL)
tailored to this viewer's search+dropdown model; click filters in place + reflects into
the URL, cmd/ctrl-click opens the shareable href, back-button + deep-link restore via
popstate/readURL.

Verified (Playwright, live :9789): click Brewster & York -> ?vendor=Brewster+%26+York,
grid 115->1 all-matching; deep-link restores; back-button restores 115; canary
FLAT->DONE; 0 pageerrors. href-drill backlog grid 1/92. TK-10089.

Files touched

Diff

commit c4e0072e76bad84ec079d0d26b3b6674dca18e03
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jul 31 14:51:04 2026 -0700

    vendor-agents-viewer: href-drill — data points now filter-links (URL-addressable)
    
    Every vendor + status data point is now an <a href="?vendor=…&status=…"> to its
    filtered view (href-drill HARD rule). Inline primitives (qstr/drill/readURL/writeURL)
    tailored to this viewer's search+dropdown model; click filters in place + reflects into
    the URL, cmd/ctrl-click opens the shareable href, back-button + deep-link restore via
    popstate/readURL.
    
    Verified (Playwright, live :9789): click Brewster & York -> ?vendor=Brewster+%26+York,
    grid 115->1 all-matching; deep-link restores; back-button restores 115; canary
    FLAT->DONE; 0 pageerrors. href-drill backlog grid 1/92. TK-10089.
---
 public/index.html | 34 ++++++++++++++++++++++++++++++----
 1 file changed, 30 insertions(+), 4 deletions(-)

diff --git a/public/index.html b/public/index.html
index 16cf214..6ae9e76 100644
--- a/public/index.html
+++ b/public/index.html
@@ -61,11 +61,28 @@
 let ALL=[];
 const $=s=>document.querySelector(s);
 const fmtDate=d=>d?new Date(d).toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric',hour:'numeric',minute:'2-digit'}):'—';
+// ── href-to-deeper-data primitives (HARD rule) — every displayed data point is an href to its
+//    filtered view; filter state is URL-addressable (shareable + deep-link + back-button). ──
+const esc0=s=>(s==null?'':String(s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/"/g,'&quot;');
+const FIL={vendor:''};                                  // vendor is the drill-only facet; status uses #filter, q uses #q
+function qstr(over){                                     // build the URL for the current (optionally overridden) filter set
+  const f=Object.assign({vendor:FIL.vendor,status:$('#filter').value,q:$('#q').value.trim()},over||{});
+  const u=new URLSearchParams();
+  if(f.vendor)u.set('vendor',f.vendor);
+  if(f.status&&f.status!=='all')u.set('status',f.status);
+  if(f.q)u.set('q',f.q);
+  const s=u.toString(); return s?('?'+s):location.pathname;
+}
+// drill(field,val,label) → an <a> whose href IS the filtered view for that value
+const drill=(field,val,label,cls)=>{ if(val==null||val==='')return esc0(label!=null?label:val);
+  return `<a class="drill ${cls||''}" data-filter="${field}" data-v="${esc0(val)}" href="${esc0(qstr({[field]:String(val)}))}" title="Filter to ${esc0(val)}">${esc0(label!=null?label:val)}</a>`; };
+function readURL(){ const u=new URLSearchParams(location.search); FIL.vendor=u.get('vendor')||''; $('#filter').value=u.get('status')||'all'; $('#q').value=u.get('q')||''; }
+function writeURL(push){ history[push?'pushState':'replaceState']({},'',qstr()); }
 function logmsg(t){const l=$('#log');l.style.display='block';l.textContent=t;clearTimeout(l._t);l._t=setTimeout(()=>l.style.display='none',6000);}
 async function act(ep,body,label){logmsg(label+'…');try{const r=await fetch('/api/'+ep,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(body)});const j=await r.json();logmsg(label+': '+(j.ok?'ok ':'')+(j.out||j.error||'').slice(0,180));setTimeout(load,2500);}catch(e){logmsg(label+' failed: '+e.message);}}
 function render(){
   const q=$('#q').value.toLowerCase(), sort=$('#sort').value, filt=$('#filter').value;
-  let a=ALL.filter(x=>(!q||((x.agent_name||'')+ (x.vendor_name||'')).toLowerCase().includes(q)) && (filt==='all'||x.status===filt));
+  let a=ALL.filter(x=>(!q||((x.agent_name||'')+ (x.vendor_name||'')).toLowerCase().includes(q)) && (filt==='all'||x.status===filt) && (!FIL.vendor||(x.vendor_name||'')===FIL.vendor));
   const num=k=>(x=>(x[k]||0));
   const cmp={status:(a,b)=>(a.status==='online'?0:1)-(b.status==='online'?0:1)||(a.agent_port||1e9)-(b.agent_port||1e9),
     port:(a,b)=>(a.agent_port||1e9)-(b.agent_port||1e9),
@@ -78,8 +95,8 @@ function render(){
   $('#grid').innerHTML=a.map(x=>`
     <div class="card ${x.status}">
       <div class="row1">
-        <div><div class="nm">${x.agent_name||'(unnamed)'}</div><div class="ven">${x.vendor_name||''}${x.agent_port?' · :'+x.agent_port:''}</div></div>
-        <span class="badge ${x.status}">${x.status}</span>
+        <div><div class="nm">${x.agent_name||'(unnamed)'}</div><div class="ven">${x.vendor_name?drill('vendor',x.vendor_name,x.vendor_name):''}${x.agent_port?' · :'+x.agent_port:''}</div></div>
+        ${drill('status',x.status,x.status,'badge '+x.status)}
       </div>
       <div class="desc">${x.description||''}</div>
       <div class="kv">
@@ -110,7 +127,16 @@ async function load(){
 // persist controls
 ['sort','filter','dens'].forEach(id=>{const el=$('#'+id);const k='vav_'+id;if(localStorage[k])el.value=localStorage[k];el.addEventListener('input',()=>{localStorage[k]=el.value;if(id==='dens')document.documentElement.style.setProperty('--cols',el.value);else render();});});
 document.documentElement.style.setProperty('--cols',$('#dens').value);
-$('#q').addEventListener('input',render);
+$('#q').addEventListener('input',()=>{writeURL(false);render();});
+$('#filter').addEventListener('change',()=>writeURL(true));
+// click a data point → filter in place + reflect into the URL (cmd/ctrl/middle-click opens the shareable href in a new tab)
+document.addEventListener('click',e=>{ const dr=e.target.closest('.drill'); if(!dr)return; if(e.metaKey||e.ctrlKey||e.shiftKey||e.button===1)return; e.preventDefault();
+  const f=dr.dataset.filter, v=dr.dataset.v;
+  if(f==='vendor') FIL.vendor = (FIL.vendor===v?'':v);
+  else if(f==='status') $('#filter').value = ($('#filter').value===v?'all':v);
+  writeURL(true); render(); });
+addEventListener('popstate',()=>{ readURL(); render(); });   // back/forward restores filter state
+readURL();                                                   // land in the filter state carried by the URL
 load();setInterval(load,45000);
 </script>
 </body>

← b2d905e chore: macstudio3 migration — reconcile from mac2 + repoint  ·  back to Vendor Agents Viewer  ·  viewer standard: add Grid/List toggle + per-field on/off men c19c33c →