← back to Commercialrealestate
feat(mls+broker-grid): fleet consistency (DTD Option D) — collapsible rail tabs (all collapsed on load) on both; mls gains wait-for-selection empty state + ◆ Non-QM in-grid filter (unwarrantable condos + small multifamily 1-9u + mixed-use = 1823). broker-grid keeps its browsable ranked default (people directory, not filter-first). Contrarian-gated, 0 errors.
acf37686617cf74f35c8c9accb8386285678b574 · 2026-07-02 11:08:46 -0700 · Steve Abrams
Officer: vp-engineering
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Files touched
M public/broker-grid.htmlM public/mls.html
Diff
commit acf37686617cf74f35c8c9accb8386285678b574
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jul 2 11:08:46 2026 -0700
feat(mls+broker-grid): fleet consistency (DTD Option D) — collapsible rail tabs (all collapsed on load) on both; mls gains wait-for-selection empty state + ◆ Non-QM in-grid filter (unwarrantable condos + small multifamily 1-9u + mixed-use = 1823). broker-grid keeps its browsable ranked default (people directory, not filter-first). Contrarian-gated, 0 errors.
Officer: vp-engineering
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
public/broker-grid.html | 8 ++++++++
public/mls.html | 27 +++++++++++++++++++++++++--
2 files changed, 33 insertions(+), 2 deletions(-)
diff --git a/public/broker-grid.html b/public/broker-grid.html
index f3ec5f4..12e4955 100644
--- a/public/broker-grid.html
+++ b/public/broker-grid.html
@@ -62,6 +62,10 @@
.minibtn{background:var(--card);color:var(--mut);border:1px solid var(--line);border-radius:7px;padding:4px 9px;font-size:11px;cursor:pointer;margin-right:5px;} .minibtn:hover{border-color:var(--gold);color:var(--ink);}
.wrap{flex:1 1 auto;min-width:0;}
@media(max-width:820px){ .rail{display:none;} }
+ .rail .rsec > h4 { cursor:pointer; display:flex; align-items:center; justify-content:space-between; user-select:none; }
+ .rail .rsec > h4::after { content:'▾'; color:var(--mut); font-size:11px; margin-left:8px; transition:transform .15s; }
+ .rail .rsec.collapsed > h4::after { transform:rotate(-90deg); }
+ .rail .rsec.collapsed > *:not(h4) { display:none !important; }
</style>
</head>
<body>
@@ -236,6 +240,10 @@ $('#fArc').addEventListener('click',e=>{const c=e.target.closest('.chip');if(!c)
$('#fFields').addEventListener('change',e=>{const cb=e.target.closest('input[data-ck]');if(!cb)return;VISCOL[cb.dataset.ck]=cb.checked;saveCols();render();});
$('#fAll').addEventListener('click',()=>{COLS.forEach(c=>VISCOL[c.k]=true);saveCols();buildFieldToggles();render();});
$('#fNone').addEventListener('click',()=>{COLS.forEach(c=>VISCOL[c.k]=false);saveCols();buildFieldToggles();render();});
+// collapsible rail tabs — all collapsed on load
+const railEl=document.querySelector('.rail');
+if(railEl){ railEl.addEventListener('click',e=>{ const h=e.target.closest('h4'); if(h && h.parentElement.classList.contains('rsec')) h.parentElement.classList.toggle('collapsed'); });
+ document.querySelectorAll('.rail .rsec').forEach(s=>s.classList.add('collapsed')); }
fetch('/api/brokers/all').then(r=>r.json()).then(d=>{DATA=d.brokers||[];autoCols();buildRail();render();})
.catch(()=>{$('#count').textContent='failed to load /api/brokers/all';});
</script>
diff --git a/public/mls.html b/public/mls.html
index b89374f..e6b8060 100644
--- a/public/mls.html
+++ b/public/mls.html
@@ -64,6 +64,11 @@
.minibtn{background:var(--card);color:var(--mut);border:1px solid var(--line);border-radius:7px;padding:4px 9px;font-size:11px;cursor:pointer;margin-right:5px;} .minibtn:hover{border-color:var(--blue);color:var(--ink);}
.wrap{flex:1 1 auto;min-width:0;}
@media(max-width:820px){ .rail{display:none;} }
+ .rail .rsec > h4 { cursor:pointer; display:flex; align-items:center; justify-content:space-between; user-select:none; }
+ .rail .rsec > h4::after { content:'▾'; color:var(--mut); font-size:11px; margin-left:8px; transition:transform .15s; }
+ .rail .rsec.collapsed > h4::after { transform:rotate(-90deg); }
+ .rail .rsec.collapsed > *:not(h4) { display:none !important; }
+ .mlsempty { padding:60px 20px; text-align:center; color:var(--mut); }
</style>
</head>
<body>
@@ -79,7 +84,7 @@
<div class="shell">
<aside class="rail">
<div class="rsec"><h4>Asset class</h4><div class="chips" id="fType"></div>
- <div style="margin-top:9px;display:flex;flex-wrap:wrap;gap:6px"><span class="chip" id="unwChip" data-warr="unwarrantable" style="border-color:rgba(248,81,73,.4);color:var(--bad)" title="Filter the table to non-FHA-approved condos (Arcstone non-QM target)">⚑ Unwarrantable condos</span><a class="navchip" href="/condos" title="Full condo drill-down page">condos page →</a></div></div>
+ <div style="margin-top:9px;display:flex;flex-wrap:wrap;gap:6px"><span class="chip" id="nqChip" data-warr="nonqm" style="border-color:rgba(188,140,255,.5);color:#bc8cff" title="Arcstone non-QM target: unwarrantable condos + small multifamily (1–9u) + mixed-use">◆ Non-QM</span><span class="chip" id="unwChip" data-warr="unwarrantable" style="border-color:rgba(248,81,73,.4);color:var(--bad)" title="Filter the table to non-FHA-approved condos (Arcstone non-QM target)">⚑ Unwarrantable condos</span><a class="navchip" href="/condos" title="Full condo drill-down page">condos page →</a></div></div>
<div class="rsec"><h4>Status</h4><div class="chips" id="fStatus"></div></div>
<div class="rsec"><h4>Submarket / city</h4><div class="chips" id="fCity"></div></div>
<div class="rsec"><h4>Price</h4><div class="row2"><div class="fld"><label>Min $</label><input type="number" id="pMin" placeholder="any" step="50000"></div><div class="fld"><label>Max $</label><input type="number" id="pMax" placeholder="any" step="50000"></div></div></div>
@@ -121,7 +126,7 @@ const COLS=[
{k:'source',l:'Source',t:'link'},
];
let DATA=[], view='table', sortKey='rank', sortDir=1, q='';
-const F={types:new Set(),cities:new Set(),status:new Set(),pmin:null,pmax:null,unwarrantable:false};
+const F={types:new Set(),cities:new Set(),status:new Set(),pmin:null,pmax:null,unwarrantable:false,nonqm:false};
function mapCondo(c){ return { id:c.id, address:c.address, city:c.city, zip:c.zip, type:'Condo', price:+c.price||0, units:1, cap_rate:null, year_built:c.year_built||null, status:'Active', source:c.source, firm:c.firm_name||null, beds:c.beds, baths:c.baths, sqft:c.sqft, hoa:c.hoa, warrantable_status:c.warrantable_status, broker_name:c.broker_name, project_name:c.project_name }; }
let VISCOL=(function(){let s={};try{s=JSON.parse(localStorage.getItem('mlsCols')||'{}');}catch(e){}return s;})();
function colVis(k){ if(k in VISCOL) return !!VISCOL[k]; const c=COLS.find(x=>x.k===k); return c?c.def!==0:true; }
@@ -148,6 +153,11 @@ function buildRail(){
buildFieldToggles();
}
function buildFieldToggles(){ $('#fFields').innerHTML=COLS.map(c=>`<label class="ftog"><input type="checkbox" data-ck="${c.k}" ${colVis(c.k)?'checked':''}><span>${esc(c.l)}</span></label>`).join(''); }
+function isNonQM(r){ const t=(r.type||'').toLowerCase();
+ if(r.type==='Condo' && r.warrantable_status && r.warrantable_status!=='fha_approved') return true;
+ if(/multi/.test(t) && r.units>=1 && r.units<=9) return true;
+ if(/mixed/.test(t)) return true; return false; }
+function anySelection(){ return F.types.size||F.cities.size||F.status.size||F.pmin!=null||F.pmax!=null||F.unwarrantable||F.nonqm||(q&&q.length); }
function passFacets(r){
if(F.types.size && !F.types.has(r.type)) return false;
if(F.cities.size && !F.cities.has(r.city)) return false;
@@ -155,6 +165,7 @@ function passFacets(r){
if(F.pmin!=null && !(+r.price>=F.pmin)) return false;
if(F.pmax!=null && !(+r.price<=F.pmax)) return false;
if(F.unwarrantable && !(r.type==='Condo' && r.warrantable_status && r.warrantable_status!=='fha_approved')) return false;
+ if(F.nonqm && !isNonQM(r)) return false;
return true;
}
const cval=(r,c)=>c.calc?c.calc(r):r[c.k];
@@ -180,6 +191,12 @@ function filtered(){
return rows;
}
function render(){
+ if(!anySelection()){
+ $('#count').textContent=`${DATA.length} loaded · pick a filter or search to view`;
+ $('#tableView').classList.add('hide'); $('#gridView').classList.remove('hide');
+ $('#gridView').className='grid'; $('#gridView').innerHTML=`<div class="mlsempty">👈 Expand a filter tab or search to begin.<br><span style="font-size:12px">${DATA.length} listings + condos loaded. Pick <b>Asset class</b> (◆ Non-QM · ⚑ Unwarrantable · Condo), a city, status, or a price range.</span></div>`;
+ return;
+ }
const rows=filtered(); const cols=visCols();
$('#count').textContent=`${rows.length} of ${DATA.length} properties`;
// table head
@@ -238,6 +255,12 @@ $('#fAll').addEventListener('click',()=>{COLS.forEach(c=>VISCOL[c.k]=true);saveC
$('#fNone').addEventListener('click',()=>{COLS.forEach(c=>VISCOL[c.k]=false);saveCols();buildFieldToggles();render();});
const unwEl=document.getElementById('unwChip');
if(unwEl) unwEl.addEventListener('click',()=>{ F.unwarrantable=!F.unwarrantable; unwEl.classList.toggle('active',F.unwarrantable); if(F.unwarrantable){ F.types=new Set(['Condo']); document.querySelectorAll('#fType .chip').forEach(x=>x.classList.toggle('active',x.dataset.ty==='Condo')); } render(); });
+const nqEl=document.getElementById('nqChip');
+if(nqEl) nqEl.addEventListener('click',()=>{ F.nonqm=!F.nonqm; nqEl.classList.toggle('active',F.nonqm); render(); });
+// collapsible rail tabs — all collapsed on load
+const railEl=document.querySelector('.rail');
+if(railEl){ railEl.addEventListener('click',e=>{ const h=e.target.closest('h4'); if(h && h.parentElement.classList.contains('rsec')) h.parentElement.classList.toggle('collapsed'); });
+ document.querySelectorAll('.rail .rsec').forEach(s=>s.classList.add('collapsed')); }
fetch('/data/ranked.json').then(r=>r.json()).then(async d=>{ DATA=(d.ranked||d)||[];
try{ const cd=await (await fetch('/api/condos')).json(); DATA=DATA.concat((cd.condos||[]).map(mapCondo)); }catch(e){}
autoCols(); buildRail(); render(); })
← 12bcd30 feat(index): click-to-sort on List column headers (Option C)
·
back to Commercialrealestate
·
feat(index): ⬇ CSV export of the current filtered+sorted vie c919ae1 →