[object Object]

← back to Quadrille Showroom

auto-save: 2026-07-01T10:11:20 (2 files) — public/js/showroom.js scripts/verify-sort-density-persist.mjs

f0433174f731a673e59c144dfc8979a8b62d2fc9 · 2026-07-01 10:11:25 -0700 · Steve Abrams

Files touched

Diff

commit f0433174f731a673e59c144dfc8979a8b62d2fc9
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Jul 1 10:11:25 2026 -0700

    auto-save: 2026-07-01T10:11:20 (2 files) — public/js/showroom.js scripts/verify-sort-density-persist.mjs
---
 public/js/showroom.js                   | 26 +++++++++++------
 scripts/verify-sort-density-persist.mjs | 50 +++++++++++++++++++++++++++++++++
 2 files changed, 68 insertions(+), 8 deletions(-)

diff --git a/public/js/showroom.js b/public/js/showroom.js
index 1cb62de..d1ea09d 100644
--- a/public/js/showroom.js
+++ b/public/js/showroom.js
@@ -81,8 +81,8 @@ let carouselActive = false;    // true while the rack is mid-shift
 let _carouselPending = null;   // pivot to open once the shift settles
 
 // Window paging across the full catalog (50 live wings at a time)
-let windowOffset = 0, windowTotal = 0, windowSize = 50;  // 50 boards in the packed arc (Slice-1 spec): each closed wing shows a ~2" sliver, fanning left+right around the centred viewer
-let currentBrand = 'all', currentSort = 'natural', currentWallGroup = null;
+let windowOffset = 0, windowTotal = 0, windowSize = parseInt(localStorage.getItem('qh_density')) || 50;  // 50 boards in the packed arc (Slice-1 spec): each closed wing shows a ~2" sliver, fanning left+right around the centred viewer; persisted per standing sort+density rule
+let currentBrand = 'all', currentSort = localStorage.getItem('qh_sort') || 'natural', currentWallGroup = null;
 // Peruse (endless auto-tour) state
 let peruseActive = false, peruseIndex = 0, peruseTimer = null;
 const PERUSE_DWELL = 5000; // ms per wing — dwell 5s on each open design
