[object Object]

← back to Rentv

Audience perf: precompute per-contact search index once on load (was JSON.stringify over 2,162 contacts per keystroke) — Cody's queued follow-up

fc57490b94e73382b7386f94e59aadeb7080d72e · 2026-08-05 13:53:57 -0700 · Steve Abrams

Files touched

Diff

commit fc57490b94e73382b7386f94e59aadeb7080d72e
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Aug 5 13:53:57 2026 -0700

    Audience perf: precompute per-contact search index once on load (was JSON.stringify over 2,162 contacts per keystroke) — Cody's queued follow-up
---
 public/audience.html | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/public/audience.html b/public/audience.html
index 86719e1f..e75b6d42 100644
--- a/public/audience.html
+++ b/public/audience.html
@@ -132,6 +132,9 @@ $('user').onclick=()=>location.href='/';
 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()=>{
   A_.data=await jget('/api/audience',{stats:{},segments:[],contacts:[]});
+  // Precompute a lean lowercase search string per contact ONCE (was JSON.stringify per keystroke
+  // over 2,162 contacts — a real perf drag; Cody-flagged). Search now filters on this index.
+  (A_.data.contacts||[]).forEach(c=>{c._q=[c.name,c.email,c.phone,c.firm,c.title,c.interest,c.persona,c.source,(c.segments||[]).join(' ')].filter(Boolean).join(' ').toLowerCase();});
   const st=A_.data.stats||{};
   $('cP').textContent=A_.data.contacts.length;$('cS').textContent=A_.data.segments.length;$('cC').textContent=A_.data.contacts.length;
   $('fresh').textContent=(st.crm||0).toLocaleString()+' CRM contacts · '+(st.newsletter||0)+' newsletter · '+(st.subleaseLinked||0)+' sublease';
@@ -156,7 +159,7 @@ function filteredContacts(term){
   let r=A_.data.contacts.slice();
   if(A_.filterSeg)r=r.filter(c=>c.segments.includes(A_.filterSeg));
   const v=(term||'').trim().toLowerCase();
-  if(v)r=r.filter(c=>JSON.stringify(c).toLowerCase().includes(v));
+  if(v)r=r.filter(c=>(c._q||"").includes(v));
   return sortContacts(r);
 }
 // ── 1. Clients & Prospects (master-detail) ──
@@ -253,7 +256,7 @@ function profiles(){
       <div class="h3">Timeline</div><div class="pill">🕓 Added ${esc(fmtWhen(c.created_at))}</div>`;
     $('cdetail').querySelectorAll('.seg-chip').forEach(ch=>ch.onclick=()=>{A_.filterSeg=ch.dataset.k;A_.selP=0;switchView('prospects');prospects();});
   }
-  function draw(){let r=all;const v=($('cq').value||'').trim().toLowerCase();if(v)r=all.filter(c=>JSON.stringify(c).toLowerCase().includes(v));
+  function draw(){let r=all;const v=($('cq').value||'').trim().toLowerCase();if(v)r=all.filter(c=>(c._q||"").includes(v));
     $('ccount').textContent=`${r.length} contact${r.length===1?'':'s'}`;
     $('crows').innerHTML=r.map((c,i)=>`<div class="row${c===all[A_.selC]?' sel':''}" data-i="${A_.data.contacts.indexOf(c)}"><div class="nm">${personaEmoji(c)} ${esc(c.name)}</div><div class="f">${esc([c.firm,c.email].filter(Boolean).join(' · ')||'—')}</div></div>`).join('')||'<div class="fresh">No matches.</div>';
     $('crows').querySelectorAll('.row').forEach(row=>row.onclick=()=>{A_.selC=+row.dataset.i;$('crows').querySelectorAll('.row').forEach(x=>x.classList.remove('sel'));row.classList.add('sel');detail(A_.data.contacts[A_.selC]);});

← f44b4fff RENTV closings: merge article corpus into /api/closings (Sal  ·  back to Rentv  ·  search-index: HomesOnSpec LIVE from Kamatera (InventoryHome, 75b7680c →