[object Object]

← back to Quadrille Showroom

furniture FIX 1: onload = clean empty room + table+chair only (no room-type dressing on boot)

6bdf2b5ddb11e4b2d34f74852713116d78ce9022 · 2026-07-02 10:16:18 -0700 · Steve

Boot no longer builds a fully-dressed 'living' room type (sofa/lamps/wingchair/
rug/sculpture/plant). Room-type dressing now appears ONLY when the user clicks a
room-type chip; a returning user also boots to the clean empty room. Verified via
probe-boot-scene: currentRoomType null, no roomType group, 1 furnitureRoot (table+chair).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Files touched

Diff

commit 6bdf2b5ddb11e4b2d34f74852713116d78ce9022
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Jul 2 10:16:18 2026 -0700

    furniture FIX 1: onload = clean empty room + table+chair only (no room-type dressing on boot)
    
    Boot no longer builds a fully-dressed 'living' room type (sofa/lamps/wingchair/
    rug/sculpture/plant). Room-type dressing now appears ONLY when the user clicks a
    room-type chip; a returning user also boots to the clean empty room. Verified via
    probe-boot-scene: currentRoomType null, no roomType group, 1 furnitureRoot (table+chair).
    
    Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---
 public/js/viewmodes.js       | 15 ++++-----
 scripts/probe-boot-scene.mjs | 45 +++++++++++++++++++++++++
 scripts/verify-qh6.mjs       | 78 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 130 insertions(+), 8 deletions(-)

