← back to Dw Pairs Well
5x sweep 1: pairs-well-specific clickthrough (shared 3x clickthrough is hardcoded for the curator UI); sweep-1 ledger
8b2123a2ee2b9fcf47eb36dcc7c53d801635637f · 2026-07-13 01:15:32 -0700 · Steve Abrams
Files touched
A 5x/clickthrough.mjsA 5x/sweep-1.md
Diff
commit 8b2123a2ee2b9fcf47eb36dcc7c53d801635637f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Jul 13 01:15:32 2026 -0700
5x sweep 1: pairs-well-specific clickthrough (shared 3x clickthrough is hardcoded for the curator UI); sweep-1 ledger
---
5x/clickthrough.mjs | 94 +++++++++++++++++++++++++++++++++++++++++++++++++++++
5x/sweep-1.md | 23 +++++++++++++
2 files changed, 117 insertions(+)
diff --git a/5x/clickthrough.mjs b/5x/clickthrough.mjs
new file mode 100644
index 0000000..1c6d0ed
--- /dev/null
+++ b/5x/clickthrough.mjs
@@ -0,0 +1,94 @@
+#!/usr/bin/env node
+// pairs-well clickthrough — exercises the REAL preview.html controls (the shared
+// 3x clickthrough.js is hardcoded for the wallco curator UI and can't test this app).
+// Asserts: sample-SKU buttons populate the coordinate grid, #sku+#load loads a product,
+// paint-brand checkboxes toggle without errors, every rendered product link is a
+// /products/<handle> URL, and ZERO console/page errors throughout.
+// Usage: node 5x/clickthrough.mjs [--url http://127.0.0.1:9821/preview.html]
+import path from 'node:path';
+import os from 'node:os';
+import { createRequire } from 'node:module';
+const require = createRequire(import.meta.url);
+
+const URL_ = process.argv.includes('--url')
+ ? process.argv[process.argv.indexOf('--url') + 1]
+ : 'http://127.0.0.1:9821/preview.html';
+
+function resolvePlaywright() {
+ for (const c of [
+ path.join(os.homedir(), 'Projects/Designer-Wallcoverings/node_modules/playwright'),
+ path.join(os.homedir(), 'node_modules/playwright'), 'playwright',
+ ]) { try { return require(c); } catch {} }
+ return null;
+}
+
+const pw = resolvePlaywright();
+if (!pw) { console.error('playwright not installed'); process.exit(2); }
+
+const CHROME = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
+let passed = 0, failed = 0;
+const errors = [];
+
+const browser = await pw.chromium.launch({ executablePath: CHROME, headless: true });
+const page = await (await browser.newContext()).newPage();
+page.on('console', m => { if (m.type() === 'error') errors.push(m.text()); });
+page.on('pageerror', e => errors.push(String(e)));
+
+async function step(name, fn) {
+ const before = errors.length;
+ try {
+ await fn();
+ if (errors.length > before) throw new Error(`console error: ${errors[errors.length - 1]}`.slice(0, 160));
+ console.log(` [PASS] ${name}`); passed++;
+ } catch (e) {
+ console.log(` [FAIL] ${name} — ${String(e.message || e).slice(0, 140)}`); failed++;
+ }
+}
+
+console.log(`\npairs-well clickthrough → ${URL_}\n`);
+await page.goto(URL_, { waitUntil: 'domcontentloaded', timeout: 20000 });
+
+await step('page load, no console errors', async () => {
+ await page.waitForTimeout(1500);
+});
+
+await step('sample SKU chip + Design-Coordinate CTA expand → grid populates', async () => {
+ await page.locator('button[data-sku]').first().click();
+ // grid renders lazily behind the collapsed CTA — expand like a real shopper would
+ const cta = page.locator('#pairs-well-with .pairs-well__cta');
+ if ((await cta.getAttribute('aria-expanded')) !== 'true') await cta.click();
+ await page.waitForFunction(() => {
+ const g = document.getElementById('catalog-grid');
+ return g && !g.hidden && g.querySelectorAll('a[href*="/products/"], img').length > 0;
+ }, null, { timeout: 25000 });
+});
+
+await step('all rendered product links are /products/<handle>', async () => {
+ const bad = await page.$$eval('#pairs-well-with a[href]', as =>
+ as.map(a => a.getAttribute('href')).filter(h => h && !/\/products\/[a-z0-9]/i.test(h)));
+ if (bad.length) throw new Error(`non-product hrefs: ${bad.slice(0, 3).join(', ')}`);
+});
+
+await step('#sku + Load → grid reloads for typed SKU', async () => {
+ await page.fill('#sku', 'BGA-47951');
+ await page.locator('#load').click();
+ await page.waitForFunction(() => {
+ const t = document.querySelector('#product-title');
+ return t && t.textContent && t.textContent.trim().length > 3;
+ }, null, { timeout: 25000 });
+});
+
+await step('paint-brand checkbox toggles cleanly', async () => {
+ const cb = page.locator('input[type=checkbox][value=sw]');
+ await cb.click(); await page.waitForTimeout(400); await cb.click();
+});
+
+await step('second sample chip → grid swaps, still error-free', async () => {
+ await page.locator('button[data-sku]').nth(3).click();
+ await page.waitForTimeout(2500);
+});
+
+await browser.close();
+console.log(`\n CLICK-THROUGH → ${passed} passed, ${failed} failed · ${errors.length} console/page errors total`);
+if (errors.length) console.log(' errors: ' + errors.slice(0, 4).join(' | ').slice(0, 300));
+process.exit(failed ? 1 : 0);
diff --git a/5x/sweep-1.md b/5x/sweep-1.md
new file mode 100644
index 0000000..2d9b096
--- /dev/null
+++ b/5x/sweep-1.md
@@ -0,0 +1,23 @@
+# 5x sweep 1 — 2026-07-13
+
+Target: http://127.0.0.1:9821/preview.html (PAIRS_CLIP_DEFAULT=1, visual-search :9914 up)
+
+## Verify (3x six-way)
+- [PASS] M1 HTTP contract (200 text/html)
+- [PASS] M2 headless render (574KB screenshot)
+- [PASS] M3 automation E2E (0 JS errors)
+- [PASS] B4 Chrome / [PASS] B5 Safari / [PASS] B6 Firefox
+- [FAIL] Phase C clickthrough: 0/10 — ALL timeouts
+
+## Caught
+1. Phase-C failure is a HARNESS MISMATCH, not a build defect: the shared
+ ~/.claude/skills/3x/clickthrough.js is hardcoded for the wallco design-curator UI
+ (#fSearch, .vbtn.dw/.wpb/.both/.skip, Board toggle, density slider) — none of those
+ selectors exist on pairs-well's preview.html, so every step timed out. The six real
+ verification layers all passed with zero console errors.
+
+## Fixed
+- Wrote 5x/clickthrough.mjs — a pairs-well-specific clickthrough that exercises the
+ ACTUAL controls (#sku, #load, button[data-sku] sample chips, paint-brand checkboxes)
+ and additionally asserts every rendered product link matches /products/<handle>.
+ Sweeps 2+ use: 3x runner (six-way) + this script as Phase C.
← 263b80d audit: pipe-title backfill generator for malformed '| Vendor
·
back to Dw Pairs Well
·
5x sweeps 2-3 clean (2 consecutive) — REPORT.md; stop condit 5a3ae69 →