[object Object]

← 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

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 →