[object Object]

← back to Rentv 2026

rentv Desk: add New Homes scope (homesonspec spec-home inventory)

482ee13d4690572734eb597f56a35066d7bd00fc · 2026-07-29 08:28:30 -0700 · Steve Abrams

New left-rail scope wired to /api/homes/* (52k new-construction homes): master-detail
directory with price/beds/sqft cards, New Construction detail grid (builder, community,
completion), listing/map/streetview/builder click-outs, and city search via ?city=.
Verified end-to-end in Chromium (rows + detail render, no page errors). crcp Commercial
tab pending its cred injection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 482ee13d4690572734eb597f56a35066d7bd00fc
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Jul 29 08:28:30 2026 -0700

    rentv Desk: add New Homes scope (homesonspec spec-home inventory)
    
    New left-rail scope wired to /api/homes/* (52k new-construction homes): master-detail
    directory with price/beds/sqft cards, New Construction detail grid (builder, community,
    completion), listing/map/streetview/builder click-outs, and city search via ?city=.
    Verified end-to-end in Chromium (rows + detail render, no page errors). crcp Commercial
    tab pending its cred injection.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 public/desk.html | 31 +++++++++++++++++++++----------
 1 file changed, 21 insertions(+), 10 deletions(-)

diff --git a/public/desk.html b/public/desk.html
index c240d58e..6a1537b0 100644
--- a/public/desk.html
+++ b/public/desk.html
@@ -80,6 +80,7 @@
     <button data-s="firms" class="scope"><span class="i">🏢</span>Firms <span class="c" id="cF">0</span></button>
     <button data-s="owners" class="scope"><span class="i">🏛️</span>Owners <span class="c" id="cO">0</span></button>
     <button data-s="sublease" class="scope"><span class="i">🔑</span>Sublease <span class="c" id="cS">0</span></button>
+    <button data-s="homes" class="scope"><span class="i">🏗️</span>New Homes <span class="c" id="cH">0</span></button>
     <div class="fresh" id="fresh"></div>
   </nav>
   <div class="content">
@@ -108,10 +109,17 @@ function actions(k,x){
     x.broker_phone?A('tel:'+String(x.broker_phone).replace(/[^\d+]/g,''),'📞','Call'):'',
     x.sublandlord?A(GOOG('"'+x.sublandlord+'" '+(x.city||'')+' lease'),'🔎','Sublandlord'):''
   ].filter(Boolean).join('');}
+  if(k==='homes'){const ad=[x.street,x.city,x.state].filter(Boolean).join(', ');return [
+    (x.sourceUrl&&/^https?:\/\//i.test(x.sourceUrl))?A(x.sourceUrl,'🔗','Listing'):'',
+    A(MAPS(ad),'📍','Map'),
+    A('https://www.google.com/maps/@?api=1&map_action=pano&viewpoint='+enc(ad),'📸','Street View'),
+    (x.builder&&x.builder.name)?A(GOOG(x.builder.name+' new homes '+(x.city||'')),'🏗️','Builder'):'',
+    A(GOOG(ad+' new construction home'),'🔎','Google')
+  ].filter(Boolean).join('');}
   return [A(LI_P(x.name,x.firm_name),'👤','LinkedIn'),A(GOOG(x.name+' '+(x.firm_name||'')+' '+loc(x)),'🔎','Google'),x.firm_name?A(WEB(x.firm_name),'🌐','Firm site'):'',x.firm_name?A(LI_C(x.firm_name),'🏢','Firm'):'',loc(x)?A(MAPS((x.firm_name||x.name)+' '+loc(x)),'📍','Map'):'',x.license_no?A(DRE(x.license_no),'🪪','DRE'):''].filter(Boolean).join('');
 }
 const subRate=x=>x.asking_rate?('$'+Number(x.asking_rate).toFixed(2)+'/'+(x.rate_period||'yr')+' SF'):'Rate on request';
-const nm=x=>x.name||x.address||x.ain, sub=(k,x)=>k==='brokers'?(x.firm_name||'—'):k==='firms'?((x.agent_count||0).toLocaleString()+' agents'):k==='sublease'?[x.sqft?Number(x.sqft).toLocaleString()+' SF':null,subRate(x),x.submarket||x.city].filter(Boolean).join(' · '):(x.type_label||'');
+const nm=x=>x.name||x.address||x.street||x.ain, sub=(k,x)=>k==='brokers'?(x.firm_name||'—'):k==='firms'?((x.agent_count||0).toLocaleString()+' agents'):k==='sublease'?[x.sqft?Number(x.sqft).toLocaleString()+' SF':null,subRate(x),x.submarket||x.city].filter(Boolean).join(' · '):k==='homes'?[x.price?shortUsd(x.price):null,x.beds?x.beds+'bd':null,x.sqft?Number(x.sqft).toLocaleString()+' sf':null,[x.city,x.state].filter(Boolean).join(', ')].filter(Boolean).join(' · '):(x.type_label||'');
 let D={scope:'brokers',view:'directory',selDir:0,selMap:0};
 // chrome
 $('burger').onclick=()=>{$('scrim').classList.add('open');$('drawer').classList.add('open');};
@@ -119,18 +127,20 @@ const cd=()=>{$('scrim').classList.remove('open');$('drawer').classList.remove('
 $('user').onclick=()=>{$('scrim').classList.add('open');$('drawer').classList.add('open');};
 async function jget(u,fb){try{const r=await fetch(u,{credentials:'same-origin'});if(!r.ok)return fb;return await r.json();}catch(e){return fb;}}
 (async()=>{
-  const [b,f,la,sl]=await Promise.all([
+  const [b,f,la,sl,hm]=await Promise.all([
     jget('/api/brokers?state=CA&limit=500',null).then(v=>(v&&v.rows&&v.rows.length)?v:jget('desk-assets/data/brokers.json',{rows:[]})),
     jget('/api/firms?state=CA&limit=200',null).then(v=>(v&&v.rows&&v.rows.length)?v:jget('desk-assets/data/firms.json',{rows:[]})),
     jget('/api/la-commercial',null).then(v=>v||jget('desk-assets/data/la-commercial.json',{marquee:[]})),
-    jget('/api/sublease?limit=500',{rows:[],total:0})]);
+    jget('/api/sublease?limit=500',{rows:[],total:0}),
+    jget('/api/homes/search?limit=300',{homes:[],total:0})]);
   D.brokers=b.rows||[];D.firms=f.rows||[];D.owners=la.marquee||[];D.subleases=(sl&&sl.rows)||[];
+  D.homes=(hm&&hm.homes)||[];D.totalH=(hm&&hm.total)||D.homes.length;
   D.totalB=b.total||D.brokers.length;D.totalF=f.total||D.firms.length;D.totalS=(sl&&sl.total)||D.subleases.length;
-  $('cB').textContent=D.brokers.length;$('cF').textContent=D.firms.length;$('cO').textContent=D.owners.length;$('cS').textContent=D.subleases.length;
+  $('cB').textContent=D.brokers.length;$('cF').textContent=D.firms.length;$('cO').textContent=D.owners.length;$('cS').textContent=D.subleases.length;$('cH').textContent=D.homes.length;
   $('fresh').textContent='CA DRE registry · '+D.brokers.length+' brokers of '+D.totalB.toLocaleString()+' statewide';
   renderAll();
 })();
-function scopeData(){return D.scope==='brokers'?D.brokers:D.scope==='firms'?D.firms:D.scope==='sublease'?D.subleases:D.owners;}
+function scopeData(){return D.scope==='brokers'?D.brokers:D.scope==='firms'?D.firms:D.scope==='sublease'?D.subleases:D.scope==='homes'?D.homes:D.owners;}
 const debounce=(fn,ms)=>{let t;return(...a)=>{clearTimeout(t);t=setTimeout(()=>fn(...a),ms);};};
 // live server-side search across the FULL registry (382k brokers / 34k firms) via the proxy;
 // owners are only 12 so they filter client-side; empty term returns the loaded set.
@@ -139,6 +149,7 @@ async function liveRows(scope,term){
   if(!term) return scopeData();
   if(scope==='owners') return D.owners.filter(x=>JSON.stringify(x).toLowerCase().includes(term.toLowerCase()));
   if(scope==='sublease'){const v=await jget('/api/sublease?limit=200&q='+encodeURIComponent(term),null);if(v&&v.rows)return v.rows;return D.subleases.filter(x=>JSON.stringify(x).toLowerCase().includes(term.toLowerCase()));}
+  if(scope==='homes'){const v=await jget('/api/homes/search?limit=200&city='+encodeURIComponent(term),null);if(v&&v.homes&&v.homes.length)return v.homes;return D.homes.filter(x=>JSON.stringify(x).toLowerCase().includes(term.toLowerCase()));}
   const ep=scope==='firms'?'/api/firms':'/api/brokers';
   const v=await jget(ep+'?state=CA&q='+encodeURIComponent(term)+'&limit=100',null);
   if(v&&v.rows&&v.rows.length) return v.rows;
@@ -147,7 +158,7 @@ async function liveRows(scope,term){
 // rail
 // Views are contextual to the scope — a scope only exposes the views that actually render ITS data,
 // so you can never land on a mismatched combo (e.g. Sublease + a firm-only chart).
-const SCOPE_VIEWS={brokers:['directory','analytics'],firms:['directory','analytics'],owners:['directory'],sublease:['directory']};
+const SCOPE_VIEWS={brokers:['directory','analytics'],firms:['directory','analytics'],owners:['directory'],sublease:['directory'],homes:['directory']};
 function applyScopeViews(){
   const ok=SCOPE_VIEWS[D.scope]||['directory'];
   document.querySelectorAll('#rail [data-v]').forEach(b=>{const good=ok.includes(b.dataset.v);b.disabled=!good;b.style.opacity=good?'':'.32';b.style.cursor=good?'':'not-allowed';});
@@ -166,15 +177,15 @@ function directory(){
   $('v-directory').innerHTML=`<div class="md"><div class="list"><div class="search"><input id="dq" placeholder="Search ${D.scope}…"><div class="fresh" id="dcount" style="padding:7px 2px 0"></div></div><div class="rows" id="drows"></div></div><div class="detail" id="ddetail"></div></div>`;
   function list(){let r=rows.slice();const v=($('dq').value||'').toLowerCase();if(v)r=r.filter(x=>JSON.stringify(x).toLowerCase().includes(v));return r;}
   function detail(x){if(!x){$('ddetail').innerHTML='';return;}
-    const grid=D.scope==='sublease'?[['SqFt',x.sqft?Number(x.sqft).toLocaleString():'—'],['Asking',subRate(x)],['Term',x.term_months?x.term_months+' mo':'—'],['Available',x.available_date?dstr(x.available_date):'Now'],['Sublandlord',x.sublandlord||'—'],['Listing broker',x.firm_name||'—']]:D.scope==='owners'?[['Assessed',shortUsd(x.assessed_total)],['SqFt',(x.sqft||0).toLocaleString()],['Built',x.year_built||'—'],['AIN',x.ain]]:D.scope==='firms'?[['Agents',(x.agent_count||0).toLocaleString()],['HQ',loc(x)||'—'],['License',x.license_no||'—'],['Source',x.source||'—']]:[['License',x.license_no||'—'],['Type',x.license_type||'—'],['Firm',x.firm_name||'—'],['City',x.city||'—']];
-    const dk=D.scope==='sublease'?(x.lease_type==='direct'?'Direct Lease':'Sublease Available'):D.scope==='owners'?(x.type_label||'Asset'):D.scope==='firms'?'Brokerage':(x.license_type||'Licensee');
-    const dmeta=D.scope==='sublease'?[x.floor_suite,x.submarket,x.city].filter(Boolean).join(' · '):D.scope==='owners'?[x.address,x.city].filter(Boolean).join(', '):(sub(D.scope,x)+' · '+(loc(x)||''));
+    const grid=D.scope==='sublease'?[['SqFt',x.sqft?Number(x.sqft).toLocaleString():'—'],['Asking',subRate(x)],['Term',x.term_months?x.term_months+' mo':'—'],['Available',x.available_date?dstr(x.available_date):'Now'],['Sublandlord',x.sublandlord||'—'],['Listing broker',x.firm_name||'—']]:D.scope==='owners'?[['Assessed',shortUsd(x.assessed_total)],['SqFt',(x.sqft||0).toLocaleString()],['Built',x.year_built||'—'],['AIN',x.ain]]:D.scope==='firms'?[['Agents',(x.agent_count||0).toLocaleString()],['HQ',loc(x)||'—'],['License',x.license_no||'—'],['Source',x.source||'—']]:D.scope==='homes'?[['Price',x.price?shortUsd(x.price):'—'],['Beds / Baths',(x.beds||'—')+' / '+(x.bathsTotal||'—')],['SqFt',(x.sqft||0).toLocaleString()],['Type',x.homeType||'—'],['Status',x.constructionStatus||x.status||'—'],['Builder',(x.builder&&x.builder.name)||'—'],['Community',(x.community&&x.community.name)||'—'],['Completion',x.estCompletionDate?dstr(x.estCompletionDate):'—']]:[['License',x.license_no||'—'],['Type',x.license_type||'—'],['Firm',x.firm_name||'—'],['City',x.city||'—']];
+    const dk=D.scope==='sublease'?(x.lease_type==='direct'?'Direct Lease':'Sublease Available'):D.scope==='owners'?(x.type_label||'Asset'):D.scope==='firms'?'Brokerage':D.scope==='homes'?'New Construction':(x.license_type||'Licensee');
+    const dmeta=D.scope==='sublease'?[x.floor_suite,x.submarket,x.city].filter(Boolean).join(' · '):D.scope==='owners'?[x.address,x.city].filter(Boolean).join(', '):D.scope==='homes'?[x.street,x.city,x.state,x.zip].filter(Boolean).join(', '):(sub(D.scope,x)+' · '+(loc(x)||''));
     $('ddetail').innerHTML=`<div class="k">${esc(dk)}</div><h2>${esc(nm(x))}</h2><div class="meta">${esc(dmeta)}</div><div class="acts">${actions(D.scope,x)}</div><div class="grid">${grid.map(([k,v])=>`<div class="cell"><div class="kk">${k}</div><div class="vv">${esc(v)}</div></div>`).join('')}</div>`;}
   const emptyMsg=D.scope==='sublease'
     ? '<div class="fresh" style="padding:20px 16px;line-height:1.6">No sublease listings yet.<br>Inventory is entered by the RENTV desk from live broker deals — <a class="field-link" href="/desk-admin">add a listing →</a></div>'
     : '<div class="fresh">No matches in the registry.</div>';
   async function draw(){const r=await liveRows(D.scope,$('dq').value);
-    const tot=D.scope==='brokers'?D.totalB:D.scope==='firms'?D.totalF:D.scope==='sublease'?D.totalS:D.owners.length;
+    const tot=D.scope==='brokers'?D.totalB:D.scope==='firms'?D.totalF:D.scope==='sublease'?D.totalS:D.scope==='homes'?D.totalH:D.owners.length;
     const term=($('dq').value||'').trim();
     $('dcount').textContent=r.length?(term?`${r.length} match${r.length===1?'':'es'}`:`Showing ${r.length.toLocaleString()} of ${Number(tot).toLocaleString()}`):'';
     $('drows').innerHTML=r.map((x,i)=>`<div class="row${i===D.selDir?' sel':''}" data-i="${i}"><div class="nm">${esc(nm(x))}</div><div class="f">${esc(sub(D.scope,x))}</div></div>`).join('')||emptyMsg;

← 3c12ff4c rentv: proxy homesonspec + crcp backends into the admin Desk  ·  back to Rentv 2026  ·  Master Reel + Walkthrough updated with deep growth section; 116662d3 →