← back to Quadrille Showroom
/5x: showroom-aware clickthrough + fix 2 real dock.js bugs it caught
b3ca21a5bef110ac3736ccc8f2dde56774551b10 · 2026-06-29 15:42:47 -0700 · Steve
The generic /3x clickthrough can't open the dock hamburger, so it false-failed on
intentionally-stowed controls. Built scripts/clickthrough-showroom.mjs that drives the
app like a user (open hamburger → launch each panel → exercise sort/sliders/nav → proxy
every tool). It caught 2 GENUINE defects the blind clickthrough never could:
1. dock.js quote-in-selector: TOOLS entries '#top-bar [data-section="wings"]' / '...overview'
were interpolated raw into data-tool="..." (inner quotes close the attr) and into
reflectMenu's querySelector('[data-tool="..."]') (throws 'not a valid selector'). Wings +
Overview were unreachable from the menu and reflectMenu threw 14× PAGEERROR. Fix: key tools
by index (data-tool-idx), resolve TOOLS[idx].sel in JS.
2. dock.js launched panels showed EMPTY: launch() removed qh-stowed from the wrapper but not
from the panel element, so every launched panel's CONTENT stayed display:none!important —
only the header bar showed (sort select measured 0×0, which is why selectOption timed out).
Fix: el.classList.remove('qh-stowed') on launch. Panels now render their real content
(verified by screenshot: Versions list + Controls sort/sliders visible).
Sweep 4 clean: sort all 5 options, all ranges, all nav, all 10 tools, 0 console errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
M public/js/dock.jsM scripts/clickthrough-showroom.mjs
Diff
commit b3ca21a5bef110ac3736ccc8f2dde56774551b10
Author: Steve <steve@designerwallcoverings.com>
Date: Mon Jun 29 15:42:47 2026 -0700
/5x: showroom-aware clickthrough + fix 2 real dock.js bugs it caught
The generic /3x clickthrough can't open the dock hamburger, so it false-failed on
intentionally-stowed controls. Built scripts/clickthrough-showroom.mjs that drives the
app like a user (open hamburger → launch each panel → exercise sort/sliders/nav → proxy
every tool). It caught 2 GENUINE defects the blind clickthrough never could:
1. dock.js quote-in-selector: TOOLS entries '#top-bar [data-section="wings"]' / '...overview'
were interpolated raw into data-tool="..." (inner quotes close the attr) and into
reflectMenu's querySelector('[data-tool="..."]') (throws 'not a valid selector'). Wings +
Overview were unreachable from the menu and reflectMenu threw 14× PAGEERROR. Fix: key tools
by index (data-tool-idx), resolve TOOLS[idx].sel in JS.
2. dock.js launched panels showed EMPTY: launch() removed qh-stowed from the wrapper but not
from the panel element, so every launched panel's CONTENT stayed display:none!important —
only the header bar showed (sort select measured 0×0, which is why selectOption timed out).
Fix: el.classList.remove('qh-stowed') on launch. Panels now render their real content
(verified by screenshot: Versions list + Controls sort/sliders visible).
Sweep 4 clean: sort all 5 options, all ranges, all nav, all 10 tools, 0 console errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
public/js/dock.js | 22 +++++++++++++++-------
scripts/clickthrough-showroom.mjs | 24 ++++++++++++++++++++----
2 files changed, 35 insertions(+), 11 deletions(-)
diff --git a/public/js/dock.js b/public/js/dock.js
index f0fe1a9..ae4fa16 100644
--- a/public/js/dock.js
+++ b/public/js/dock.js
@@ -161,6 +161,9 @@
ModalRig.rig(wrap, { key: 'qhdock:' + p.sel, handle: head, width: p.w, height: p.h });
}
wrap.classList.remove('qh-stowed');
+ el.classList.remove('qh-stowed'); // the PANEL itself was stowed (display:none!important) on boot;
+ // removing it from the wrap alone left every launched panel's
+ // CONTENT invisible/0×0 — only the header showed. (Phase-5 /5x.)
bringToFront(p.sel, wrap);
if (openOrder.indexOf(p.sel) === -1) openOrder.push(p.sel);
saveOpen(); reflectMenu();
@@ -212,8 +215,12 @@
'<span>' + p.label + '</span><span class="qh-dot">●</span></button>';
});
html += '<div class="qh-menu-eyebrow">Tools</div>';
- TOOLS.forEach(function (t) {
- html += '<button class="qh-menu-item" data-tool="' + t.sel + '">' +
+ // NOTE: a TOOL selector may contain quotes/spaces (e.g. #top-bar [data-section="wings"]).
+ // Storing the raw selector in a data-* attribute breaks the HTML (inner quotes close the
+ // attribute early) AND breaks querySelector('[data-tool="<sel>"]') ("is not a valid
+ // selector"). So we key tools by INDEX and resolve TOOLS[idx].sel in JS. (Phase-5 /5x.)
+ TOOLS.forEach(function (t, i) {
+ html += '<button class="qh-menu-item" data-tool-idx="' + i + '">' +
'<span>' + t.label + '</span><span class="qh-dot">●</span></button>';
});
menu.innerHTML = html;
@@ -221,12 +228,13 @@
menu.addEventListener('click', function (e) {
var item = e.target.closest('.qh-menu-item'); if (!item) return;
var pSel = item.getAttribute('data-panel');
- var tSel = item.getAttribute('data-tool');
+ var tIdx = item.getAttribute('data-tool-idx');
if (pSel) {
var p = PANELS.filter(function (x) { return x.sel === pSel; })[0];
if (p) toggle(p);
- } else if (tSel) {
- var btn = $(tSel);
+ } else if (tIdx !== null) {
+ var t = TOOLS[+tIdx];
+ var btn = t && $(t.sel);
if (btn) { btn.click(); setTimeout(reflectMenu, 30); }
}
});
@@ -250,8 +258,8 @@
var it = menu.querySelector('[data-panel="' + p.sel + '"]');
if (it) it.classList.toggle('active', isOpen(p));
});
- TOOLS.forEach(function (t) {
- var it = menu.querySelector('[data-tool="' + t.sel + '"]');
+ TOOLS.forEach(function (t, i) {
+ var it = menu.querySelector('[data-tool-idx="' + i + '"]'); // index key — selector may contain quotes
var btn = $(t.sel);
if (it) it.classList.toggle('active', !!btn && btn.classList.contains('active'));
});
diff --git a/scripts/clickthrough-showroom.mjs b/scripts/clickthrough-showroom.mjs
index d0c0324..fdf24dc 100644
--- a/scripts/clickthrough-showroom.mjs
+++ b/scripts/clickthrough-showroom.mjs
@@ -93,7 +93,21 @@ const TOOLS = [
}
await page.screenshot({ path: path.join(OUT, 'panels-open.png') });
+ // helper: stow every modal, then launch ONLY the named panel (so it's on top + clickable,
+ // not buried under other open modals — that stacking caused false timeouts in sweep 2).
+ async function openOnly(panelSel) {
+ await page.evaluate(() => document.querySelectorAll('.qh-modal').forEach(m => m.classList.add('qh-stowed')));
+ await page.evaluate(() => { const m = document.getElementById('qh-menu'); if (m && !m.classList.contains('open')) document.getElementById('qh-burger').click(); });
+ await sleep(150);
+ const item = await page.$(`.qh-menu-item[data-panel="${panelSel}"]`);
+ if (item) { await item.click(); await sleep(400); }
+ // close the launcher menu so it doesn't overlap the panel
+ await page.evaluate(() => { const m = document.getElementById('qh-menu'); if (m && m.classList.contains('open')) document.getElementById('qh-burger').click(); });
+ await sleep(200);
+ }
+
// 3. exercise Controls (#window-bar): sort select all options + ranges
+ await openOnly('#window-bar');
const sortSel = '#sort-select';
if (await page.$(sortSel)) {
const opts = await page.$$eval(sortSel + ' option', os => os.map(o => o.value));
@@ -118,7 +132,8 @@ const TOOLS = [
} catch (e) { note('range', `${rng} threw ${e.message.split('\n')[0]}`); }
}
- // 4. Navigate buttons
+ // 4. Navigate buttons (open only the Navigate panel so it isn't buried)
+ await openOnly('#guided-bar');
for (const b of ['#g-prev', '#g-next', '#g-tour', '#g-grid']) {
if (!(await page.$(b))) { note('nav', `${b} missing`); continue; }
try { await page.click(b, { timeout: 4000 }); await sleep(400); ok(`nav ${b} clicked`); }
@@ -128,11 +143,12 @@ const TOOLS = [
await page.evaluate(() => { const c = document.getElementById('go-close'); if (c) c.click(); });
await sleep(200);
- // 5. proxy-click each tool from the menu
- for (const t of TOOLS) {
+ // 5. proxy-click each tool from the menu (keyed by index — selectors may contain quotes)
+ for (let ti = 0; ti < TOOLS.length; ti++) {
+ const t = TOOLS[ti];
await page.evaluate(() => { const m = document.getElementById('qh-menu'); if (m && !m.classList.contains('open')) document.getElementById('qh-burger').click(); });
await sleep(150);
- const item = await page.$(`.qh-menu-item[data-tool="${t.replace(/"/g, '\\"')}"]`);
+ const item = await page.$(`.qh-menu-item[data-tool-idx="${ti}"]`);
if (!item) { note('tool:' + t, 'menu item missing'); continue; }
const before = consoleErrors.length;
try { await item.click({ timeout: 4000 }); await sleep(450); }
← 52d16fc snapshot: clickthrough script + 5x report from review pass
·
back to Quadrille Showroom
·
/5x REPORT: 2 dock.js bugs fixed, 2 clean sweeps 2bef6b4 →