[object Object]

โ† back to Commercialrealestate

feat(index): shareable URL state (DTD dissent B / Codex) โ€” every filter/sort/view/scenario โ†” query string via replaceState; deep-links restore the exact view and override wait-for-selection; ๐Ÿ”— Copy-link button. Contrarian gate caught + fixed a real bug: local 'history' array shadowed window.history, silently no-op'ing replaceState.

2414c9bf18f4bad0afd781fb06c35f2e065f25cc ยท 2026-07-02 10:46:24 -0700 ยท Steve Abrams

Officer: vp-engineering
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files touched

Diff

commit 2414c9bf18f4bad0afd781fb06c35f2e065f25cc
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 2 10:46:24 2026 -0700

    feat(index): shareable URL state (DTD dissent B / Codex) โ€” every filter/sort/view/scenario โ†” query string via replaceState; deep-links restore the exact view and override wait-for-selection; ๐Ÿ”— Copy-link button. Contrarian gate caught + fixed a real bug: local 'history' array shadowed window.history, silently no-op'ing replaceState.
    
    Officer: vp-engineering
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
 public/index.html | 48 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 47 insertions(+), 1 deletion(-)

diff --git a/public/index.html b/public/index.html
index d47ef7c..99e2abd 100644
--- a/public/index.html
+++ b/public/index.html
@@ -421,6 +421,7 @@
       <button data-view="list">โ˜ฐ List</button>
     </span>
     <div class="right">
+      <button id="linkbtn" class="ex" title="Copy a shareable link to this exact filtered view">๐Ÿ”— Copy link</button>
       <label>Sort</label>
       <select id="sortTop" style="width:auto;min-width:180px"></select>
     </div>
@@ -887,10 +888,52 @@ function clearFilter(spec){
   else if(spec==='nonqm'){ F.nonqm=false; }
   syncChips(); render();
 }
+// ---- shareable URL state (every filter/sort/view/scenario โ†” query string) ----
+function stateToURL(){
+  const p=new URLSearchParams();
+  if(F.types.size) p.set('t',[...F.types].join(','));
+  if(F.firms.size) p.set('fm',[...F.firms].join('~'));
+  if(F.cityQuery) p.set('city',F.cityQuery);
+  if(F.priceMin!=null) p.set('pmin',F.priceMin);
+  if(F.priceMax!=null) p.set('pmax',F.priceMax);
+  if(F.capMin>0) p.set('cap',F.capMin);
+  if(F.unitsMin!=null) p.set('u',F.unitsMin);
+  if(F.yearMin!=null) p.set('yr',F.yearMin);
+  if(F.status!=='all') p.set('st',F.status);
+  if(F.afford) p.set('aff','1');
+  if(F.unwarrantable) p.set('unw','1');
+  if(F.nonqm) p.set('nq','1');
+  if(scenario!=='25') p.set('sc',scenario);
+  const so=$('#sort')&&$('#sort').value; if(so && so!=='deal') p.set('sort',so);
+  if(view!=='grid') p.set('v',view);
+  const qs=p.toString();
+  // NOTE: the app declares a local `history` (chat log) that shadows window.history โ€” must qualify.
+  try{ window.history.replaceState(null,'',location.pathname+(qs?('?'+qs):'')); }catch(e){}
+}
+function urlToState(){
+  const p=new URLSearchParams(location.search); let any=false;
+  if(p.get('t')){ F.types=new Set(p.get('t').split(',').filter(Boolean)); any=true; }
+  if(p.get('fm')){ F.firms=new Set(p.get('fm').split('~').filter(Boolean)); any=true; }
+  if(p.get('city')){ F.cityQuery=p.get('city'); if($('#fCity'))$('#fCity').value=F.cityQuery; any=true; }
+  if(p.get('pmin')){ F.priceMin=+p.get('pmin'); if($('#fPriceMin'))$('#fPriceMin').value=F.priceMin; any=true; }
+  if(p.get('pmax')){ F.priceMax=+p.get('pmax'); if($('#fPriceMax'))$('#fPriceMax').value=F.priceMax; any=true; }
+  if(p.get('cap')){ F.capMin=+p.get('cap'); if($('#fCap')){$('#fCap').value=F.capMin; $('#capVal').textContent=F.capMin+'%+';} any=true; }
+  if(p.get('u')){ F.unitsMin=+p.get('u'); if($('#fUnits'))$('#fUnits').value=F.unitsMin; any=true; }
+  if(p.get('yr')){ F.yearMin=+p.get('yr'); if($('#fYear'))$('#fYear').value=F.yearMin; any=true; }
+  if(p.get('st')){ F.status=p.get('st'); any=true; }
+  if(p.get('aff')==='1'){ F.afford=true; if($('#fAfford'))$('#fAfford').checked=true; any=true; }
+  if(p.get('unw')==='1'){ F.unwarrantable=true; any=true; }
+  if(p.get('nq')==='1'){ F.nonqm=true; any=true; }
+  if(p.get('sc')){ scenario=p.get('sc'); }
+  if(p.get('sort')&&$('#sort')){ $('#sort').value=p.get('sort'); if($('#sortTop'))$('#sortTop').value=p.get('sort'); }
+  if(p.get('v')){ view=p.get('v'); }
+  return any;
+}
+function copyShareLink(){ const t=$('#linkbtn'); try{ navigator.clipboard.writeText(location.href); if(t){ const o=t.textContent; t.textContent='โœ“ Copied'; setTimeout(()=>t.textContent=o,1400); } }catch(e){ if(t) t.textContent='โŒ˜C to copy'; } }
 function render(){
   const g=$('#grid'); const fc=activeFilterCount();
   $('#fcount').textContent=fc; $('#fcount').classList.toggle('on',fc>0);
-  renderActiveBar();
+  renderActiveBar(); stateToURL();
   // Wait-for-selection: on a clean load (no filter chosen) don't dump 2,134 cards โ€” prompt for a pick.
   if(fc===0 && !F.cityQuery){
     g.className='grid';
@@ -1094,6 +1137,8 @@ fetch('data/ranked.json').then(r=>r.json()).then(async d=>{
   $$('#scenario button').forEach(b=>b.classList.toggle('active',b.dataset.s===scenario));
   buildFacets();
   loadState();
+  urlToState();  // a shared ?query overrides saved localStorage state and restores the exact view
+  $$('#scenario button').forEach(b=>b.classList.toggle('active',b.dataset.s===scenario));
 
   // drawer wiring
   $('#burger').onclick=openDrawer; $('#dclose').onclick=closeDrawer; $('#scrim').onclick=closeDrawer;
@@ -1103,6 +1148,7 @@ fetch('data/ranked.json').then(r=>r.json()).then(async d=>{
   $$('#drawer .fsec').forEach(s=>s.classList.add('collapsed'));
   // active-filter summary bar: remove one filter or reset all
   $('#activebar').addEventListener('click',e=>{ if(e.target.closest('#afreset')){ resetAll(); return; } const c=e.target.closest('.afchip'); if(c) clearFilter(c.dataset.clear); });
+  $('#linkbtn').onclick=copyShareLink;
   // facet chip handlers (multi-select toggles)
   $('#fType').onclick=e=>{ const c=e.target.closest('.chip'); if(!c) return;
     if(c.dataset.warr==='nonqm'){ F.nonqm=!F.nonqm; syncChips(); render(); return; }

โ† 9dc699f feat(index): active-filter summary bar โ€” removable chips for  ยท  back to Commercialrealestate  ยท  auto-save: 2026-07-02T10:47:21 (1 files) โ€” public/index.html d036a01 โ†’