[object Object]

← back to Commercialrealestate

TK-10703 cycle7: property → always-on listing-agent contact strip (address-join, non-fabricated)

1390e7a7cc7a95aaaee414c2033d5bdf3b537faa · 2026-08-21 06:52:31 -0700 · Steve Abrams

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

Files touched

Diff

commit 1390e7a7cc7a95aaaee414c2033d5bdf3b537faa
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Aug 21 06:52:31 2026 -0700

    TK-10703 cycle7: property → always-on listing-agent contact strip (address-join, non-fabricated)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 public/property.html | 40 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 40 insertions(+)

diff --git a/public/property.html b/public/property.html
index f87e3c5..f2f681a 100644
--- a/public/property.html
+++ b/public/property.html
@@ -50,6 +50,45 @@ const $=s=>document.querySelector(s), esc=s=>String(s??'').replace(/[&<>"]/g,c=>
 async function api(u){const r=await fetch(u,{headers:{'Accept':'application/json'}});if(!r.ok)throw new Error('HTTP '+r.status);return r.json();}
 let _seq=0;   // guards a stale slow response from overwriting a newer lookup
 
+// ── listing-agent contact join (Steve HARD RULE: always show the agent's phone + email when a
+//    real listing agent exists for this address). NON-FABRICATION: the ONLY source is
+//    /api/listing-brokers → rows[]{address,name,phone,email,website}, joined on the NORMALIZED
+//    address (authoritative). No address match → source/DRE fallback link, NEVER an invented number.
+const na=s=>String(s||'').toLowerCase().replace(/\b(street|st|avenue|ave|boulevard|blvd|road|rd|drive|dr|lane|ln|place|pl|court|ct|suite|ste|unit|#|north|south|east|west|n|s|e|w)\b/g,'').replace(/[^a-z0-9]+/g,'');
+const cleanv=v=>{const s=(v==null?'':String(v)).trim();return(!s||/^null/i.test(s))?null:s;};
+const weburl=w=>{w=cleanv(w);return w?(/^https?:\/\//i.test(w)?w:'http://'+w):null;};
+const ADDRC={};   // normalized address -> {name,phone,email,website}
+window.ADDRC=ADDRC; window.na=na;   // exposed for the TK-10703 verification harness (read-only hooks)
+(async function loadBrokers(){
+  try{
+    const lb=await api('/api/listing-brokers');
+    ((lb&&lb.rows)||[]).forEach(row=>{
+      const k=na(row.address); if(!k)return;
+      const ph=cleanv(row.phone),em=cleanv(row.email);
+      if(!ADDRC[k] || ((ph||em) && !(ADDRC[k].phone||ADDRC[k].email)))
+        ADDRC[k]={name:cleanv(row.name),phone:ph,email:em,website:weburl(row.website)};
+    });
+  }catch(e){ /* brokers unavailable — dossier still renders, strip falls back to source link */ }
+})();
+// Build the always-on contact strip for a rendered property (never fabricates).
+function contactStrip(p){
+  const b=ADDRC[na(p&&p.address)];
+  if(b && (b.phone||b.email)){
+    const ph=b.phone?`<a href="tel:${esc(b.phone.replace(/[^0-9+]/g,''))}">📞 ${esc(b.phone)}</a>`:'<span class="mut">phone via source ↗</span>';
+    const em=b.email?`<a href="mailto:${esc(b.email)}">✉️ ${esc(b.email)}</a>`:'<span class="mut">email via source ↗</span>';
+    const web=b.website?` · <a href="${esc(b.website)}" target="_blank" rel="noopener noreferrer">🌐 site</a>`:'';
+    return `<div style="margin-top:14px;padding-top:12px;border-top:1px solid var(--line)">`
+      +`<div style="font-weight:600;margin-bottom:6px">Listing agent${b.name?` · ${esc(b.name)}`:''}</div>`
+      +`<div style="display:flex;gap:14px;flex-wrap:wrap;font-size:13px">${ph} ${em}${web}</div></div>`;
+  }
+  // No confirmed listing agent for this address — DRE / source fallback, no invented contact.
+  const q=encodeURIComponent(p&&p.address||'');
+  return `<div style="margin-top:14px;padding-top:12px;border-top:1px solid var(--line)">`
+    +`<div style="font-weight:600;margin-bottom:4px">Listing agent</div>`
+    +`<div class="mut" style="font-size:12px">No confirmed listing agent on record for this address. `
+    +`<a href="https://www.dre.ca.gov/PublicASP/pplinfo.asp" target="_blank" rel="noopener noreferrer">CA DRE license lookup ↗</a></div></div>`;
+}
+
 // Render a dossier object exactly like deals-flow.html's renderDossier (single source of truth for the shape).
 function renderDossier(d,fallbackTitle){
   const p=d.parcel||{};
@@ -67,6 +106,7 @@ function renderDossier(d,fallbackTitle){
     if(d.ula_note)html+=`<div class="mut" style="font-size:11px;margin-top:4px">${esc(d.ula_note)}</div>`;
   }
   if(d.deed&&!d.deed.available){ html+=`<div style="margin-top:14px;font-weight:600">Buyer / seller (recorded deed)</div><div class="mut" style="font-size:11.5px">${esc(d.deed.reason)}</div>`; }
+  html+=contactStrip(p);   // always-on listing-agent phone+email (address-join, non-fabricated)
   html+=`<div class="mut" style="font-size:10.5px;margin-top:12px">Sources: ${esc((d.sources||[]).join(' · '))}</div></div>`;
   $('#out').innerHTML=html;
 }

← 184e5b1 TK-10703 cycle7: recities → corner-nav + day/night theme (ac  ·  back to Commercialrealestate  ·  TK-10703 cycle7: direct-listings → CRCP standard (grid-contr cc4510a →