diff --git a/public/js/viewmodes.js b/public/js/viewmodes.js
index 676866d..a815c54 100644
--- a/public/js/viewmodes.js
+++ b/public/js/viewmodes.js
@@ -842,16 +842,15 @@ function boot() {
   // Perf toggle restore.
   if (localStorage.getItem('qh_perf') === '1') togglePerf(true); else togglePerf(false);
 
-  // Room-type restore (Phase 2). Default = Living Room (closest to the canonical box).
-  // Build the furnished room WITHOUT auto-re-framing on boot so the showroom's own
-  // walk/guided boot pose still owns the camera; the user re-frames by clicking a chip.
-  const savedRoom = localStorage.getItem('qh_room_type');
-  const bootRoom = (savedRoom && QH.ROOM_TYPES && QH.ROOM_TYPES[savedRoom]) ? savedRoom : 'living';
-  // Reflect the saved floor override on the picker.
+  // Room-type dressing (sofa / lamps / wingchair / boards / rug / sculpture / plant) is
+  // OFF on boot (Steve 2026-07-02): "onload load nothing except the table and chair;
+  // remove all other elements." The room boots as a CLEAN empty room + the active style's
+  // table + chair only (swapFurniture owns those). A furnished room type is built ONLY when
+  // the user explicitly clicks a room-type chip (applyRoomType, wired at buildPanel). We do
+  // NOT auto-restore a saved room type either — a returning user also boots to the clean
+  // empty room and re-dresses by choosing a chip.
   const savedFloor = QH.floorOverride || 'default';
   document.querySelectorAll('#vm-floor .vm-chip').forEach(c => c.classList.toggle('active', c.dataset.floor === savedFloor));
-  // Defer the room build a beat so products/center-book have a chance to load (palette hook).
-  setTimeout(() => { applyRoomType(bootRoom, { noFrame: true }); }, 900);
 
   // View: Hero is ALWAYS the boot view for a first-time load (locked rule). But if
   // the user previously picked a mode, restore it AFTER the guided default has
diff --git a/scripts/probe-boot-scene.mjs b/scripts/probe-boot-scene.mjs
new file mode 100644
index 0000000..c3ba6ac
--- /dev/null
+++ b/scripts/probe-boot-scene.mjs
@@ -0,0 +1,45 @@
+import pw from '/Users/stevestudio2/.npm-global/lib/node_modules/playwright/index.js';
+const { chromium } = pw;
+
+const errors = [];
+const browser = await chromium.launch({ channel: 'chrome', headless: true, args: ['--use-angle=metal', '--ignore-gpu-blocklist'] });
+const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
+page.on('pageerror', e => errors.push('pageerror: ' + e.message));
+page.on('console', m => { if (m.type() === 'error') errors.push('console.error: ' + m.text()); });
+
+await page.goto('http://localhost:7690/', { waitUntil: 'domcontentloaded' });
+await page.waitForFunction(() => window._qh && window._qh.wingBoards && window._qh.wingBoards.length >= 50, { timeout: 25000 });
+await page.waitForTimeout(3500);
+
+const report = await page.evaluate(() => {
+  const qh = window._qh;
+  const scene = qh.scene;
+  // top-level scene children summary
+  const topLevel = scene.children.map(c => ({
+    name: c.name || '(unnamed)',
+    type: c.type,
+    isLight: !!c.isLight,
+    childCount: c.children ? c.children.length : 0
+  }));
+  // count meshes by keyword to detect room-dressing props
+  const kw = { sofa:0, lamp:0, wing:0, board:0, rug:0, plant:0, olive:0, tree:0, sculpture:0, bust:0, chair:0, table:0 };
+  let meshTotal = 0;
+  scene.traverse(o => {
+    if (o.isMesh) meshTotal++;
+    const n = (o.name || '').toLowerCase();
+    for (const k of Object.keys(kw)) if (n.includes(k)) kw[k]++;
+  });
+  return {
+    furniturePresent: qh.furniturePresent,
+    furnitureChildCount: qh.furnitureChildCount,
+    currentRoomType: qh.currentRoomType,
+    roomTypeGroupPresent: !!(scene.getObjectByName && scene.children.some(c => (c.name||'').startsWith('roomType:'))),
+    topLevelCount: topLevel.length,
+    topLevel,
+    meshTotal,
+    keywordCounts: kw
+  };
+});
+
+console.log(JSON.stringify({ errors, report }, null, 2));
+await browser.close();
diff --git a/scripts/verify-qh6.mjs b/scripts/verify-qh6.mjs
new file mode 100644
index 0000000..e89cc26
--- /dev/null
+++ b/scripts/verify-qh6.mjs
@@ -0,0 +1,78 @@
+import pw from '/Users/stevestudio2/.npm-global/lib/node_modules/playwright/index.js';
+const { chromium } = pw;
+
+const errors = [];
+const browser = await chromium.launch({ channel: 'chrome', headless: true, args: ['--use-angle=metal', '--ignore-gpu-blocklist', '--window-size=1440,900'] });
+const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
+page.on('pageerror', e => errors.push('pageerror: ' + e.message));
+page.on('console', m => { if (m.type() === 'error') errors.push('console.error: ' + m.text()); });
+
+// Clean slate — clear any saved room-type/view so we test true first-boot.
+await page.addInitScript(() => { try { localStorage.clear(); } catch (e) {} });
+
+await page.goto('http://localhost:7690/', { waitUntil: 'domcontentloaded' });
+await page.waitForFunction(() => window._qh && window._qh.wingBoards && window._qh.wingBoards.length >= 50, { timeout: 25000 });
+await page.waitForTimeout(4000);   // let boot settle
+
+const out = {};
+
+// (a) ONLOAD: only table+chair + empty room, no room-type dressing
+out.boot = await page.evaluate(() => {
+  const qh = window._qh, scene = qh.scene;
+  const roomTypeGroups = scene.children.filter(c => (c.name || '').startsWith('roomType:')).map(c => c.name);
+  const furnitureRoots = scene.children.filter(c => c.name === 'furnitureRoot').length;
+  return {
+    currentRoomType: qh.currentRoomType,
+    roomTypeGroups,
+    furnitureRoots,
+    furnitureChildCount: qh.furnitureChildCount,
+    furnitureStyle: qh.furnitureStyle
+  };
+});
+
+// Screenshots per style at DEFAULT camera (dead-ahead rest pose).
+const styles = ['contemporary', 'traditional', 'transitional', 'brutalist'];
+for (const s of styles) {
+  await page.evaluate((k) => window._qh.swapFurniture(k, { includeExtras: false }), s);
+  await page.waitForTimeout(700);
+  // reset to canonical rest pose so all 4 shots are the same framing
+  await page.evaluate(() => { if (window._qh.setSpin) window._qh.setSpin(0, -0.08); });
+  await page.waitForTimeout(500);
+  await page.screenshot({ path: `/tmp/qh6-${s}.png` });
+}
+
+// (c) all 4 swap + (d) exactly 1 furnitureRoot after 10 swaps (leak test)
+const order = ['contemporary', 'traditional', 'transitional', 'brutalist'];
+let swapOK = true;
+for (let i = 0; i < 10; i++) {
+  const k = order[i % order.length];
+  const res = await page.evaluate((key) => {
+    window._qh.swapFurniture(key, { includeExtras: false });
+    return { style: window._qh.furnitureStyle, present: window._qh.furniturePresent, childCount: window._qh.furnitureChildCount };
+  }, k);
+  if (res.style !== k || !res.present || res.childCount < 1) swapOK = false;
+  await page.waitForTimeout(120);
+}
+out.leak = await page.evaluate(() => {
+  const scene = window._qh.scene;
+  return { furnitureRootCount: scene.children.filter(c => c.name === 'furnitureRoot').length, style: window._qh.furnitureStyle };
+});
+out.swapAll4OK = swapOK;
+
+// (f) "At the Table" seated view still works
+out.tableView = await page.evaluate(async () => {
+  try {
+    window._viewmode.set('table');
+    return { set: true };
+  } catch (e) { return { set: false, err: String(e) }; }
+});
+await page.waitForTimeout(1500);
+out.tableViewAfter = await page.evaluate(() => {
+  const cam = window._qh.camera.position;
+  return { camY: +cam.y.toFixed(3), camZ: +cam.z.toFixed(3), errorsSoFar: 0 };
+});
+await page.screenshot({ path: '/tmp/qh6-tableview.png' });
+
+out.errors = errors;
+console.log(JSON.stringify(out, null, 2));
+await browser.close();

← 0a7d7ab furniture: art-direction fixes — concrete-grey Brutalist, wa  ·  back to Quadrille Showroom  ·  furniture FIX 2: Transitional now unmistakably distinct from 02b4e0a →