โ 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 โ