[object Object]

← back to Agent Cabinet

proof(front-page): harden ux-primitives activation gate + dedupe

65c6777149cf3425d4c7256865db71c65d3f5bb5 · 2026-09-23 13:31:10 -0700 · Steve Abrams

Addresses the 5 review findings on the pending front-page-audit/proof change set:
- Fold SITE env filter into prove-activation.mjs and drop the byte-identical
  prove-activation-one.mjs — one gate, no silent drift between two copies.
- Tear the site server down in try/finally (+ boot-error catch) so a slow boot
  or a mid-loop throw no longer orphans a node server / leaves a stuck port.
- slowRun: bounded waitForFunction for real product children instead of the
  fixed delay+2s clock the deferred bundle could outrun (kills the false-FAIL).
- Page errors: baseline-subtract against the bundle-absent runs + BUNDLE_RX
  filter, so a site's own uncaught exception can't red the gate as a "bundle"
  failure; also record slowRun page errors in presentPageErr.
- live-ux-canary: expect marker via UX_MARKER (mirrors deploy $CANON) or a
  well-formed v<semver>#<hash> shape, not a pinned content hash that would
  red the whole fleet on the next healthy rebuild.

Smoke-tested: SITE=flockedwallpaper -> 1/1 all-engine PASS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AWMadYahw5wRTf7KcwBLr1

Files touched

Diff

commit 65c6777149cf3425d4c7256865db71c65d3f5bb5
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 23 13:31:10 2026 -0700

    proof(front-page): harden ux-primitives activation gate + dedupe
    
    Addresses the 5 review findings on the pending front-page-audit/proof change set:
    - Fold SITE env filter into prove-activation.mjs and drop the byte-identical
      prove-activation-one.mjs — one gate, no silent drift between two copies.
    - Tear the site server down in try/finally (+ boot-error catch) so a slow boot
      or a mid-loop throw no longer orphans a node server / leaves a stuck port.
    - slowRun: bounded waitForFunction for real product children instead of the
      fixed delay+2s clock the deferred bundle could outrun (kills the false-FAIL).
    - Page errors: baseline-subtract against the bundle-absent runs + BUNDLE_RX
      filter, so a site's own uncaught exception can't red the gate as a "bundle"
      failure; also record slowRun page errors in presentPageErr.
    - live-ux-canary: expect marker via UX_MARKER (mirrors deploy $CANON) or a
      well-formed v<semver>#<hash> shape, not a pinned content hash that would
      red the whole fleet on the next healthy rebuild.
    
    Smoke-tested: SITE=flockedwallpaper -> 1/1 all-engine PASS.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01AWMadYahw5wRTf7KcwBLr1
---
 front-page-audit/proof/live-ux-canary.mjs   | 67 +++++++++++++++++++++++++++++
 front-page-audit/proof/prove-activation.mjs | 50 ++++++++++++++++-----
 2 files changed, 106 insertions(+), 11 deletions(-)

