[object Object]

← back to Commercialrealestate

CRCP: Phase-1 per-city scoping — host-keyed fetch filter + hide-empty hub (index.html untouched)

5cef9aefda7cba4fea34c04863c996d1743751e5 · 2026-08-20 12:54:15 -0700 · Steve Abrams

Files touched

Diff

commit 5cef9aefda7cba4fea34c04863c996d1743751e5
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Aug 20 12:54:15 2026 -0700

    CRCP: Phase-1 per-city scoping — host-keyed fetch filter + hide-empty hub (index.html untouched)
---
 public/crcp-cities.js |  46 ++++++++++++++++
 public/crcp-scope.js  |  73 ++++++++++++++++++++++++++
 public/recities.html  | 142 +++++++++++++++++++++++++++++---------------------
 scripts/serve.js      |  11 +++-
 4 files changed, 211 insertions(+), 61 deletions(-)

diff --git a/public/crcp-cities.js b/public/crcp-cities.js
new file mode 100644
index 0000000..e211b2b
--- /dev/null
+++ b/public/crcp-cities.js
@@ -0,0 +1,46 @@
+/* crcp-cities.js — single source of truth for the *.crcp.agentabrams.com city fleet.
+ * Used by recities.html (the launcher hub) AND crcp-scope.js (per-city grid scoping).
+ * To add/remove a city market: add its subdomain vhost + DNS, then add the slug here. */
+(function () {
+  // Multi-word / special-cased labels; single-word slugs fall back to Title Case.
+  var LABELS = {
+    belair: "Bel-Air", beverlyhills: "Beverly Hills", canyondam: "Canyon Dam", chinesecamp: "Chinese Camp",
+    crescentmills: "Crescent Mills", echolake: "Echo Lake", forestranch: "Forest Ranch", grassvalley: "Grass Valley",
+    grizzlyflats: "Grizzly Flats", klamathriver: "Klamath River", lakeelsinore: "Lake Elsinore", northfork: "North Fork",
+    pacificpalisades: "Pacific Palisades", paynescreek: "Paynes Creek", sanjuancapistrano: "San Juan Capistrano",
+    santamonica: "Santa Monica", shermanoaks: "Sherman Oaks", sierramadre: "Sierra Madre", southlaketahoe: "South Lake Tahoe",
+    studiocity: "Studio City", twinbridges: "Twin Bridges", woffordheights: "Wofford Heights"
+  };
+  var SLUGS = ["altadena", "belair", "beverlyhills", "caliente", "camarillo", "canyondam", "chester", "chico",
+    "chinesecamp", "clearlake", "cohasset", "colfax", "crescentmills", "doyle", "echolake", "encino", "foresthill",
+    "forestranch", "georgetown", "grassvalley", "greenville", "grizzlyflats", "havilah", "hemet", "janesville",
+    "klamathriver", "lakeelsinore", "lakehead", "malibu", "mariposa", "mineral", "northfork", "pacificpalisades",
+    "pasadena", "paynescreek", "redding", "sanjuancapistrano", "santamonica", "shermanoaks", "sierramadre", "somerset",
+    "somis", "southlaketahoe", "studiocity", "topanga", "twinbridges", "ukiah", "weed", "woffordheights", "wrightwood"];
+
+  // Hosts that are NOT a city (serve the app unscoped, or the hub). label-0 of these -> no scope.
+  var RESERVED = { crcp: 1, recities: 1, www: 1, localhost: 1, "127": 1 };
+
+  function title(s) { return s.replace(/(^|\s)\S/g, function (c) { return c.toUpperCase(); }); }
+  function norm(s) { return String(s == null ? "" : s).toLowerCase().replace(/[^a-z]/g, ""); }
+
+  var CITIES = SLUGS.map(function (slug) {
+    var label = LABELS[slug] || title(slug);
+    return { slug: slug, label: label, host: slug + ".crcp.agentabrams.com", norm: norm(label) };
+  });
+  var BY_SLUG = {}; CITIES.forEach(function (c) { BY_SLUG[c.slug] = c; });
+
+  // Resolve the current host to a city object, or null when unscoped (base crcp / recities / ip / localhost).
+  function cityFromHost(host) {
+    var label0 = String(host || "").toLowerCase().split(".")[0];
+    if (!label0 || RESERVED[label0] || /^\d+$/.test(label0)) return null;
+    return BY_SLUG[label0] || null;
+  }
+  // Does a listing's city string belong to this city object?
+  function sameCity(cityStr, cityObj) { return !!cityObj && norm(cityStr) === cityObj.norm; }
+
+  window.CRCP_CITIES = {
+    LABELS: LABELS, SLUGS: SLUGS, CITIES: CITIES, BY_SLUG: BY_SLUG,
+    cityFromHost: cityFromHost, sameCity: sameCity, norm: norm, title: title
+  };
+})();
diff --git a/public/crcp-scope.js b/public/crcp-scope.js
new file mode 100644
index 0000000..bc9186d
--- /dev/null
+++ b/public/crcp-scope.js
@@ -0,0 +1,73 @@
+/* crcp-scope.js — per-city scoping for <city>.crcp.agentabrams.com (Phase 1, front-end only).
+ *
+ * The grid loads a static data/ranked.json (prod is DB-less). To scope a city subdomain WITHOUT
+ * editing the big shared index.html, this script — injected in <head> by serve.js, BEFORE the app's
+ * inline boot — wraps window.fetch: when the app pulls ranked.json (and /api/condos), we filter the
+ * listings to the host's city. The app then renders the scoped set as if the snapshot only held that
+ * city. Unscoped hosts (base crcp., recities., localhost, IPs) are a no-op — the app runs untouched.
+ *
+ * Reversible: removing the two injected <script> tags (serve.js) fully disables this. Guarded: only
+ * URLs containing ranked.json / api/condos are touched; anything else passes straight through.  */
+(function () {
+  if (!window.CRCP_CITIES) return;                          // cities table must load first
+  var CITY = window.CRCP_CITIES.cityFromHost(location.hostname);
+  if (!CITY) return;                                        // base crcp / recities / localhost -> no scope
+
+  var scoped = { ranked: 0, total: 0 };
+  var _fetch = window.fetch;
+  window.fetch = function (input, init) {
+    var url = (typeof input === "string") ? input : (input && input.url) || "";
+    var p = _fetch.apply(this, arguments);
+    if (/ranked\.json/.test(url)) {
+      return p.then(function (r) {
+        return r.clone().json().then(function (d) {
+          if (d && Array.isArray(d.ranked)) {
+            scoped.total = d.ranked.length;
+            d.ranked = d.ranked.filter(function (x) { return window.CRCP_CITIES.sameCity(x.city, CITY); });
+            scoped.ranked = d.ranked.length;
+            if (d.meta) d.meta.market = CITY.label;          // sub-header shows the scoped market name
+          }
+          return new Response(JSON.stringify(d), { status: 200, headers: { "Content-Type": "application/json" } });
+        }).catch(function () { return r; });                 // parse failure -> pass original through
+      });
+    }
+    if (/\/api\/condos/.test(url)) {                         // keep the in-grid condo overlay city-consistent
+      return p.then(function (r) {
+        return r.clone().json().then(function (d) {
+          if (d && Array.isArray(d.condos)) d.condos = d.condos.filter(function (c) { return window.CRCP_CITIES.sameCity(c.city, CITY); });
+          return new Response(JSON.stringify(d), { status: 200, headers: { "Content-Type": "application/json" } });
+        }).catch(function () { return r; });
+      });
+    }
+    return p;
+  };
+
+  // Scope banner + empty-state, injected once the DOM is ready (does not touch app JS).
+  function paintBanner() {
+    if (document.getElementById("crcp-scope-banner")) return;
+    var bar = document.createElement("div");
+    bar.id = "crcp-scope-banner";
+    bar.style.cssText = "position:sticky;top:0;z-index:9999;display:flex;gap:12px;align-items:center;" +
+      "justify-content:center;padding:9px 16px;background:#12305a;color:#eaf1fb;font:13px/1.4 -apple-system," +
+      "BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;border-bottom:1px solid #24487e";
+    var n = scoped.ranked;
+    bar.innerHTML =
+      '<span>Scoped to <b>' + CITY.label + '</b>' + (n ? ' · ' + n + ' listing' + (n === 1 ? '' : 's') : '') + '</span>' +
+      '<a href="https://crcp.agentabrams.com/" style="color:#9ecbff;text-decoration:none;border:1px solid #2f5da3;border-radius:8px;padding:3px 10px">View all markets →</a>' +
+      '<a href="https://recities.crcp.agentabrams.com/" style="color:#9ecbff;text-decoration:none;border:1px solid #2f5da3;border-radius:8px;padding:3px 10px">All cities</a>';
+    document.body.insertBefore(bar, document.body.firstChild);
+
+    if (scoped.ranked === 0 && scoped.total > 0) {          // scoped after data loaded, but city has none
+      var note = document.createElement("div");
+      note.style.cssText = "margin:22px;padding:18px 20px;border:1px solid #24487e;border-radius:12px;" +
+        "background:#0f1b30;color:#c7d5ea;font:14px/1.5 -apple-system,sans-serif;text-align:center";
+      note.innerHTML = "No CRCP listings in <b>" + CITY.label + "</b> yet. " +
+        '<a href="https://crcp.agentabrams.com/" style="color:#9ecbff">Browse all markets →</a>';
+      bar.insertAdjacentElement("afterend", note);
+    }
+  }
+  // ranked.json loads async; re-check the empty-state shortly after boot so the count is accurate.
+  function ready() { paintBanner(); setTimeout(paintBanner, 2500); }
+  if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", ready);
+  else ready();
+})();
diff --git a/public/recities.html b/public/recities.html
index db5f854..61b2eb0 100644
--- a/public/recities.html
+++ b/public/recities.html
@@ -11,36 +11,36 @@
   }
   *{box-sizing:border-box}
   body{margin:0;background:linear-gradient(180deg,#0f1419,#121924);color:var(--ink);
-       font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
-       min-height:100vh}
+       font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;min-height:100vh}
   .wrap{max-width:1080px;margin:0 auto;padding:40px 20px 80px}
   header.top{display:flex;align-items:baseline;gap:14px;flex-wrap:wrap;margin-bottom:6px}
   header.top h1{font-size:26px;font-weight:700;margin:0;letter-spacing:.2px}
   header.top .tag{color:var(--mut);font-size:14px}
   .sub{color:var(--mut);margin:0 0 26px}