@@ -3880,9 +3880,14 @@ function initHUD() {
   if (nextB) nextB.addEventListener('click', () => { stopPeruse(); advanceWindow(1); });
 
   const sortSel = document.getElementById('sort-select');
-  if (sortSel) sortSel.addEventListener('change', async () => {
-    stopPeruse(); currentSort = sortSel.value; await fetchWindow(0); rebuildWingWall();
-  });
+  if (sortSel) {
+    sortSel.value = currentSort;                          // reflect the persisted choice into the control on boot
+    sortSel.addEventListener('change', async () => {
+      stopPeruse(); currentSort = sortSel.value;
+      localStorage.setItem('qh_sort', currentSort);       // standing rule: sort persists across reloads
+      await fetchWindow(0); rebuildWingWall();
+    });
+  }
 
   // Wing-angle slider — live, persisted. Controls the open-board VIEW angle.
   const ang = document.getElementById('angle-range');
@@ -3902,9 +3907,14 @@ function initHUD() {
   }
 
   const dens = document.getElementById('density-range');
-  if (dens) dens.addEventListener('change', async () => {
-    stopPeruse(); windowSize = parseInt(dens.value) || 50; await fetchWindow(0); rebuildWingWall();
-  });
+  if (dens) {
+    dens.value = windowSize;                              // reflect the persisted density into the slider on boot
+    dens.addEventListener('change', async () => {
+      stopPeruse(); windowSize = parseInt(dens.value) || 50;
+      localStorage.setItem('qh_density', windowSize);     // standing rule: density persists across reloads
+      await fetchWindow(0); rebuildWingWall();
+    });
+  }
 
   // REVEAL slider — how much of each closed board's pattern shows in the packed arc.
   // Live: re-rakes every board's closed yaw via QHRack.relayout (no full rebuild), so
diff --git a/scripts/verify-sort-density-persist.mjs b/scripts/verify-sort-density-persist.mjs
new file mode 100644
index 0000000..6638dc4
--- /dev/null
+++ b/scripts/verify-sort-density-persist.mjs
@@ -0,0 +1,50 @@
+// verify-sort-density-persist.mjs — proves the standing sort+density rule's localStorage
+// requirement: change sort + density, RELOAD, and assert both survive. Repeatable (not a
+// one-time manual drive). Exit 1 on any failure. Resolves playwright from a skill install
+// since the project itself doesn't depend on it.
+import { createRequire } from 'module';
+const require = createRequire(import.meta.url);
+const CANDIDATES = [
+  'playwright',
+  '/Users/stevestudio2/.claude/skills/hero-readability-auditor/node_modules/playwright/index.js',
+  '/Users/stevestudio2/.claude/skills/app-demo-video/node_modules/playwright/index.js',
+];
+let chromium=null;
+for (const c of CANDIDATES){ try { ({ chromium } = require(c)); if (chromium) break; } catch {} }
+if (!chromium){ console.error('playwright not found'); process.exit(2); }
+
+const URL = process.env.URL || 'http://127.0.0.1:7690';
+const openControls = async (p) => {
+  await p.locator('#qh-burger').click({ timeout:6000 }); await p.waitForTimeout(400);
+  await p.locator('#qh-menu .qh-menu-item', { hasText:'Controls' }).first().click({ timeout:5000 });
+  await p.waitForTimeout(1200);
+};
+const b = await chromium.launch({ channel:'chrome' });
+const ctx = await b.newContext({ httpCredentials:{ username:'admin', password:'DWSecure2024!' } });
+const p = await ctx.newPage();
+let fail=0;
+await p.goto(URL,{ waitUntil:'load', timeout:20000 }); await p.waitForTimeout(3500);
+await openControls(p);
+// change sort → newest, density → 30
+await p.selectOption('#sort-select','newest',{ timeout:6000 });
+await p.locator('#density-range').evaluate(el=>{ el.value=30; el.dispatchEvent(new Event('change',{bubbles:true})); });
+await p.waitForTimeout(800);
+const ls1 = await p.evaluate(()=>({ sort:localStorage.getItem('qh_sort'), dens:localStorage.getItem('qh_density') }));
+console.log('after change  →', JSON.stringify(ls1));
+if (ls1.sort!=='newest'){ console.log('❌ qh_sort not written'); fail++; }
+if (String(ls1.dens)!=='30'){ console.log('❌ qh_density not written'); fail++; }
+// RELOAD — the real test
+await p.reload({ waitUntil:'load', timeout:20000 }); await p.waitForTimeout(3500);
+await openControls(p);
+const after = await p.evaluate(()=>({
+  ls:{ sort:localStorage.getItem('qh_sort'), dens:localStorage.getItem('qh_density') },
+  sel:document.getElementById('sort-select')?.value,
+  slider:document.getElementById('density-range')?.value,
+}));
+console.log('after reload  →', JSON.stringify(after));
+if (after.ls.sort!=='newest'){ console.log('❌ qh_sort lost on reload'); fail++; }
+if (after.sel!=='newest'){ console.log('❌ sort <select> did not restore to newest'); fail++; }
+if (String(after.slider)!=='30'){ console.log('❌ density slider did not restore to 30'); fail++; }
+await b.close();
+console.log(fail ? `\n❌ ${fail} persistence assertion(s) FAILED` : '\n✅ sort + density persist across reload (standing rule satisfied)');
+process.exit(fail?1:0);

← da29667 5x: prove sort/density reachable+functional via ☰→Controls (  ·  back to Quadrille Showroom  ·  5x sweep 3 (contrarian-driven): persist sort+density to loca 504a0ed →