← back to CelebritySignatures
wear: fix "See on garment" not hiding the editorial worn shot + modernize gallery E2E
d72fd54089327bd97995b8a6c87f8c69df2fcd2f · 2026-09-13 11:51:43 -0700 · Steve
- wear.html: #lifestyleShot had display:block with no [hidden] guard, so
setLifeView("garment") (.hidden=true) never actually hid the seated
Signature Edit portrait — for all 50+ mapped signers, "See on garment"
left the worn shot covering the garment mockup/3D view. Add
#lifestyleShot[hidden]{display:none}.
- gallery-shopping.e2e.cjs: port the retired murals journey to the shipped
/wallpaper studio (commit 3d9677d), expect all 9 garments, correct the
sale state (dad/trucker/bucket/socks are for sale; the two AOP shirts are
the coming-soon holds), cover the worn-shot -> garment toggle, screenshot
the always-visible .preview instead of the now-hidden #stage.
- 5x/ink-gate.mjs: supplementary CDP E2E for the modal ink control (PASS).
Both viewports PASS, zero page errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VAzDMrVAjLD4umvnMMStjD
Files touched
A 5x/ink-gate.mjsM public/wear.htmlM verification/gallery-shopping.e2e.cjs
Diff
commit d72fd54089327bd97995b8a6c87f8c69df2fcd2f
Author: Steve <steve@designerwallcoverings.com>
Date: Sun Sep 13 11:51:43 2026 -0700
wear: fix "See on garment" not hiding the editorial worn shot + modernize gallery E2E
- wear.html: #lifestyleShot had display:block with no [hidden] guard, so
setLifeView("garment") (.hidden=true) never actually hid the seated
Signature Edit portrait — for all 50+ mapped signers, "See on garment"
left the worn shot covering the garment mockup/3D view. Add
#lifestyleShot[hidden]{display:none}.
- gallery-shopping.e2e.cjs: port the retired murals journey to the shipped
/wallpaper studio (commit 3d9677d), expect all 9 garments, correct the
sale state (dad/trucker/bucket/socks are for sale; the two AOP shirts are
the coming-soon holds), cover the worn-shot -> garment toggle, screenshot
the always-visible .preview instead of the now-hidden #stage.
- 5x/ink-gate.mjs: supplementary CDP E2E for the modal ink control (PASS).
Both viewports PASS, zero page errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VAzDMrVAjLD4umvnMMStjD
---
5x/ink-gate.mjs | 55 +++++++++++++++++++++++++++
public/wear.html | 1 +
verification/gallery-shopping.e2e.cjs | 70 ++++++++++++++++++++++++++++-------
3 files changed, 112 insertions(+), 14 deletions(-)
diff --git a/5x/ink-gate.mjs b/5x/ink-gate.mjs
new file mode 100644
index 0000000..af159cf
--- /dev/null
+++ b/5x/ink-gate.mjs
@@ -0,0 +1,55 @@
+// Supplementary E2E for the modal-gated SIGNATURE INK control, via CDP.
+// Opens the /wear product modal, asserts the ink chips populate and are visible,
+// clicks a preset and Auto, and collects any console/page errors. Exit 0 = pass.
+import { spawn } from 'child_process';
+const PORT = process.argv[2]; const DEBUG = 9337;
+const CHROME = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
+const proc = spawn(CHROME, ['--headless=new','--disable-gpu',`--remote-debugging-port=${DEBUG}`,`--user-data-dir=/tmp/inkgate-${process.pid}`], {stdio:'ignore'});
+await new Promise(r=>setTimeout(r,2000));
+const errors = [];
+try {
+ const list = await (await fetch(`http://localhost:${DEBUG}/json/list`)).json();
+ const tgt = list.find(t=>t.type==='page');
+ const ws = new WebSocket(tgt.webSocketDebuggerUrl);
+ let id=0; const pending=new Map();
+ const send=(m,p={})=>new Promise(r=>{const i=++id;pending.set(i,r);ws.send(JSON.stringify({id:i,method:m,params:p}));});
+ await new Promise(r=>ws.onopen=r);
+ ws.onmessage=e=>{const m=JSON.parse(e.data);
+ if(m.method==='Runtime.consoleAPICalled'&&m.params.type==='error')errors.push('console:'+m.params.args.map(a=>a.value||a.description||'').join(' '));
+ if(m.method==='Runtime.exceptionThrown')errors.push('pageerror:'+(m.params.exceptionDetails?.exception?.description||m.params.exceptionDetails?.text||''));
+ if(m.id&&pending.has(m.id))pending.get(m.id)(m.result),pending.delete(m.id);};
+ await send('Page.enable'); await send('Runtime.enable');
+ await send('Page.navigate',{url:`http://localhost:${PORT}/wear`});
+ await new Promise(r=>setTimeout(r,4500));
+ const script=`(()=>{const first=document.querySelector('.card');if(first)first.click();
+ return new Promise(res=>setTimeout(()=>{
+ const ink=document.getElementById('inkChips');
+ const swatches=[...ink.querySelectorAll('[data-ink]')];
+ const burg=ink.querySelector('[data-ink="#6e1f2e"]'); if(burg)burg.click();
+ setTimeout(()=>{
+ const auto=ink.querySelector('[data-ink="auto"]');
+ const afterBurg=window.__wearInk; // not exposed; read selected class instead
+ const burgSel=ink.querySelector('[data-ink="#6e1f2e"]')?.classList.contains('sel');
+ if(auto)auto.click();
+ setTimeout(()=>{
+ const autoSel=ink.querySelector('[data-ink="auto"]')?.classList.contains('sel');
+ res(JSON.stringify({modalOpen:document.getElementById('scrim').classList.contains('on'),chips:swatches.length,inkVisible:ink.offsetParent!==null,burgSelectable:!!burg,burgSel,autoSel}));
+ },400);
+ },500);
+ },1200));})()`;
+ const r=await send('Runtime.evaluate',{expression:script,awaitPromise:true,returnByValue:true});
+ const st=JSON.parse(r.result.value);
+ ws.close();
+ const fails=[];
+ if(!st.modalOpen)fails.push('modal did not open on card click');
+ if(st.chips<9)fails.push(`only ${st.chips} ink chips (expected >=9)`);
+ if(!st.inkVisible)fails.push('ink chips not visible');
+ if(!st.burgSelectable)fails.push('burgundy preset missing');
+ if(!st.burgSel)fails.push('burgundy preset did not select');
+ if(!st.autoSel)fails.push('Auto did not re-select');
+ if(errors.length)fails.push('JS errors: '+errors.join(' | '));
+ console.log('INK-GATE state:',JSON.stringify(st));
+ if(fails.length){console.log('INK-GATE FAIL:',fails.join('; '));process.exitCode=1;}
+ else console.log('INK-GATE PASS');
+} catch(e){ console.log('INK-GATE ERROR:',e.message); process.exitCode=2; }
+finally { proc.kill(); }
diff --git a/public/wear.html b/public/wear.html
index d60be12..6569a83 100644
--- a/public/wear.html
+++ b/public/wear.html
@@ -99,6 +99,7 @@
.view3d-btn:hover { border-color:var(--ink); }
/* Editorial "worn" shot — the celebrity seated in the full Signature Edit. */
#lifestyleShot { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; display:block; }
+ #lifestyleShot[hidden] { display:none; } /* the explicit display:block above defeats the [hidden] default — restore it so "See on garment" actually reveals the mockup */
.lifestyle-toggle { position:absolute; top:14px; right:14px; z-index:6; font:inherit; font-size:12px; background:rgba(255,255,255,.92); border:1px solid var(--line); border-radius:999px; padding:6px 12px; cursor:pointer; }
.lifestyle-toggle:hover { border-color:var(--ink); }
@media (max-width:720px){ .preview{ border-radius:16px 16px 0 0; } }
diff --git a/verification/gallery-shopping.e2e.cjs b/verification/gallery-shopping.e2e.cjs
index 38e36b1..234e15b 100644
--- a/verification/gallery-shopping.e2e.cjs
+++ b/verification/gallery-shopping.e2e.cjs
@@ -23,11 +23,16 @@ const checks = [];
const page = await context.newPage();
const errors = [];
page.on('pageerror', e => errors.push(e.message));
+ // All three initial catalog responses can redraw cards independently.
+ const galleryReady = Promise.all(['/api/signatures', '/api/signature-evolution', '/api/portraits'].map(endpoint =>
+ page.waitForResponse(r => new URL(r.url()).pathname === endpoint).then(r => r.finished())));
await page.goto(base+'/?cat=Politics', { waitUntil:'domcontentloaded' });
+ await galleryReady;
+ await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))));
const card = page.locator('.card[data-qid="Q91"]');
await card.waitFor();
assert.equal(await page.locator('.gnav a[href="/wear"]').count(), 1);
- assert.ok(await card.locator('.shop-mural').isVisible());
+ assert.ok(await card.locator('.shop-wallpaper').isVisible());
assert.ok(await card.locator('.shop-clothing').isVisible());
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), true);
await card.scrollIntoViewIfNeeded();
@@ -38,29 +43,50 @@ const checks = [];
await page.locator('#scrim.on').waitFor();
assert.equal(await page.locator('#pName').textContent(), 'Abraham Lincoln');
assert.equal(new URL(page.url()).searchParams.get('qid'), 'Q91');
- assert.equal(await page.locator('#garments .chip').count(), 7);
+ assert.deepEqual(await page.locator('#garments .chip').allTextContents(), ['Classic Tee', 'Polo', 'Pocket Tee', 'Dad Cap', 'Trucker Cap', 'Bucket Hat', 'Crew Socks', 'Signature Camp Shirt', 'Signature Long-Sleeve']);
await page.locator('#garments [data-g="polo"]').click();
await page.locator('#colors [data-c="black"]').click();
await page.locator('#sizes [data-z="2XL"]').click();
assert.match(await page.locator('#price').textContent(), /48\.00/);
await page.waitForFunction(() => photoCache[state.color.photo]?.naturalWidth > 0 && sigImg?.naturalWidth > 0);
await page.screenshot({ path:path.join(artifacts, `${name}-clothing.png`) });
+ // The four accessories are now for sale: One size, real price, purchasable, no shirt-only 3D toggle.
+ const accessoryPrice = { 'dad-cap':'$29.00', 'trucker-cap':'$33.00', 'bucket-hat':'$38.00', 'crew-socks':'$28.00' };
for (const id of ['dad-cap', 'trucker-cap', 'bucket-hat', 'crew-socks']) {
await page.locator(`#garments [data-g="${id}"]`).click();
await page.waitForFunction(id => state.garment.id === id && photoCache[state.color.photo]?.naturalWidth > 0, id);
assert.equal(await page.locator('#sizes .chip').textContent(), 'One size');
+ assert.equal(await page.locator('#buy').isDisabled(), false);
+ assert.notEqual(await page.locator('#buy').textContent(), 'Coming soon');
+ assert.equal(await page.locator('#price').textContent(), accessoryPrice[id]);
+ assert.equal(await page.locator('#view3dToggle').isVisible(), false);
+ assert.match(await page.locator('#productNote').textContent(), /front of the (cap|hat)|both socks/);
+ if (id === 'crew-socks') assert.match(await page.locator('#sizeNote').textContent(), /6–10/);
+ await page.locator('.preview').screenshot({ path:path.join(artifacts, `${name}-${id}-preview.png`) });
+ await page.locator('#buy').scrollIntoViewIfNeeded();
+ await page.screenshot({ path:path.join(artifacts, `${name}-${id}-options.png`) });
+ }
+ // The all-over shirts remain coming-soon holds: multi-size, no price, placement-preview note.
+ for (const id of ['aop-camp-shirt', 'aop-longsleeve-shirt']) {
+ await page.locator(`#garments [data-g="${id}"]`).click();
+ await page.waitForFunction(id => state.garment.id === id && photoCache[state.color.photo]?.naturalWidth > 0, id);
+ assert.ok(await page.locator('#sizes .chip').count() > 1);
assert.equal(await page.locator('#buy').isDisabled(), true);
assert.equal(await page.locator('#buy').textContent(), 'Coming soon');
assert.equal(await page.locator('#price').textContent(), 'Coming soon');
assert.equal(await page.locator('#view3dToggle').isVisible(), false);
- assert.match(await page.locator('#productNote').textContent(), /Placement preview/);
- if (id === 'crew-socks') assert.match(await page.locator('#sizeNote').textContent(), /6–10/);
- await page.locator('#stage').screenshot({ path:path.join(artifacts, `${name}-${id}-preview.png`) });
+ assert.match(await page.locator('#productNote').textContent(), /coming soon/i);
+ await page.locator('.preview').screenshot({ path:path.join(artifacts, `${name}-${id}-preview.png`) });
await page.locator('#buy').scrollIntoViewIfNeeded();
await page.screenshot({ path:path.join(artifacts, `${name}-${id}-options.png`) });
}
await page.locator('#garments [data-g="classic-tee"]').click();
assert.equal(await page.locator('#buy').isDisabled(), false);
+ // Lincoln opens in the editorial "worn" Signature Edit shot; the 3D toggle is a garment-view control.
+ assert.ok(await page.locator('#lifestyleShot').isVisible());
+ assert.equal(await page.locator('#view3dToggle').isVisible(), false);
+ await page.locator('#lifestyleToggle').click();
+ assert.equal(await page.locator('#lifestyleShot').isVisible(), false);
assert.equal(await page.locator('#view3dToggle').isVisible(), true);
await page.goBack({ waitUntil:'domcontentloaded' });
assert.equal(new URL(page.url()).searchParams.get('cat'), 'Politics');
@@ -71,21 +97,37 @@ const checks = [];
assert.ok(box && box.y >= 0 && box.y + box.height <= viewport.height, 'product choices visible without scrolling the details');
assert.equal(await choices.locator('a').count(), 2);
await page.screenshot({ path:path.join(artifacts, `${name}-details.png`) });
- await choices.locator('.shop-mural').click();
- await page.waitForURL('**/murals?**');
- await page.waitForFunction(() => document.querySelector('#finderInput').value === 'Abraham Lincoln');
- assert.match(await page.locator('#finderRes').textContent(), /Abraham Lincoln/);
- assert.match(await page.locator('#selMural option:checked').textContent(), /Politic/i);
- await page.locator('#finder').scrollIntoViewIfNeeded();
- await page.screenshot({ path:path.join(artifacts, `${name}-mural.png`) });
- assert.ok(await page.locator('#payBtn').isVisible());
+ await choices.locator('.shop-wallpaper').click();
+ await page.waitForURL('**/wallpaper?**');
+ assert.equal(new URL(page.url()).searchParams.get('qid'), 'Q91');
+ await page.waitForFunction(() => document.querySelector('#selectedName').textContent === 'Abraham Lincoln');
+ assert.equal(await page.locator('#previewName').textContent(), 'Abraham Lincoln');
+ await page.waitForFunction(() => document.querySelector('#wallpaperPreview').dataset.signature === 'Q91');
+ await page.locator('#previewFrame').scrollIntoViewIfNeeded();
+ await page.screenshot({ path:path.join(artifacts, `${name}-wallpaper.png`) });
await page.goto(base+'/wear?qid=QUnknownFixture&name=Not+available', { waitUntil:'domcontentloaded' });
await page.locator('#selectionNotice:not([hidden])').waitFor();
assert.match(await page.locator('#selectionNotice').textContent(), /not available on clothing/);
assert.equal(await page.locator('#scrim.on').count(), 0);
assert.ok(await page.locator('#grid .card').count() > 0);
+ await page.goto(base+'/wear', { waitUntil:'domcontentloaded' });
+ await page.locator('#grid .card').first().waitFor();
+ const total = await page.locator('#grid .card').count();
+ await page.locator('#sigSearch').fill('Lincoln');
+ await page.locator('#ac .ac-item[data-qid="Q91"]').waitFor();
+ assert.ok(await page.locator('#grid .card').count() < total);
+ await page.locator('#sigSearch').press('Enter');
+ await page.locator('#scrim.on').waitFor();
+ assert.equal(await page.locator('#pName').textContent(), 'Abraham Lincoln');
+ await page.locator('#close').click();
+ await page.locator('#sigClear').click();
+ assert.equal(await page.locator('#sigSearch').inputValue(), '');
+ assert.equal(await page.locator('#grid .card').count(), total);
+ await page.locator('#sigSearch').fill('zzzz-no-signature-fixture');
+ assert.match(await page.locator('#empty').textContent(), /No signatures match/);
+ assert.equal(await page.locator('#ac').isVisible(), false);
assert.deepEqual(errors, []);
- checks.push({ viewport:name, verdict:'PASS', paths:['Politics card -> selected clothing', 'four accessory photos, sizes and sale holds', 'switch back to shirt restores purchase control', 'Politics details -> matching mural finder', 'unknown clothing signature rejected'], pageErrors:errors });
+ checks.push({ viewport:name, verdict:'PASS', paths:['Politics card -> selected clothing', 'four accessories for sale, two all-over shirts on hold', 'switch back to shirt restores purchase control', 'Politics details -> signature wallpaper studio', 'unknown clothing signature rejected', 'typeahead selection, clear and no-match recovery'], pageErrors:errors });
await context.close();
}
} finally { await browser.close(); }
← a310a07 wear: enable Dad Cap (29) + Trucker Cap (33) for sale, cost/
·
back to CelebritySignatures
·
campaign: lead the homepage hero with the full seated Signat 8272a62 →