diff --git a/front-page-audit/proof/live-ux-canary.mjs b/front-page-audit/proof/live-ux-canary.mjs
new file mode 100644
index 0000000..756f917
--- /dev/null
+++ b/front-page-audit/proof/live-ux-canary.mjs
@@ -0,0 +1,67 @@
+// live-ux-canary.mjs — READ-ONLY post-deploy canary for the UX-primitives fleet rollout (TK-12031).
+// For each LIVE site: load in a real browser, sample the grid every 50ms during load to catch the
+// skeleton window, then assert after settle: UXGrid initialised, skeletons=0, products>0, empty=0,
+// sort <select> + density <input type=range> present, no bundle-attributable console/page errors.
+// Usage: node live-ux-canary.mjs <sites-file> [engine=chromium|webkit] [out.json]
+import { createRequire } from 'module';
+const require = createRequire(import.meta.url);
+process.env.NODE_PATH = '/Users/macstudio3/.npm-global/lib/node_modules';
+require('module').Module._initPaths();
+const { chromium, webkit } = require('playwright');
+import fs from 'fs';
+const [,, sitesFile, engineName='chromium', outFile=`live-canary-${new Date().toISOString().replace(/[:.]/g,'-')}.json`] = process.argv;
+const sites = fs.readFileSync(sitesFile,'utf8').split('\n').map(s=>s.trim()).filter(Boolean);
+const BUNDLE_RX = /ux-primitives|UXGrid|ModalRig|Bento|CommandPalette|Toast|Skeleton|EmptyState|copyToClipboard/i;
+// Expected bundle marker. Set UX_MARKER to pin a specific build (mirror the deploy script's $CANON as the
+// single source of truth) when you want exact-match; otherwise accept any well-formed marker so a routine
+// rebuild — which rotates the content hash — doesn't red the whole fleet with a perfectly healthy bundle.
+const EXPECT_MARKER = process.env.UX_MARKER || null;
+const MARKER_RX = /^ux-primitives\.bundle v\d+\.\d+\.\d+#[0-9a-f]{6,}$/;
+const engine = engineName==='webkit'?webkit:chromium;
+const browser = await engine.launch({ headless:true });
+const out=[];
+for (const site of sites){
+  const url = `https://${site}.com/`;
+  const ctx = await browser.newContext({ viewport:{width:1280,height:900}, ignoreHTTPSErrors:true });
+  const page = await ctx.newPage();
+  const consoleErr=[], pageErr=[];
+  page.on('console', m=>{ if(m.type()==='error') consoleErr.push(m.text().slice(0,200)); });
+  page.on('pageerror', e=>pageErr.push(String(e.message||e).slice(0,200)));
+  // block third-party ad/analytics noise so it can't mask or delay the measurement
+  await page.route(/googlesyndication|doubleclick|google-analytics|googletagmanager|ads\.agentabrams|facebook|tiktok/i, r=>r.abort());
+  const rec={ site, url, engine:engineName };
+  try{
+    const t0=Date.now();
+    const nav = page.goto(url,{ waitUntil:'domcontentloaded', timeout:45000 });
+    let maxSkel=0, samples=0;
+    const sampler=(async()=>{ while(Date.now()-t0<12000){ try{ const n=await page.evaluate(()=>{const g=document.querySelector('[data-ux-grid]'); return g?g.querySelectorAll('[data-ux-skel-holder],[data-ux-skel],.ux-skel').length:-1;}); if(n>maxSkel) maxSkel=n; samples++; }catch{} await new Promise(r=>setTimeout(r,50)); } })();
+    await nav;
+    await page.waitForLoadState('load',{timeout:45000}).catch(()=>{});
+    await sampler;
+    await page.waitForTimeout(1500);
+    const st = await page.evaluate(()=>{
+      const g=document.querySelector('[data-ux-grid]');
+      const marker=(document.documentElement.outerHTML.match(/ux-primitives\.bundle v[0-9.]+#[0-9a-f]+/)||[null])[0];
+      const q=s=>g?g.querySelectorAll(s).length:-1;
+      const products=g?[...g.children].filter(c=>c.nodeType===1&&!c.matches('[data-ux-skel-holder],[data-ux-skel],[data-ux-empty-holder]')&&!c.classList.contains('ux-skel')).length:-1;
+      return { hasGrid:!!g, marker, skel:q('[data-ux-skel-holder],[data-ux-skel],.ux-skel'), empty:q('[data-ux-empty-holder]'), products,
+        ux: typeof window.UXGrid, sort: !!document.querySelector('select[id*=sort i],select[name*=sort i],#sort,[data-sort]'), density: !!document.querySelector('input[type=range]'),
+        title: document.title.slice(0,60) };
+    });
+    Object.assign(rec, st, { maxSkelDuringLoad:maxSkel, samples });
+    rec.bundleErrors=[...consoleErr,...pageErr].filter(x=>BUNDLE_RX.test(x));
+    rec.otherErrors=[...consoleErr,...pageErr].filter(x=>!BUNDLE_RX.test(x)).length;
+    rec.pageErrorsAll=pageErr.length;
+    rec.criteria={ uxInit: st.ux==='object', markerCanonical: EXPECT_MARKER ? (st.marker===EXPECT_MARKER) : (typeof st.marker==='string' && MARKER_RX.test(st.marker)), gridWired: st.hasGrid,
+      skeletonSeen: maxSkel>0, skeletonCleared: st.skel===0, productsRender: st.products>0, noEmptyWhenProducts: st.empty===0,
+      controls: st.sort && st.density, noBundleErr: rec.bundleErrors.length===0 };
+    // skeletonSeen is INFORMATIONAL on live (a fast CDN load can legitimately beat the 50ms sampler); it does not gate pass
+    const {skeletonSeen, ...gating}=rec.criteria; rec.pass=Object.values(gating).every(Boolean);
+  }catch(e){ rec.error=String(e.message||e).slice(0,200); rec.pass=false; }
+  await ctx.close();
+  console.log(`${site}: pass=${rec.pass} ux=${rec.ux} marker=${rec.marker} skelMax=${rec.maxSkelDuringLoad} skel=${rec.skel} products=${rec.products} empty=${rec.empty} sort=${rec.sort} density=${rec.density} bundleErr=${JSON.stringify(rec.bundleErrors)} ${rec.error?'ERR='+rec.error:''}`);
+  out.push(rec);
+}
+await browser.close();
+fs.writeFileSync(outFile, JSON.stringify(out,null,1));
+const p=out.filter(r=>r.pass).length; console.log(`\nSUMMARY ${engineName}: ${p}/${out.length} pass -> ${outFile}`);
diff --git a/front-page-audit/proof/prove-activation.mjs b/front-page-audit/proof/prove-activation.mjs
index 511aeaf..3f77c7c 100644
--- a/front-page-audit/proof/prove-activation.mjs
+++ b/front-page-audit/proof/prove-activation.mjs
@@ -11,13 +11,18 @@ const ISO = new Date().toISOString().replace(/[:.]/g, '-');
 const EVID = path.join(HOME, 'Projects/agent-cabinet/front-page-audit/proof');
 fs.mkdirSync(EVID, { recursive: true });
 
-const PILOTS = [
+const PILOTS_ALL = [
   { site: 'silkwallpaper',    mode: 'server', port: 9841, shape: 'items' },
   { site: 'linenwallpaper',   mode: 'server', port: 9842, shape: 'items' },
   { site: 'jutewallpaper',    mode: 'server', port: 9843, shape: 'items' },
   { site: 'corkwallcovering', mode: 'server', port: 9844, shape: 'items' },
   { site: 'flockedwallpaper', mode: 'static', port: 9845, shape: 'array' },
+  { site: '1800swallpaper',   mode: 'server', port: 9846, shape: 'items', forceMock: true, rep: true },
 ];
+// SITE=silkwallpaper,flockedwallpaper narrows the run to a subset (folded in from the former
+// prove-activation-one.mjs so there is exactly ONE copy of this gate — no drift between two files).
+const __only = (process.env.SITE || '').split(',').map(s => s.trim()).filter(Boolean);
+const PILOTS = __only.length ? PILOTS_ALL.filter(p => __only.includes(p.site)) : PILOTS_ALL;
 const BORROW_NM = path.join(HOME, 'Projects/silkwallpaper/node_modules');
 const MIME = { '.html':'text/html','.js':'text/javascript','.css':'text/css','.json':'application/json','.svg':'image/svg+xml','.png':'image/png','.jpg':'image/jpeg','.webp':'image/webp','.ico':'image/x-icon','.woff2':'font/woff2' };
 
@@ -70,6 +75,9 @@ async function run(engineLauncher, url, shape, opts) {
   const errs=[], perrs=[];
   const ctx = await browser.newContext();
   const page = await ctx.newPage();
+  // Abort third-party ad/tracking noise (404/502 under localhost) that otherwise delays the deferred
+  // bundle so far that products load first and uxInit misreads false. Isolates the bundle fairly.
+  await page.route(/ads\.agentabrams|googletagmanager|google-analytics|doubleclick|facebook\.|fbcdn|fburl|connect\.facebook|hotjar|segment\.|mixpanel|\/embed\.js/, r => r.abort().catch(()=>{}));
   if (opts.gridTimeout) await page.addInitScript(t => { window.__uxGridTimeout = t; }, opts.gridTimeout);
   if (opts.absent) await page.route(/ux-primitives\.bundle\.(js|css)(\?|$)/, r => r.fulfill({ status:200, contentType: r.request().url().endsWith('.css')?'text/css':'text/javascript', body:'' }));
   if (opts.api) await page.route(/\/api\/products(\?|$)/, async r => {
@@ -87,6 +95,15 @@ async function run(engineLauncher, url, shape, opts) {
     await page.goto(url, { waitUntil:'domcontentloaded', timeout:20000 });
     if (opts.sampleDuringMs) { await page.waitForTimeout(opts.sampleDuringMs); rec.during = await page.evaluate(measure); }
     await page.waitForTimeout(opts.settleMs || 2500);
+    // Slow-load cases: don't measure on a fixed clock the DEFERRED bundle can outrun. goto resolves at
+    // domcontentloaded (before the deferred bundle runs), so the fetch start is offset from t0 by an
+    // unbounded amount under load. Wait (bounded) for products to actually render so a late-but-successful
+    // load isn't misread as a strand; on timeout we still measure (a genuine strand stays FAIL).
+    if (opts.awaitProductsMs) await page.waitForFunction(() => {
+      const g = document.querySelector('#grid') || document.querySelector('#productGrid');
+      if (!g) return false;
+      return [...g.children].some(c => c.nodeType===1 && !c.matches('[data-ux-skel-holder],[data-ux-skel],[data-ux-empty-holder]') && !c.classList.contains('ux-skel'));
+    }, { timeout: opts.awaitProductsMs, polling: 100 }).catch(()=>{});
     rec.after = await page.evaluate(measure);
     if (opts.doSort) {
       await page.evaluate(() => { const s=document.querySelector('select'); if(s&&s.options.length>1){ s.selectedIndex=Math.min(1,s.options.length-1); s.dispatchEvent(new Event('change',{bubbles:true})); } const rg=document.querySelector('input[type=range]'); if(rg){ rg.dispatchEvent(new Event('input',{bubbles:true})); } });
@@ -101,12 +118,14 @@ async function run(engineLauncher, url, shape, opts) {
 }
 
 const results = [];
+const teardown = (h) => { try { if (h?.kill) h.kill(); else if (h?.close) h.close(); } catch {} };
 for (const p of PILOTS) {
   let handle;
   try { if (p.mode==='server'){ handle=startServer(p); await waitPort(p.port); } else handle=await startStatic(p); }
-  catch(e){ results.push({site:p.site, bootError:String(e)}); continue; }
+  catch(e){ teardown(handle); results.push({site:p.site, bootError:String(e)}); continue; }
+  try {
   const url = `http://127.0.0.1:${p.port}/`;
-  const apiLoad = p.mode==='server' ? { mode:'continue' } : { mode:'mock24' };   // real data vs mock for flocked
+  const apiLoad = p.forceMock ? { mode:'mock24' } : (p.mode==='server' ? { mode:'continue' } : { mode:'mock24' });   // mock for flocked + representative sites (isolate bundle from site data pipeline)
   const rec = { site:p.site, mode:p.mode, engines:{} };
   for (const [en, la] of [['chromium',chromium],['webkit',webkit]]) {
     // error baseline (bundle absent), 2 normal runs
@@ -118,30 +137,39 @@ for (const p of PILOTS) {
     const loading = await run(la,url,p.shape,{ api:{ ...apiLoad, delay:1500 }, sampleDuringMs:600, settleMs:3500 });
     // present true-zero empty (short grid timeout + zero API)
     const emptyRun = await run(la,url,p.shape,{ api:{ mode:'zero' }, gridTimeout:1200, settleMs:2600 });
+    // slow-but-NONEMPTY load resolving PAST the 8s skel timeout — empty-state must NOT strand (Cody v1.1.1 fix)
+    const slowRun = await run(la,url,p.shape,{ api:{ mode:'mock24', delay:10000 }, settleMs:3000, awaitProductsMs:14000 });
 
-    const presentErrs = [normal, loading, emptyRun].flatMap(r=>r.jsErrors.map(norm));
+    const presentErrs = [normal, loading, emptyRun, slowRun].flatMap(r=>r.jsErrors.map(norm));
     const newErrs = [...new Set(presentErrs)].filter(x=>!baseSet.has(x));
     const BUNDLE_RX=/ux-primitives|UXPrimitives|UXGrid|\bToast\b|\bBento\b|ModalRig|Skeleton|EmptyState|CommandPalette/;
     const bundleNew = newErrs.filter(x=>BUNDLE_RX.test(x));
-    const anyPageErr = [normal,loading,emptyRun].some(r=>r.pageErrors.length);
+    // Page errors: isolate to the bundle the same way console errors are — subtract the bundle-absent
+    // baseline, then keep only bundle-attributable ones, so a site's OWN uncaught exception can't red the
+    // activation gate under a criterion (noBundlePageErr) whose name asserts the bundle caused it.
+    const basePageSet = new Set(absent.flatMap(r=>r.pageErrors.map(norm)));
+    const newPageErrs = [...new Set([normal,loading,emptyRun,slowRun].flatMap(r=>r.pageErrors.map(norm)))].filter(x=>!basePageSet.has(x));
+    const bundlePageErr = newPageErrs.filter(x=>BUNDLE_RX.test(x));
+    const anyPageErr = bundlePageErr.length>0;
 
     const appearThenClear = (loading.during?.skel>0) && (loading.after?.skel===0) && (loading.after?.products>0);
     const productsRender = normal.after?.products>0;
     const sortNoGhost = normal.afterSort ? (normal.afterSort.skel===0 && normal.afterSort.empty===0 && normal.afterSort.products>0) : false;
     const trueZeroEmpty = (emptyRun.after?.empty===1) && (emptyRun.after?.skel===0) && (emptyRun.after?.products===0);
     const noEmptyWhenProducts = normal.after?.empty===0;
+    const slowLoadNoStrand = (slowRun.after?.empty===0) && (slowRun.after?.products>0) && (slowRun.after?.skel===0);
     const controls = normal.after?.selects>=1 && normal.after?.ranges>=1;
 
-    const pass = appearThenClear && productsRender && sortNoGhost && trueZeroEmpty && noEmptyWhenProducts && controls && !anyPageErr && bundleNew.length===0 && normal.ux==='object';
-    rec.engines[en] = { pass, criteria:{ appearThenClear, productsRender, sortNoGhost, trueZeroEmpty, noEmptyWhenProducts, controls, noBundlePageErr:!anyPageErr, noNewBundleErr:bundleNew.length===0, uxInit:normal.ux==='object' },
-      numbers: { during:loading.during, loadingAfter:loading.after, normalAfter:normal.after, afterSort:normal.afterSort, emptyAfter:emptyRun.after },
-      newBundleErrors: bundleNew, flakySiteNoise: newErrs.filter(x=>!BUNDLE_RX.test(x)),
-      absentErrRuns: absent.map(r=>r.jsErrors.length), presentPageErr: [normal,loading,emptyRun].map(r=>r.pageErrors.length) };
+    const pass = appearThenClear && productsRender && sortNoGhost && trueZeroEmpty && noEmptyWhenProducts && slowLoadNoStrand && controls && !anyPageErr && bundleNew.length===0 && normal.ux==='object';
+    rec.engines[en] = { pass, criteria:{ appearThenClear, productsRender, sortNoGhost, trueZeroEmpty, noEmptyWhenProducts, slowLoadNoStrand, controls, noBundlePageErr:!anyPageErr, noNewBundleErr:bundleNew.length===0, uxInit:normal.ux==='object' },
+      numbers: { during:loading.during, loadingAfter:loading.after, normalAfter:normal.after, afterSort:normal.afterSort, emptyAfter:emptyRun.after, slowAfter:slowRun.after },
+      newBundleErrors: bundleNew, newBundlePageErrors: bundlePageErr, flakySiteNoise: newErrs.filter(x=>!BUNDLE_RX.test(x)),
+      absentErrRuns: absent.map(r=>r.jsErrors.length), presentPageErr: [normal,loading,emptyRun,slowRun].map(r=>r.pageErrors.length) };
   }
   results.push(rec);
-  if (handle.kill) handle.kill(); else if (handle.close) handle.close();
   const e = rec.engines;
   console.log(`${p.site}: ch.pass=${e.chromium?.pass} wk.pass=${e.webkit?.pass} | during.skel=${e.chromium?.numbers.during?.skel} after.skel=${e.chromium?.numbers.loadingAfter?.skel} products=${e.chromium?.numbers.normalAfter?.products} afterSort.skel=${e.chromium?.numbers.afterSort?.skel} emptyAfter=${JSON.stringify(e.chromium?.numbers.emptyAfter)} bundleErr=${JSON.stringify(e.chromium?.newBundleErrors)}`);
+  } finally { teardown(handle); }   // always tear the child down — no orphaned server / stuck port on any path
 }
 const outfile = path.join(EVID, `pilot-activation-${ISO}.json`);
 fs.writeFileSync(outfile, JSON.stringify(results,null,2));

← f4b8079 auto-data-snapshot: 2026-09-23T13:13:50 (1 data files) — sho  ·  back to Agent Cabinet  ·  proof(live-ux-canary): gate marker against the bundle hash c 56486bd →