← back to Commercialrealestate
5x sweep 3 (contrarian-gated): fix medAll ÷0 → $NaN tile; guard F.money/F.num/countUp + grad() undefined-color crash (was swallowed on empty data); make Refresh/toggle verifier-discoverable; add strong deals-assert.js (charts-drew-data + empty-data + no-NaN gate)
292b7fb70b4ebecb0a451c301c497e82c8bf2648 · 2026-06-28 22:11:26 -0700 · Steve
Files touched
A 5x/deals-assert.jsM 5x/sweep-2-ct.rawM 5x/sweep-2.rawM public/deals.html
Diff
commit 292b7fb70b4ebecb0a451c301c497e82c8bf2648
Author: Steve <steve@designerwallcoverings.com>
Date: Sun Jun 28 22:11:26 2026 -0700
5x sweep 3 (contrarian-gated): fix medAll ÷0 → $NaN tile; guard F.money/F.num/countUp + grad() undefined-color crash (was swallowed on empty data); make Refresh/toggle verifier-discoverable; add strong deals-assert.js (charts-drew-data + empty-data + no-NaN gate)
---
5x/deals-assert.js | 40 ++++++++++++++++++++++++++++++++++++++++
5x/sweep-2-ct.raw | 4 ++++
5x/sweep-2.raw | 7 +++++++
public/deals.html | 15 ++++++++-------
4 files changed, 59 insertions(+), 7 deletions(-)
diff --git a/5x/deals-assert.js b/5x/deals-assert.js
new file mode 100644
index 0000000..ae316fc
--- /dev/null
+++ b/5x/deals-assert.js
@@ -0,0 +1,40 @@
+// Strong deals.html gate (what /3x's --selector cannot do): assert the 6 charts actually drew DATA,
+// no tile renders NaN/$NaN, and the EMPTY-DATA path degrades without crashing or showing NaN.
+const pw = require('/Users/stevestudio2/.claude/skills/browserbase/node_modules/playwright-core');
+const EXEC = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
+const CHARTS = ['cPipe','cType','cCap','cMed','cGeo','cBand'];
+
+async function run(label, mockEmpty){
+ const errors=[];
+ const b = await pw.chromium.launch({ executablePath: EXEC, headless: true });
+ const pg = await b.newPage({ viewport:{width:1320,height:1000} });
+ pg.on('pageerror', e=>errors.push('PAGEERROR: '+e.message));
+ pg.on('console', m=>{ if(m.type()==='error' && !/Failed to load resource/.test(m.text())) errors.push('CONSOLE: '+m.text()); });
+ if (mockEmpty){
+ await pg.route('**/api/segments', r=>r.fulfill({status:200,contentType:'application/json',body:'{"segments":[]}'}));
+ await pg.route('**/api/crcp/stats', r=>r.fulfill({status:200,contentType:'application/json',body:'{}'}));
+ await pg.route('**/data/ranked.json', r=>r.fulfill({status:200,contentType:'application/json',body:'{"ranked":[]}'}));
+ await pg.route('**/data/map-points.json', r=>r.fulfill({status:200,contentType:'application/json',body:'{"points":[]}'}));
+ }
+ await pg.goto('http://127.0.0.1:9911/deals.html', { waitUntil:'networkidle' });
+ await pg.waitForTimeout(2200);
+ const r = await pg.evaluate((CHARTS)=>{
+ const charts = CHARTS.map(id=>{ const c=Chart.getChart(id); return { id, exists:!!c, points: c?(c.data.datasets[0]?.data?.length||0):0 }; });
+ const tiles = [...document.querySelectorAll('.tile .v')].map(v=>v.textContent);
+ const nanTiles = tiles.filter(t=>/nan|undefined/i.test(t));
+ return { charts, tiles, nanTiles };
+ }, CHARTS);
+ await b.close();
+ const chartsWithData = r.charts.filter(c=>c.points>0).length;
+ return { label, ...r, chartsWithData, jsErrors:errors };
+}
+
+(async()=>{
+ const normal = await run('NORMAL', false);
+ const empty = await run('EMPTY-DATA', true);
+ // Pass criteria:
+ const passNormal = normal.charts.every(c=>c.exists) && normal.chartsWithData>=5 && normal.nanTiles.length===0 && normal.jsErrors.length===0;
+ const passEmpty = empty.nanTiles.length===0 && empty.jsErrors.length===0; // empty allowed; NaN/crash NOT
+ console.log(JSON.stringify({ normal, empty, passNormal, passEmpty, VERDICT: (passNormal&&passEmpty)?'PASS':'FAIL' }, null, 2));
+ process.exit((passNormal&&passEmpty)?0:1);
+})().catch(e=>{ console.error('FAIL', e.message); process.exit(1); });
diff --git a/5x/sweep-2-ct.raw b/5x/sweep-2-ct.raw
index 9c07957..15ba57e 100644
--- a/5x/sweep-2-ct.raw
+++ b/5x/sweep-2-ct.raw
@@ -9,3 +9,7 @@ click-through agent → http://127.0.0.1:9911/deals.html
[FAIL] verdict button: skip — locator.click: Timeout 30000ms exceeded.
[FAIL] Next → — locator.click: Timeout 30000ms exceeded.
[FAIL] ← Prev — locator.click: Timeout 30000ms exceeded.
+ [FAIL] switch to Board — locator.click: Timeout 30000ms exceeded.
+ [FAIL] density slider — locator.fill: Timeout 30000ms exceeded.
+
+ CLICK-THROUGH → 0 passed, 10 failed, 0 skipped
diff --git a/5x/sweep-2.raw b/5x/sweep-2.raw
index 84ce958..cd331bd 100644
--- a/5x/sweep-2.raw
+++ b/5x/sweep-2.raw
@@ -23,3 +23,10 @@ click-through agent → http://127.0.0.1:9911/deals.html
[FAIL] verdict button: skip — locator.click: Timeout 30000ms exceeded.
[FAIL] Next → — locator.click: Timeout 30000ms exceeded.
[FAIL] ← Prev — locator.click: Timeout 30000ms exceeded.
+ [FAIL] switch to Board — locator.click: Timeout 30000ms exceeded.
+ [FAIL] density slider — locator.fill: Timeout 30000ms exceeded.
+
+ CLICK-THROUGH → 0 passed, 10 failed, 0 skipped
+
+ RESULT → 6/7 passed, 1 FAILED
+ artifacts: /var/folders/2n/jmw9_4v56_x4m3539tr4v0lr0000gn/T/3x-nqf9lO
diff --git a/public/deals.html b/public/deals.html
index 23c0be2..7ca9514 100644
--- a/public/deals.html
+++ b/public/deals.html
@@ -82,8 +82,8 @@
<p id="asof">LA County commercial + residential pipeline · live</p>
</div>
<div class="ctrls">
- <button id="refresh" class="ctrl-btn" title="Re-pull live data + redraw all charts"><span class="ico">↻</span> Refresh</button>
- <span class="seg-toggle" id="metric" title="Switch the pipeline chart metric">
+ <button id="refresh" class="ctrl-btn btn" title="Re-pull live data + redraw all charts"><span class="ico">↻</span> Refresh</button>
+ <span class="seg-toggle seg" id="metric" title="Switch the pipeline chart metric">
<button data-m="count" class="on">Deals</button><button data-m="med">Median $</button>
</span>
</div>
@@ -119,18 +119,18 @@
</div>
<script>
-const F = { money:n=>n==null?'—':'$'+(n>=1e9?(n/1e9).toFixed(1)+'B':n>=1e6?(n/1e6).toFixed(2)+'M':n>=1e3?Math.round(n/1e3)+'k':n),
- num:n=>Number(n||0).toLocaleString() };
+const F = { money:n=>(n==null||!isFinite(n))?'—':'$'+(n>=1e9?(n/1e9).toFixed(1)+'B':n>=1e6?(n/1e6).toFixed(2)+'M':n>=1e3?Math.round(n/1e3)+'k':n),
+ num:n=>{ n=Number(n); return isFinite(n)?n.toLocaleString():'—'; } };
const PAL = ['#e9c46a','#2dd4bf','#8b7cf6','#60a5fa','#f472b6','#fb923c','#34d399','#f87171','#a78bfa','#fbbf24'];
// translucent gradient fill for glass-matched bars
-function grad(ctx, c, vertical=true){ const a=ctx.chart.chartArea; if(!a) return c+'cc';
+function grad(ctx, c, vertical=true){ if(typeof c!=='string'||!c) c='#888888'; const a=ctx.chart.chartArea; if(!a) return c+'cc';
const g=ctx.chart.ctx.createLinearGradient(vertical?0:a.left,vertical?a.bottom:0,vertical?0:a.right,vertical?a.top:0);
g.addColorStop(0,c+'33'); g.addColorStop(1,c+'ee'); return g; }
Chart.defaults.color='#9aa3b4'; Chart.defaults.borderColor='rgba(255,255,255,.07)';
Chart.defaults.font.family="-apple-system,'SF Pro Display',Segoe UI,Roboto,sans-serif";
const glassTip={backgroundColor:'rgba(16,20,28,.92)',borderColor:'rgba(255,255,255,.14)',borderWidth:1,padding:11,cornerRadius:12,titleColor:'#fff',bodyColor:'#cfd6e4',displayColors:true};
-function countUp(el,to,fmt){ const dur=900,t0=performance.now();
+function countUp(el,to,fmt){ if(!isFinite(to))to=0; const dur=900,t0=performance.now();
(function step(t){ const k=Math.min(1,(t-t0)/dur), e=1-Math.pow(1-k,3); el.textContent=fmt(Math.round(to*e)); if(k<1)requestAnimationFrame(step); })(t0); }
let LAST=null, METRIC='count';
@@ -156,7 +156,8 @@ function draw(){
const totalProps=(stats.listings||0)+(stats.sfr||rows.length*0||0)+ (pts.filter(p=>p.kind==='condo').length||0) || pts.length;
const screened = pts.length || (segs.reduce((s,x)=>s+x.props,0));
const agents=(stats.brokers||stats.agents_commercial||0);
- const medAll = segs.length? Math.round(segs.reduce((s,x)=>s+x.med,0)/segs.filter(x=>x.med).length):0;
+ const priced = segs.filter(x=>x.med>0); // guard ÷0 → no "$NaN" tile
+ const medAll = priced.length ? Math.round(priced.reduce((s,x)=>s+x.med,0)/priced.length) : 0;
const tiles=[
{k:'Properties screened',v:screened,cls:'gold',ic:'🏙️',s:'commercial · condo · SFR, geocoded'},
{k:'Loan products live',v:segs.length,cls:'teal',ic:'◆',s:'Arcstone call segments'},
← 022582f 5x: sweep logs + REPORT (deals.html clean twice; clickthroug
·
back to Commercialrealestate
·
5x: final REPORT — clean twice (sweeps 4-5) with strengthene 2f5592e →