← 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
M public/js/showroom.jsA scripts/verify-sort-density-persist.mjs
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 →