[object Object]

← back to Commercialrealestate

5x night item 1: map.html — aerial-img onerror graceful fallback + strong map-assert.js gate (markers/toggles/popup-decode/empty-data); map verified 6/6 + strong PASS

f98b8afb018211228c5f734420dbe2cb1ef4ce79 · 2026-06-28 23:01:12 -0700 · Steve

Files touched

Diff

commit f98b8afb018211228c5f734420dbe2cb1ef4ce79
Author: Steve <steve@designerwallcoverings.com>
Date:   Sun Jun 28 23:01:12 2026 -0700

    5x night item 1: map.html — aerial-img onerror graceful fallback + strong map-assert.js gate (markers/toggles/popup-decode/empty-data); map verified 6/6 + strong PASS
---
 5x/map-assert.js   | 37 +++++++++++++++++++++++++++++++++++++
 5x/map-sweep-1.raw | 15 +++++++++++++++
 public/map.html    |  2 +-
 3 files changed, 53 insertions(+), 1 deletion(-)

diff --git a/5x/map-assert.js b/5x/map-assert.js
new file mode 100644
index 0000000..7dbfe9d
--- /dev/null
+++ b/5x/map-assert.js
@@ -0,0 +1,37 @@
+// Strong map.html gate: markers drew per type, legend toggles add/remove layers, popup aerial decodes to
+// real pixels, and empty-data degrades (no markers, no JS error). What /3x's --selector cannot assert.
+const pw=require('/Users/stevestudio2/.claude/skills/browserbase/node_modules/playwright-core');
+const EXEC='/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
+async function run(label, mockEmpty){
+  const errors=[]; const b=await pw.chromium.launch({executablePath:EXEC,headless:true});
+  const pg=await b.newPage({viewport:{width:1280,height:820}});
+  pg.on('pageerror',e=>errors.push('PAGEERR: '+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('**/data/map-points.json',r=>r.fulfill({status:200,contentType:'application/json',body:'{"points":[]}'}));
+  await pg.goto('http://127.0.0.1:9911/map.html',{waitUntil:'networkidle'}); await pg.waitForTimeout(2500);
+  const base=await pg.evaluate(()=>({legend:document.querySelectorAll('#legend label').length,
+    layerTypes:Object.keys(layers||{}).length,
+    markers:Object.values(layers||{}).reduce((n,l)=>n+l.getLayers().length,0)}));
+  let toggleOk=true, popup={};
+  if(!mockEmpty){
+    const labs=await pg.$$('#legend label');
+    for(const lab of labs){ const t=await lab.evaluate(e=>e.querySelector('input').dataset.t);
+      await lab.click(); await pg.waitForTimeout(80); const off=await pg.evaluate(t=>map.hasLayer(layers[t]),t);
+      await lab.click(); await pg.waitForTimeout(80); const on=await pg.evaluate(t=>map.hasLayer(layers[t]),t);
+      if(off||!on) toggleOk=false; }
+    popup=await pg.evaluate(async()=>{let tgt=null;for(const t in layers){layers[t].eachLayer(l=>{if(!tgt)tgt=l});if(tgt)break;}
+      if(!tgt)return{open:false}; tgt.openPopup(); await new Promise(r=>setTimeout(r,700));
+      const img=document.querySelector('.leaflet-popup img'); return{open:!!document.querySelector('.leaflet-popup'),hasImg:!!img};});
+    if(popup.hasImg){ await pg.waitForTimeout(1500);
+      popup.decoded=await pg.evaluate(()=>{const i=document.querySelector('.leaflet-popup img');return i.complete&&i.naturalWidth>0;}); }
+  }
+  await b.close();
+  return {label, ...base, toggleOk, popup, jsErrors:errors};
+}
+(async()=>{
+  const normal=await run('NORMAL',false); const empty=await run('EMPTY',true);
+  const passNormal = normal.markers>1000 && normal.legend>=4 && normal.toggleOk && normal.popup.open && normal.popup.decoded && normal.jsErrors.length===0;
+  const passEmpty = empty.markers===0 && empty.jsErrors.length===0;
+  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/map-sweep-1.raw b/5x/map-sweep-1.raw
new file mode 100644
index 0000000..f9ad8fb
--- /dev/null
+++ b/5x/map-sweep-1.raw
@@ -0,0 +1,15 @@
+
+/3x — verifying http://127.0.0.1:9911/map.html
+
+Phase A — 3 unique methods:
+  [PASS] M1 HTTP contract  request layer — HTTP 200 text/html
+  [PASS] M2 headless render  render layer — screenshot 2201KB → /var/folders/2n/jmw9_4v56_x4m3539tr4v0lr0000gn/T/3x-B6UBTO/render.png
+  [PASS] M3 automation E2E  interaction layer — 5×"#legend label", 0 JS errors
+
+Phase B — 3 browsers (cross-browser):
+  [PASS] B4 Google Chrome  cross-browser — automated render OK
+  [PASS] B5 Safari  cross-browser — automated render OK
+  [PASS] B6 Firefox  cross-browser — automated render OK
+
+  RESULT → 6/6 passed
+  artifacts: /var/folders/2n/jmw9_4v56_x4m3539tr4v0lr0000gn/T/3x-B6UBTO
diff --git a/public/map.html b/public/map.html
index 715541a..aaa5f20 100644
--- a/public/map.html
+++ b/public/map.html
@@ -72,7 +72,7 @@ function popupHtml(p){
   return `<div class="pop"><h3>${p.address||'(address n/a)'}</h3><div class="ad">${p.city||''} ${p.zip||''}</div>`+
     `<div class="row"><span class="tag" style="border-color:${COLORS[p.type]||'#888'}">${p.type}</span>`+
     (p.price?`<span class="tag price">${money(p.price)}</span>`:'')+`</div>`+
-    `<img loading="lazy" src="${aerialUrl(p.lat,p.lng)}" alt="aerial view">`+
+    `<img loading="lazy" src="${aerialUrl(p.lat,p.lng)}" alt="aerial view" onerror="this.style.display='none';this.nextElementSibling.textContent='Aerial imagery unavailable for this point';">`+
     `<div class="aerialcap">Overhead imagery © Esri World Imagery (free)</div>`+
     `<div class="links">`+
     (p.source?`<a href="${p.source}" target="_blank" rel="noopener">View listing ↗</a>`:'')+

← 14b36ac 5x: overnight refinement night-log + queue  ·  back to Commercialrealestate  ·  5x night item 3: wire nonqm segment agent count (union of no b6a5bdd →