← back to Quadrille Showroom
auto-save: 2026-06-28T18:16:07 (2 files) — public/js/showroom.js scripts/diag-probe.mjs
34871bce02b69dfeb7889232c8965b9599e5f3a9 · 2026-06-28 18:16:10 -0700 · Steve Abrams
Files touched
M public/js/showroom.jsA scripts/diag-probe.mjs
Diff
commit 34871bce02b69dfeb7889232c8965b9599e5f3a9
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Jun 28 18:16:10 2026 -0700
auto-save: 2026-06-28T18:16:07 (2 files) — public/js/showroom.js scripts/diag-probe.mjs
---
public/js/showroom.js | 15 ++++---
scripts/diag-probe.mjs | 104 +++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 114 insertions(+), 5 deletions(-)
diff --git a/public/js/showroom.js b/public/js/showroom.js
index 0c1c6e9..9649cb3 100644
--- a/public/js/showroom.js
+++ b/public/js/showroom.js
@@ -1980,12 +1980,17 @@ function specRow(label, value) {
return `<div class="wd-spec"><dt>${label}</dt><dd>${value}</dd></div>`;
}
-// Detail popup starts COLLAPSED on load (Steve) — shows just brand + pattern name;
-// the user taps the chevron to expand the full spec sheet. Choice persists.
-let detailCollapsed = (localStorage.getItem('qh_detail_collapsed') !== '0');
+// Detail popup starts COLLAPSED on EVERY fresh selection (Steve HARD rule) — shows just
+// brand + pattern name; the user taps the chevron to expand the full spec sheet for THAT
+// selection only. The expand is a per-session, per-selection toggle — it is NOT persisted
+// as a sticky default, because a stored "expanded" flag would silently defeat
+// collapsed-by-default on the next click / reload (the HOLE-3 footgun). So:
+// • detailCollapsed is the LIVE state of the currently-shown card (starts true);
+// • setDetailCollapsed() drives the live state + DOM but never writes localStorage;
+// • every showWingDetail() hard-resets the card to COLLAPSED regardless of prior toggle.
+let detailCollapsed = true;
function setDetailCollapsed(c) {
detailCollapsed = c;
- localStorage.setItem('qh_detail_collapsed', c ? '1' : '0');
const panel = document.getElementById('wing-detail');
if (panel) panel.classList.toggle('collapsed', c);
const btn = document.getElementById('detail-collapse');
@@ -1996,7 +2001,7 @@ function showWingDetail(product) {
window._activeProduct = product;
const panel = document.getElementById('wing-detail');
panel.classList.remove('hidden');
- setDetailCollapsed(detailCollapsed); // apply collapsed state (default collapsed)
+ setDetailCollapsed(true); // HARD reset: every fresh selection starts COLLAPSED (HOLE-3 fix)
const { pattern, colorway } = splitPatternColor(product.pattern_name || product.name);
document.getElementById('detail-pattern').textContent = pattern || 'Pattern';
diff --git a/scripts/diag-probe.mjs b/scripts/diag-probe.mjs
new file mode 100644
index 0000000..9ee3367
--- /dev/null
+++ b/scripts/diag-probe.mjs
@@ -0,0 +1,104 @@
+// Diagnostic probe (NOT proof) — measure, via REAL yaw only, two things:
+// (A) HOLE-2: after selecting a board, how wide is the hero on screen vs a closed
+// sliver neighbour? (project the hero face corners + a neighbour face corners).
+// (B) HOLE-1: yaw to the +72 / -72 clamp via real ArrowRight/ArrowLeft keydowns and
+// measure how many screen pixels of clad SIDE/BACK wall are visible (raycast a grid
+// of screen points into the scene; count hits on a roomWall mesh that carries a clad
+// material map). NO setMode('room'), NO park(), NO camera move.
+import pw from '/Users/stevestudio2/.npm-global/lib/node_modules/playwright/index.js';
+const { chromium } = pw;
+const browser = await chromium.launch({ args: ['--use-gl=angle','--use-angle=swiftshader','--enable-webgl','--ignore-gpu-blocklist'] });
+const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
+const errors = [];
+page.on('console', m => { if (m.type()==='error') errors.push(m.text()); });
+page.on('pageerror', e => errors.push('PAGEERROR: '+e.message));
+await page.goto('http://localhost:7690/', { waitUntil: 'networkidle' });
+await page.waitForFunction(() => window._qh && window._qh.wingBoards && window._qh.wingBoards.length>0, { timeout:20000 });
+await page.waitForTimeout(2500);
+
+// Expose a raycast-based "clad wall pixels visible" sampler into the page.
+await page.addScriptTag({ content: `
+window.__measureCladWall = function() {
+ const qh = window._qh, THREE = qh.THREE, cam = qh.camera, scene = qh.scene;
+ const W = window.innerWidth, H = window.innerHeight;
+ const rc = new THREE.Raycaster();
+ // collect all room-wall meshes that currently carry a clad texture map
+ const cladMeshes = [];
+ scene.traverse(o => {
+ if (o.isMesh && o.material && o.material.map && o.geometry && o.geometry.type==='PlaneGeometry') {
+ // wall planes are large; wings are PlaneGeometry too but smaller — tag by size
+ const g = o.geometry.parameters || {};
+ if (g.width >= 1.0 && g.height >= 2.0) cladMeshes.push(o);
+ }
+ });
+ // sample a 40x24 screen grid
+ let cladHits=0, total=0, wingHits=0, otherHits=0;
+ const targets = [];
+ scene.traverse(o => { if (o.isMesh) targets.push(o); });
+ for (let yi=0; yi<24; yi++) for (let xi=0; xi<40; xi++) {
+ const nx = (xi+0.5)/40*2-1, ny = -((yi+0.5)/24*2-1);
+ rc.setFromCamera({x:nx,y:ny}, cam);
+ const hit = rc.intersectObjects(targets, false)[0];
+ total++;
+ if (!hit) continue;
+ if (cladMeshes.includes(hit.object)) cladHits++;
+ else if (hit.object.userData && hit.object.userData.isWingFace) wingHits++;
+ else otherHits++;
+ }
+ return { cladHits, wingHits, otherHits, total, cladMeshCount: cladMeshes.length, yawDeg:+(qh.spinYaw*180/Math.PI).toFixed(1) };
+};
+// hero vs neighbour on-screen width (projected face X-extent in px)
+window.__measureHeroWidth = function() {
+ const qh = window._qh, THREE = qh.THREE, cam = qh.camera;
+ const f = qh.focusedWing; if (!f) return null;
+ const wb = qh.wingBoards;
+ function faceWidthPx(pivot) {
+ const ud = pivot.userData; const face = ud.face; if (!face) return 0;
+ const g = face.geometry.parameters; const hw = g.width/2, hh=g.height/2;
+ const corners = [[-hw,-hh],[hw,-hh],[hw,hh],[-hw,hh]];
+ let minx=1e9,maxx=-1e9;
+ for (const [cx,cy] of corners) {
+ const v = new THREE.Vector3(cx,cy,0);
+ face.localToWorld(v); v.project(cam);
+ const px=(v.x*0.5+0.5)*window.innerWidth;
+ minx=Math.min(minx,px); maxx=Math.max(maxx,px);
+ }
+ return maxx-minx;
+ }
+ const heroW = faceWidthPx(f);
+ // nearest closed neighbour (index +/-2 from hero, whichever exists)
+ const hi = f.userData.index;
+ const nb = wb[hi+3] || wb[hi-3] || wb[hi+1] || wb[hi-1];
+ const nbW = nb ? faceWidthPx(nb) : 0;
+ return { heroW:+heroW.toFixed(1), nbW:+nbW.toFixed(1), ratio:+(heroW/(nbW||1)).toFixed(2), heroFlip:+f.userData.flip.toFixed(3), heroIdx:hi };
+};
+`});
+
+// --- select a board via the REAL focus path ---
+await page.evaluate(() => {
+ const wb = window._qh.wingBoards;
+ const idx = Math.floor(wb.length/2) + 4;
+ window._qh.focusOnWing(wb[idx]);
+});
+// wait for carousel settle + flip + clad load
+await page.waitForTimeout(4500);
+
+const heroW = await page.evaluate(() => window.__measureHeroWidth());
+const cladFront = await page.evaluate(() => window.__measureCladWall());
+
+// --- REAL yaw to +72 (right) via held ArrowRight keydowns ---
+async function holdYaw(key, ms) {
+ await page.evaluate((k)=>{ window.dispatchEvent(new KeyboardEvent('keydown',{key:k,code:k,bubbles:true})); }, key);
+ await page.waitForTimeout(ms);
+ await page.evaluate((k)=>{ window.dispatchEvent(new KeyboardEvent('keyup',{key:k,code:k,bubbles:true})); }, key);
+ await page.waitForTimeout(400);
+}
+await holdYaw('ArrowRight', 1400);
+const cladRight = await page.evaluate(() => window.__measureCladWall());
+// back to centre then full left
+await holdYaw('ArrowLeft', 1400); // to ~0
+await holdYaw('ArrowLeft', 1400); // to ~-72
+const cladLeft = await page.evaluate(() => window.__measureCladWall());
+
+console.log(JSON.stringify({ heroW, cladFront, cladRight, cladLeft, controlsLocked: await page.evaluate(()=>window._qh.controlsLocked), exploreMode: await page.evaluate(()=>window._qh.exploreMode), errors: errors.slice(0,5), errorCount: errors.length }, null, 2));
+await browser.close();
← 0e9e564 Open-face clean: hero board pushes forward + re-centres so t
·
back to Quadrille Showroom
·
Slice-2 contrarian fixes: HOLE-3 detail starts COLLAPSED on c4a14bb →