-  .panel{background:var(--panel);border:1px solid var(--edge);border-radius:14px;padding:22px;
-         box-shadow:0 8px 30px rgba(0,0,0,.25)}
+  .panel{background:var(--panel);border:1px solid var(--edge);border-radius:14px;padding:22px;box-shadow:0 8px 30px rgba(0,0,0,.25)}
   .pickrow{display:flex;gap:12px;flex-wrap:wrap;align-items:center}
-  label.lbl{color:var(--mut);font-size:13px;text-transform:uppercase;letter-spacing:.6px;
-            display:block;margin-bottom:8px}
+  label.lbl{color:var(--mut);font-size:13px;text-transform:uppercase;letter-spacing:.6px;display:block;margin-bottom:8px}
   select,button,input[type=search]{font:inherit}
-  select#city{flex:1 1 320px;min-width:240px;background:var(--panel2);color:var(--ink);
-    border:1px solid var(--edge);border-radius:10px;padding:13px 14px;appearance:none;
+  select#city{flex:1 1 320px;min-width:240px;background:var(--panel2);color:var(--ink);border:1px solid var(--edge);
+    border-radius:10px;padding:13px 14px;appearance:none;
     background-image:linear-gradient(45deg,transparent 50%,var(--mut) 50%),linear-gradient(135deg,var(--mut) 50%,transparent 50%);
     background-position:calc(100% - 20px) 20px,calc(100% - 14px) 20px;background-size:6px 6px,6px 6px;background-repeat:no-repeat}
