[object Object]

← back to Commercialrealestate

chore: lint, refactor, v0.9.0 (session close)

87cbbdedbf790617eb322ec3344f6803022fe251 · 2026-07-13 09:56:31 -0700 · Steve Abrams

- email-hunt.js: rename blockedOrEmpty→shouldEscalate + clearer comment (no behavior change)
- serve.js: find-email timeout message now reflects the real cap (45s/75s) instead of hardcoded 45s
- crcp.html: add esc() helper + apply in webLink() (XSS-harden the site-name link)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Files touched

Diff

commit 87cbbdedbf790617eb322ec3344f6803022fe251
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jul 13 09:56:31 2026 -0700

    chore: lint, refactor, v0.9.0 (session close)
    
    - email-hunt.js: rename blockedOrEmpty→shouldEscalate + clearer comment (no behavior change)
    - serve.js: find-email timeout message now reflects the real cap (45s/75s) instead of hardcoded 45s
    - crcp.html: add esc() helper + apply in webLink() (XSS-harden the site-name link)
    
    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
 package.json              | 2 +-
 public/crcp.html          | 3 ++-
 scripts/lib/email-hunt.js | 9 +++++----
 scripts/serve.js          | 2 +-
 4 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/package.json b/package.json
index 858c974..60cfa8b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "commercialrealestate",
-  "version": "0.8.0",
+  "version": "0.9.0",
   "private": true,
   "description": "LA County CRE investment explorer — multi-firm sourced, Census + assessor enriched, Qwen-analyzed",
   "scripts": {
diff --git a/public/crcp.html b/public/crcp.html
index 50560b8..bcfd553 100644
--- a/public/crcp.html
+++ b/public/crcp.html
@@ -229,7 +229,8 @@ function srt(arr,col,dir){ return arr.slice().sort((a,b)=>{ let x=a[col],y=b[col
 function markHdr(tbl,sort){ document.querySelectorAll('#'+tbl+' th.srt').forEach(th=>{ th.classList.remove('asc','desc'); if(th.dataset.col===sort.col) th.classList.add(sort.dir>0?'asc':'desc'); }); }
 // Web column shows the SITE NAME (domain), not a bare "site↗" — Steve 2026-07-13.
 const siteName=u=>{ try{ return new URL(/^https?:/.test(u)?u:'https://'+u).hostname.replace(/^www\./,''); }catch(e){ return u; } };
-const webLink=u=>{ if(!u) return '<span class="miss">—</span>'; const h=/^https?:/.test(u)?u:'https://'+u; return `<a href="${h}" target="_blank" rel="noopener noreferrer" style="color:var(--gold)" title="${h}">${siteName(u)}↗</a>`; };
+const esc=s=>(s==null?'':String(s)).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/"/g,'&quot;');
+const webLink=u=>{ if(!u) return '<span class="miss">—</span>'; const h=/^https?:/.test(u)?u:'https://'+u; return `<a href="${esc(h)}" target="_blank" rel="noopener noreferrer" style="color:var(--gold)" title="${esc(h)}">${esc(siteName(u))}↗</a>`; };
 // On-demand email hunt (the "✉ find email" button). hunt[] survives the 3s poll re-renders;
 // a found email also lands in broker.email server-side, so the next poll carries it natively.
 const hunt={};
diff --git a/scripts/lib/email-hunt.js b/scripts/lib/email-hunt.js
index 4003b76..bd55bf0 100644
--- a/scripts/lib/email-hunt.js
+++ b/scripts/lib/email-hunt.js
@@ -158,10 +158,11 @@ async function huntEmails(b, opts = {}) {
     }
   }
   let { email, basis, pool } = pickBest([...mailtos], [...texts], dom, b.name);
-  // Escalate to the real browser only when plain fetch struck out (blocked or JS-rendered) AND we
-  // don't already have a confident hit. Renders JS + carries a genuine browser fingerprint.
-  const blockedOrEmpty = !email && (tried.some(t => t.status === 403 || t.status === 0) || tried.every(t => t.status >= 200));
-  if (opts.browserHtml && blockedOrEmpty) {
+  // Escalate to the real browser when no email found: either the site blocked us (403/0) or it
+  // rendered fine but published no email (JS-rendered contact widgets). Renders JS + carries a
+  // genuine browser fingerprint.
+  const shouldEscalate = !email && (tried.some(t => t.status === 403 || t.status === 0) || tried.every(t => t.status >= 200));
+  if (opts.browserHtml && shouldEscalate) {
     const bh = await opts.browserHtml(b.website);
     tried.push({ url: b.website + ' [browser]', status: bh.status, err: bh.err });
     if (bh.ok) {
diff --git a/scripts/serve.js b/scripts/serve.js
index 6cbad78..ee3646b 100644
--- a/scripts/serve.js
+++ b/scripts/serve.js
@@ -874,7 +874,7 @@ app.post('/api/broker/find-email', async (req, res) => {
       site: emailHunt.siteDomain(website),
       reason: r.found ? null
         : r.basis === 'ambiguous-shared-page' ? `shared page — ${(r.all || []).length} other agents' emails, none match ${name}; refused to guess`
-        : r.basis === 'timeout' ? 'site too slow (45s cap)'
+        : r.basis === 'timeout' ? `site too slow (${cap / 1000}s cap)`
         : reachedOk ? 'reached the site but no email is published on it'
         : 'site unreachable/blocked (' + (r.tried || []).map(t => t.status || t.err || '?').join('/') + ')',
       cost: '$0 (local fetch)'

← 96492dc auto-save: 2026-07-13T09:54:06 (1 files) — data/alerts-state  ·  back to Commercialrealestate  ·  data: CRCP listings sweep +2 (palm-beach) from launchd kicks 701f6cd →