← back to Commercialrealestate
feat(index): active-filter summary bar — removable chips for every applied filter + one-click Reset all above the grid (DTD verdict A, 2/3: Claude+Qwen; Codex dissent=URL-share). Closes the 'what's filtering this?' gap from the collapsed-tabs/wait-for-selection UX; empty-state now guides expanding a tab. Contrarian-gated.
9dc699f2bdd79206503ca977083ed98a925a06e4 · 2026-07-02 10:43:01 -0700 · Steve Abrams
Officer: vp-engineering
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Files touched
Diff
commit 9dc699f2bdd79206503ca977083ed98a925a06e4
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 2 10:43:01 2026 -0700
feat(index): active-filter summary bar — removable chips for every applied filter + one-click Reset all above the grid (DTD verdict A, 2/3: Claude+Qwen; Codex dissent=URL-share). Closes the 'what's filtering this?' gap from the collapsed-tabs/wait-for-selection UX; empty-state now guides expanding a tab. Contrarian-gated.
Officer: vp-engineering
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
public/index.html | 50 ++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 48 insertions(+), 2 deletions(-)
diff --git a/public/index.html b/public/index.html
index 48d6ea3..d47ef7c 100644
--- a/public/index.html
+++ b/public/index.html
@@ -172,6 +172,14 @@
.fsec.collapsed > h4::after { transform:rotate(-90deg); }
.fsec.collapsed > *:not(h4) { display:none !important; }
.assessline { border-top:1px dashed rgba(255,255,255,.06); padding-top:6px; margin-top:2px; }
+ /* active-filter summary bar */
+ .activebar { display:flex; flex-wrap:wrap; gap:8px; align-items:center; padding:10px 20px; border-bottom:1px solid var(--line); background:rgba(88,166,255,.03); }
+ .activebar:empty { display:none; }
+ .activebar .lbl { font-size:11px; color:var(--mut); text-transform:uppercase; letter-spacing:.5px; }
+ .afchip { display:inline-flex; align-items:center; gap:6px; background:rgba(88,166,255,.10); border:1px solid rgba(88,166,255,.4); color:var(--blue); border-radius:16px; padding:4px 10px; font-size:12px; cursor:pointer; }
+ .afchip:hover { background:rgba(88,166,255,.2); } .afchip .xx { opacity:.65; font-weight:700; }
+ .afreset { background:transparent; border:1px solid #7d2b28; color:var(--bad); border-radius:14px; padding:4px 11px; font-size:12px; cursor:pointer; margin-left:auto; }
+ .afreset:hover { background:rgba(248,81,73,.1); }
</style>
</head>
<body>
@@ -417,6 +425,7 @@
<select id="sortTop" style="width:auto;min-width:180px"></select>
</div>
</div>
+ <div class="activebar" id="activebar"></div>
<div class="grid" id="grid"></div>
<div class="foot" id="foot"></div>
</main>
@@ -844,13 +853,48 @@ const SORTS={ deal:(a,b)=>dealNum(b)-dealNum(a), composite:(a,b)=>b.composite-a.
const num=v=>v==null?-1e9:v;
const dealNum=p=>(p.deal && p.deal.score!=null) ? p.deal.score : -1e9; // unscored sink to the bottom
+// ---- active-filter summary bar (removable chips + reset) ----
+function renderActiveBar(){
+ const bar=$('#activebar'); if(!bar) return; const chips=[];
+ F.types.forEach(t=>chips.push(['type:'+t, t]));
+ F.firms.forEach(f=>chips.push(['firm:'+f, '🏢 '+f]));
+ if(F.cityQuery) chips.push(['city', 'city ~ “'+F.cityQuery+'”']);
+ if(F.priceMin!=null) chips.push(['priceMin', '≥ $'+(+F.priceMin).toLocaleString()]);
+ if(F.priceMax!=null) chips.push(['priceMax', '≤ $'+(+F.priceMax).toLocaleString()]);
+ if(F.capMin>0) chips.push(['capMin', 'cap ≥ '+F.capMin+'%']);
+ if(F.unitsMin!=null) chips.push(['unitsMin', '≥ '+F.unitsMin+' units']);
+ if(F.yearMin!=null) chips.push(['yearMin', 'built ≥ '+F.yearMin]);
+ if(F.status!=='all') chips.push(['status', 'active only']);
+ if(F.afford) chips.push(['afford', 'in budget']);
+ if(F.unwarrantable) chips.push(['unwarrantable', '⚑ unwarrantable']);
+ if(F.nonqm) chips.push(['nonqm', '◆ non-QM']);
+ bar.innerHTML = chips.length
+ ? '<span class="lbl">Filtering</span>'+chips.map(([k,l])=>`<span class="afchip" data-clear="${k.replace(/"/g,'"')}">${esc(l)} <span class="xx">✕</span></span>`).join('')+'<button class="afreset" id="afreset">Reset all ✕</button>'
+ : '';
+}
+function clearFilter(spec){
+ if(spec.startsWith('type:')){ F.types.delete(spec.slice(5)); if(!F.types.has('Condo')) F.unwarrantable=false; }
+ else if(spec.startsWith('firm:')){ F.firms.delete(spec.slice(5)); }
+ else if(spec==='city'){ F.cityQuery=''; if($('#fCity')) $('#fCity').value=''; }
+ else if(spec==='priceMin'){ F.priceMin=null; if($('#fPriceMin')) $('#fPriceMin').value=''; }
+ else if(spec==='priceMax'){ F.priceMax=null; if($('#fPriceMax')) $('#fPriceMax').value=''; }
+ else if(spec==='capMin'){ F.capMin=0; if($('#fCap')){ $('#fCap').value=0; $('#capVal').textContent='any'; } }
+ else if(spec==='unitsMin'){ F.unitsMin=null; if($('#fUnits')) $('#fUnits').value=''; }
+ else if(spec==='yearMin'){ F.yearMin=null; if($('#fYear')) $('#fYear').value=''; }
+ else if(spec==='status'){ F.status='all'; }
+ else if(spec==='afford'){ F.afford=false; if($('#fAfford')) $('#fAfford').checked=false; }
+ else if(spec==='unwarrantable'){ F.unwarrantable=false; }
+ else if(spec==='nonqm'){ F.nonqm=false; }
+ syncChips(); render();
+}
function render(){
const g=$('#grid'); const fc=activeFilterCount();
$('#fcount').textContent=fc; $('#fcount').classList.toggle('on',fc>0);
+ renderActiveBar();
// 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';
- g.innerHTML=`<div class="empty">👈 Pick an asset type or filter to begin.<br><span class="small">${(DATA.ranked.length).toLocaleString()} listings loaded · ${DATA.meta.market}. Try <b>Condo</b>, <b>⚑ Unwarrantable</b>, <b>◆ Non-QM</b>, a city, or a price range — results appear instantly.</span></div>`;
+ g.innerHTML=`<div class="empty">👈 Expand a filter tab on the left to begin.<br><span class="small">${(DATA.ranked.length).toLocaleString()} listings loaded · ${DATA.meta.market}. Open <b>Asset type</b> (Condo · ◆ Non-QM · ⚑ Unwarrantable), <b>City</b>, or <b>Price</b> — results appear instantly, and your active filters show as removable chips up top.</span></div>`;
$('#count').innerHTML=`<b>${(DATA.ranked.length).toLocaleString()}</b> loaded · choose a filter to view`;
saveState(); return;
}
@@ -946,7 +990,7 @@ function loadState(){ try{ const s=JSON.parse(localStorage.getItem('cre_filters'
if(s.capMin){ $('#fCap').value=s.capMin; $('#capVal').textContent=s.capMin+'%+'; } if(s.unitsMin) $('#fUnits').value=s.unitsMin; if(s.yearMin) $('#fYear').value=s.yearMin;
$('#fAfford').checked=F.afford; if(s.sort){ $('#sort').value=s.sort; }
}catch(e){} }
-function resetAll(){ F.types.clear(); F.firms.clear(); F.cityQuery=''; F.priceMin=F.priceMax=F.unitsMin=F.yearMin=null; F.capMin=0; F.status='all'; F.afford=false;
+function resetAll(){ F.types.clear(); F.firms.clear(); F.cityQuery=''; F.priceMin=F.priceMax=F.unitsMin=F.yearMin=null; F.capMin=0; F.status='all'; F.afford=false; F.unwarrantable=false; F.nonqm=false;
['fCity','fPriceMin','fPriceMax','fUnits','fYear'].forEach(id=>$('#'+id).value=''); $('#fCap').value=0; $('#capVal').textContent='any'; $('#fAfford').checked=false;
syncChips(); render(); }
@@ -1057,6 +1101,8 @@ fetch('data/ranked.json').then(r=>r.json()).then(async d=>{
// collapsible filter tabs — click a section header to expand/collapse; all collapsed on load
$('#drawer').addEventListener('click',e=>{ const h=e.target.closest('h4'); if(h && h.parentElement.classList.contains('fsec')) h.parentElement.classList.toggle('collapsed'); });
$$('#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); });
// 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; }
← e9d13a8 feat(index): collapsible filter tabs (all collapsed on load)
·
back to Commercialrealestate
·
feat(index): shareable URL state (DTD dissent B / Codex) — e 2414c9b →