← 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 →