[object Object]

← back to Commercialrealestate

feat(index): saved searches (DTD verdict D, 2/3 Codex+Claude; Qwen dissent C=$/SF-vs-submarket OVERRIDDEN on honest-data grounds — comps.json submarket benchmarks are prose cap ranges, no joinable $/SF/median) — name + save the current filter/sort/view/heat state (reuses the shareable-URL serialization) into a rail 'Saved searches' list; one-click restore (full-URL, bulletproof) + ✕ delete; localStorage-persisted. Contrarian-gated, 0 errors.

45fe54b683ea17f80c5624acde46ca98ba0cf733 · 2026-07-02 15:45:19 -0700 · Steve Abrams

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

Files touched

Diff

commit 45fe54b683ea17f80c5624acde46ca98ba0cf733
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 2 15:45:19 2026 -0700

    feat(index): saved searches (DTD verdict D, 2/3 Codex+Claude; Qwen dissent C=$/SF-vs-submarket OVERRIDDEN on honest-data grounds — comps.json submarket benchmarks are prose cap ranges, no joinable $/SF/median) — name + save the current filter/sort/view/heat state (reuses the shareable-URL serialization) into a rail 'Saved searches' list; one-click restore (full-URL, bulletproof) + ✕ delete; localStorage-persisted. Contrarian-gated, 0 errors.
    
    Officer: vp-engineering
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
 public/index.html | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/public/index.html b/public/index.html
index a51450c..734ae22 100644
--- a/public/index.html
+++ b/public/index.html
@@ -203,6 +203,13 @@
   .quickstart { display:flex; flex-wrap:wrap; gap:8px; justify-content:center; margin:16px 0; }
   .qchip { background:var(--card); border:1px solid var(--line); color:var(--ink); border-radius:18px; padding:7px 14px; font-size:13px; cursor:pointer; }
   .qchip:hover { border-color:var(--blue); color:var(--blue); }
+  .savedrow { display:flex; gap:6px; align-items:center; margin-bottom:5px; }
+  .savedname { flex:1; text-align:left; background:var(--card); border:1px solid var(--line); color:var(--ink); border-radius:8px; padding:6px 9px; font-size:12px; cursor:pointer; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
+  .savedname:hover { border-color:var(--blue); color:var(--blue); }
+  .saveddel { background:none; border:0; color:var(--mut); cursor:pointer; font-size:13px; }
+  .saveddel:hover { color:var(--bad); }
+  #savebtn { width:100%; background:transparent; border:1px dashed #284b7a; color:var(--blue); border-radius:8px; padding:7px; font-size:12px; cursor:pointer; margin-top:4px; }
+  .savedempty { color:var(--mut); font-size:11px; }
   /* compare overlay */
   #cmpov { display:none; position:fixed; inset:0; background:rgba(0,0,0,.6); z-index:60; align-items:flex-start; justify-content:center; padding:40px 14px; overflow:auto; }
   #cmpov.on { display:flex; }
@@ -262,6 +269,7 @@
 <aside id="drawer">
   <div class="dhead"><b>Filters</b><button class="x" id="dclose" aria-label="Close">✕</button></div>
 
+  <div class="fsec"><h4>Saved searches</h4><div id="savedList"></div><button id="savebtn">💾 Save current view</button></div>
   <div class="fsec"><h4>Asset type</h4><div class="chips" id="fType"></div></div>
   <div class="fsec"><h4>Listing firm</h4><div class="chips" id="fFirm"></div></div>
   <div class="fsec"><h4>Submarket / city</h4>
@@ -1127,6 +1135,14 @@ function paintPage(){
   else { g.className='grid'; g.innerHTML=slice.map(card).join('')+moreBtn(); enrichAssessor(); }
   applyHeat(slice);
 }
+// ---- saved searches (name + restore the full shareable-URL state) ----
+const SAVED=(function(){ try{ return JSON.parse(localStorage.getItem('cre_saved')||'[]'); }catch(e){ return []; } })();
+function saveSaved(){ try{ localStorage.setItem('cre_saved', JSON.stringify(SAVED)); }catch(e){} }
+function buildSavedList(){ const el=$('#savedList'); if(!el) return;
+  el.innerHTML = SAVED.length
+    ? SAVED.map((s,i)=>`<div class="savedrow"><button class="savedname" data-qs="${(s.qs||'').replace(/"/g,'&quot;')}" title="Restore this saved view">📁 ${esc(s.name)}</button><button class="saveddel" data-del="${i}" aria-label="Delete saved search">✕</button></div>`).join('')
+    : '<div class="savedempty">None yet — filter, then “Save current view”.</div>';
+}
 function applyQuick(k){
   if(k==='condo'){ F.types=new Set(['Condo']); }
   else if(k==='mf'){ F.types=new Set(['Multifamily']); }
@@ -1384,6 +1400,10 @@ 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); });
+  // saved searches
+  buildSavedList();
+  $('#savedList').addEventListener('click',e=>{ const del=e.target.closest('[data-del]'); if(del){ SAVED.splice(+del.dataset.del,1); saveSaved(); buildSavedList(); return; } const n=e.target.closest('.savedname'); if(n){ location.href=location.pathname+(n.dataset.qs||''); } });
+  $('#savebtn').onclick=()=>{ const name=(prompt('Name this saved search:')||'').trim(); if(!name) return; SAVED.push({name, qs:location.search||''}); saveSaved(); buildSavedList(); };
   $('#linkbtn').onclick=copyShareLink;
   $('#csvbtn').onclick=exportCSV;
   // facet chip handlers (multi-select toggles)

← 6c08b42 feat(index): Compare -> deliverable (DTD verdict A, 2/3 Code  ·  back to Commercialrealestate  ·  feat(index): per-card 📋 Copy summary (DTD verdict C, UNANIM a8fedb1 →