-  button.go{background:var(--accent2);color:#fff;border:1px solid var(--accent);border-radius:10px;
-    padding:13px 22px;cursor:pointer;font-weight:600}
+  button.go{background:var(--accent2);color:#fff;border:1px solid var(--accent);border-radius:10px;padding:13px 22px;cursor:pointer;font-weight:600}
   button.go:hover{background:var(--accent)}
-  .count{color:var(--mut);font-size:13px;margin:16px 0 10px}
-  input#filter{width:100%;background:var(--panel2);color:var(--ink);border:1px solid var(--edge);
-    border-radius:10px;padding:12px 14px;margin-top:22px}
-  .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));gap:10px;margin-top:14px}
-  a.city{display:block;text-decoration:none;color:var(--ink);background:var(--panel2);
-    border:1px solid var(--edge);border-radius:10px;padding:12px 14px;transition:.12s}
+  .row2{display:flex;gap:14px;align-items:center;flex-wrap:wrap;margin-top:16px}
+  .count{color:var(--mut);font-size:13px}
+  .toggle{color:var(--mut);font-size:13px;display:flex;gap:7px;align-items:center;cursor:pointer;user-select:none}
+  input#filter{width:100%;background:var(--panel2);color:var(--ink);border:1px solid var(--edge);border-radius:10px;padding:12px 14px;margin-top:22px}
+  .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px;margin-top:14px}
+  a.city{display:flex;justify-content:space-between;align-items:center;gap:10px;text-decoration:none;color:var(--ink);
+    background:var(--panel2);border:1px solid var(--edge);border-radius:10px;padding:12px 14px;transition:.12s}
   a.city:hover{border-color:var(--accent);background:#26313f;transform:translateY(-1px)}
+  a.city.empty{opacity:.5}
   a.city .nm{font-weight:600}
-  a.city .host{display:block;color:var(--mut);font-size:12px;margin-top:2px}
-  .empty{color:var(--mut);padding:20px 4px}
+  a.city .host{display:block;color:var(--mut);font-size:12px;margin-top:2px;font-weight:400}
+  a.city .n{flex:0 0 auto;font-size:12px;color:var(--mut);background:#1a222e;border:1px solid var(--edge);border-radius:20px;padding:2px 9px}
+  a.city .n.has{color:#bfe0ff;border-color:#2f5da3}
+  .empty-note{color:var(--mut);padding:20px 4px}
   footer{color:var(--mut);font-size:12px;margin-top:34px}
 </style>
 </head>
@@ -50,7 +50,7 @@
     <h1>CRCP · Cities</h1>
     <span class="tag">Commercial Real Estate — pick a market</span>
   </header>
-  <p class="sub">Every city market we serve. Choose one to open its dashboard.</p>
+  <p class="sub">Markets with active listings. Choose one to open its scoped dashboard.</p>
 
   <div class="panel">
     <label class="lbl" for="city">Jump to a city</label>
@@ -59,64 +59,86 @@
       <button class="go" id="go">Open →</button>
     </div>
     <input id="filter" type="search" placeholder="Filter cities…" autocomplete="off">
-    <div class="count" id="count"></div>
+    <div class="row2">
+      <span class="count" id="count"></span>
+      <label class="toggle"><input type="checkbox" id="showEmpty"> Show empty markets</label>
+    </div>
     <div class="grid" id="grid"></div>
   </div>
 
   <footer id="foot"></footer>
 </div>
 
+<script src="/crcp-cities.js"></script>
 <script>
-// The set of city markets served under *.crcp.agentabrams.com (each resolves + serves the CRCP app).
-// To add/remove a city: add its subdomain vhost + DNS, then add it here. slug = subdomain label.
-const LABELS = {
-  belair:"Bel-Air", beverlyhills:"Beverly Hills", canyondam:"Canyon Dam", chinesecamp:"Chinese Camp",
-  crescentmills:"Crescent Mills", echolake:"Echo Lake", forestranch:"Forest Ranch", grassvalley:"Grass Valley",
-  grizzlyflats:"Grizzly Flats", klamathriver:"Klamath River", lakeelsinore:"Lake Elsinore", northfork:"North Fork",
-  pacificpalisades:"Pacific Palisades", paynescreek:"Paynes Creek", sanjuancapistrano:"San Juan Capistrano",
-  santamonica:"Santa Monica", shermanoaks:"Sherman Oaks", sierramadre:"Sierra Madre", southlaketahoe:"South Lake Tahoe",
-  studiocity:"Studio City", twinbridges:"Twin Bridges", woffordheights:"Wofford Heights"
-};
-const SLUGS = ["altadena","belair","beverlyhills","caliente","camarillo","canyondam","chester","chico",
- "chinesecamp","clearlake","cohasset","colfax","crescentmills","doyle","echolake","encino","foresthill",
- "forestranch","georgetown","grassvalley","greenville","grizzlyflats","havilah","hemet","janesville",
- "klamathriver","lakeelsinore","lakehead","malibu","mariposa","mineral","northfork","pacificpalisades",
- "pasadena","paynescreek","redding","sanjuancapistrano","santamonica","shermanoaks","sierramadre","somerset",
- "somis","southlaketahoe","studiocity","topanga","twinbridges","ukiah","weed","woffordheights","wrightwood"];
+var CITIES = window.CRCP_CITIES.CITIES.slice();       // from the shared cities module
+var sameCity = window.CRCP_CITIES.sameCity;
+var counts = {};                                      // slug -> listing count (from ranked.json)
+var loaded = false;
+var urlOf = function (c) { return "https://" + c.host + "/"; };
 
-const title = s => s.replace(/(^|\s)\S/g, c => c.toUpperCase());
-const CITIES = SLUGS.map(slug => ({
-  slug, label: LABELS[slug] || title(slug),
-  host: slug + ".crcp.agentabrams.com"
-})).sort((a,b) => a.label.localeCompare(b.label));
-const urlOf = c => "https://" + c.host + "/";
+var sel = document.getElementById('city'), grid = document.getElementById('grid'),
+    count = document.getElementById('count'), filter = document.getElementById('filter'),
+    showEmpty = document.getElementById('showEmpty'), foot = document.getElementById('foot');
 
-const sel = document.getElementById('city');
-CITIES.forEach(c => { const o=document.createElement('option'); o.value=c.slug; o.textContent=c.label; sel.appendChild(o); });
+// Count listings per city from the same snapshot the app uses. Best-effort: if it fails, show all.
+fetch('/data/ranked.json').then(function (r) { return r.json(); }).then(function (d) {
+  var arr = (d && d.ranked) || [];
+  CITIES.forEach(function (c) {
+    counts[c.slug] = arr.filter(function (p) { return sameCity(p.city, c); }).length;
+  });
+  loaded = true; render();
+}).catch(function () { loaded = true; render(); });     // no counts -> treat all as shown
 
-const grid = document.getElementById('grid'), count = document.getElementById('count'),
-      filter = document.getElementById('filter'), foot = document.getElementById('foot');
+function visible() {
+  var withData = CITIES.filter(function (c) { return (counts[c.slug] || 0) > 0; });
+  // Default: hide empty markets (Steve's choice). Toggle reveals all 50. Before counts load, show all.
+  var base = (!loaded || showEmpty.checked || withData.length === 0) ? CITIES : withData;
+  return base.slice().sort(function (a, b) {
+    var na = counts[a.slug] || 0, nb = counts[b.slug] || 0;
+    if (nb !== na) return nb - na;                       // most inventory first
+    return a.label.localeCompare(b.label);
+  });
+}
+
+function fillSelect() {
+  var list = visible(); sel.innerHTML = '';
+  list.forEach(function (c) {
+    var o = document.createElement('option'); o.value = c.slug;
+    o.textContent = c.label + (loaded ? ' (' + (counts[c.slug] || 0) + ')' : '');
+    sel.appendChild(o);
+  });
+}
 
-function draw(q){
-  q = (q||'').trim().toLowerCase();
-  const shown = CITIES.filter(c => !q || c.label.toLowerCase().includes(q) || c.slug.includes(q));
+function render() {
+  fillSelect();
+  var q = (filter.value || '').trim().toLowerCase();
+  var list = visible().filter(function (c) { return !q || c.label.toLowerCase().indexOf(q) >= 0 || c.slug.indexOf(q) >= 0; });
   grid.innerHTML = '';
-  if(!shown.length){ grid.innerHTML = '<div class="empty">No cities match “'+q+'”.</div>'; }
-  shown.forEach(c => {
-    const a = document.createElement('a');
-    a.className='city'; a.href=urlOf(c);
-    a.innerHTML = '<span class="nm">'+c.label+'</span><span class="host">'+c.host+'</span>';
+  if (!list.length) { grid.innerHTML = '<div class="empty-note">No cities match “' + q + '”.</div>'; }
+  list.forEach(function (c) {
+    var n = counts[c.slug] || 0;
+    var a = document.createElement('a');
+    a.className = 'city' + (loaded && n === 0 ? ' empty' : '');
+    a.href = urlOf(c);
+    a.innerHTML = '<span><span class="nm">' + c.label + '</span><span class="host">' + c.host + '</span></span>' +
+                  '<span class="n' + (n ? ' has' : '') + '">' + (loaded ? n : '·') + '</span>';
     grid.appendChild(a);
   });
-  count.textContent = shown.length + (shown.length===CITIES.length ? '' : ' of '+CITIES.length) + ' cities';
+  var shownCities = visible();
+  var withData = CITIES.filter(function (c) { return (counts[c.slug] || 0) > 0; }).length;
+  count.textContent = loaded
+    ? (shownCities.length + ' markets shown · ' + withData + ' with listings · ' + CITIES.length + ' total')
+    : (CITIES.length + ' city markets');
+  foot.textContent = 'CRCP · recities.crcp.agentabrams.com';
 }
-function open(){ const c = CITIES.find(x=>x.slug===sel.value); if(c) window.location.href = urlOf(c); }
 
+function open() { var c = CITIES.find(function (x) { return x.slug === sel.value; }); if (c) window.location.href = urlOf(c); }
 document.getElementById('go').addEventListener('click', open);
-sel.addEventListener('keydown', e => { if(e.key==='Enter') open(); });
-filter.addEventListener('input', e => draw(e.target.value));
-draw('');
-foot.textContent = CITIES.length + ' city markets · CRCP · recities.crcp.agentabrams.com';
+sel.addEventListener('keydown', function (e) { if (e.key === 'Enter') open(); });
+filter.addEventListener('input', render);
+showEmpty.addEventListener('change', render);
+render();
 </script>
 </body>
 </html>
diff --git a/scripts/serve.js b/scripts/serve.js
index a783f69..1177149 100644
--- a/scripts/serve.js
+++ b/scripts/serve.js
@@ -67,6 +67,10 @@ app.use((req, res, next) => {
 // so the sign-in page stays pristine. Anything not a readable public html file falls through.
 const PUB_DIR = path.join(ROOT, 'public');
 const BADGE_TAG = '<script src="/user-badge.js" defer></script>';
+// Per-city scoping: injected in <head> BEFORE the app's inline boot so crcp-scope.js can wrap
+// window.fetch and filter ranked.json to <city>.crcp.agentabrams.com. Unscoped hosts are a no-op.
+// (sync scripts, order matters: cities table then the scoper.)
+const SCOPE_HEAD = '<script src="/crcp-cities.js"></script><script src="/crcp-scope.js"></script>';
 app.get(/(?:^\/$|\.html$)/, (req, res, next) => {
   // recities.crcp.agentabrams.com serves the city-launcher hub at '/'; every other host gets the app.
   const isRecities = (req.hostname || '').toLowerCase() === 'recities.crcp.agentabrams.com';
@@ -76,9 +80,14 @@ app.get(/(?:^\/$|\.html$)/, (req, res, next) => {
   if (!file.startsWith(PUB_DIR + path.sep)) return next();  // path-traversal guard
   fs.readFile(file, 'utf8', (err, html) => {
     if (err) return next();                                 // not a public html file -> other routes
-    const out = html.includes('/user-badge.js') ? html
+    let out = html.includes('/user-badge.js') ? html
       : html.includes('</body>') ? html.replace(/<\/body>/i, BADGE_TAG + '</body>')
       : html + BADGE_TAG;
+    // recities.html loads crcp-cities.js itself; the scoper is only meaningful on the app pages.
+    if (rel !== 'recities.html' && !out.includes('/crcp-scope.js')) {
+      out = out.includes('</head>') ? out.replace(/<\/head>/i, SCOPE_HEAD + '</head>')
+          : (out.includes('<body') ? out.replace(/<body[^>]*>/i, '$&' + SCOPE_HEAD) : SCOPE_HEAD + out);
+    }
     res.type('html').send(out);
   });
 });

← 34a021c 5x: verify crcp list view — six-way green, 29 clickthrough f  ·  back to Commercialrealestate  ·  loan-officers: ingest CA DRE MLO List (LA-County active indi beeafe7 →