← back to Majilite Jewelry Cases
chore: lint (drop dead gql/bySky), refactor, hoverproof cleanup, v-patch (session close)
c2a07c24e82c9196548596eb4e187e706b5dbd4f · 2026-08-10 16:21:24 -0700 · Steve Abrams
Files touched
A hoverproof.mjsM package-lock.jsonM package.jsonM scripts/push-shopify.mjs
Diff
commit c2a07c24e82c9196548596eb4e187e706b5dbd4f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Aug 10 16:21:24 2026 -0700
chore: lint (drop dead gql/bySky), refactor, hoverproof cleanup, v-patch (session close)
---
hoverproof.mjs | 19 +++++++++++++++++++
package-lock.json | 6 ++++--
package.json | 3 ++-
scripts/push-shopify.mjs | 26 +++++++++++++++-----------
4 files changed, 40 insertions(+), 14 deletions(-)
diff --git a/hoverproof.mjs b/hoverproof.mjs
new file mode 100644
index 0000000..0b390d1
--- /dev/null
+++ b/hoverproof.mjs
@@ -0,0 +1,19 @@
+import { chromium } from 'playwright-core';
+const EXE = '/Users/macstudio3/Library/Caches/ms-playwright/chromium-1234/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing';
+const b = await chromium.launch({ executablePath: EXE });
+try {
+ const p = await b.newPage({ viewport: { width: 1280, height: 1000 } });
+ await p.goto('https://novasuede.com/', { waitUntil: 'networkidle', timeout: 30000 });
+ await p.waitForSelector('.color-item', { timeout: 20000 });
+ const item = p.locator('.color-item').nth(8);
+ await item.scrollIntoViewIfNeeded(); await p.waitForTimeout(400);
+ await item.locator('.color-swatch').screenshot({ path: 'output/_proof/LIVE-before.png' });
+ await item.hover(); await p.waitForTimeout(900);
+ const op = await item.locator('.jewel').evaluate(el => getComputedStyle(el).opacity).catch(()=>'no-jewel');
+ const src = await item.locator('.jewel').getAttribute('src').catch(()=>null);
+ const nat = await item.locator('.jewel').evaluate(el=>el.naturalWidth).catch(()=>0);
+ console.log('LIVE hover -> jewel opacity:', op, '| naturalWidth:', nat, '| src:', src);
+ await item.locator('.color-swatch').screenshot({ path: 'output/_proof/LIVE-hover.png' });
+} finally {
+ await b.close();
+}
diff --git a/package-lock.json b/package-lock.json
index bb08208..6e53dbf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,7 +6,8 @@
"": {
"dependencies": {
"playwright-core": "^1.62.1"
- }
+ },
+ "version": "0.0.1"
},
"node_modules/playwright-core": {
"version": "1.62.1",
@@ -20,5 +21,6 @@
"node": ">=20"
}
}
- }
+ },
+ "version": "0.0.1"
}
diff --git a/package.json b/package.json
index 823de25..1061e67 100644
--- a/package.json
+++ b/package.json
@@ -1,5 +1,6 @@
{
"dependencies": {
"playwright-core": "^1.62.1"
- }
+ },
+ "version": "0.0.1"
}
diff --git a/scripts/push-shopify.mjs b/scripts/push-shopify.mjs
index a44be74..34aa245 100644
--- a/scripts/push-shopify.mjs
+++ b/scripts/push-shopify.mjs
@@ -23,7 +23,6 @@ const PUBONLY = has('--published-only');
const ONLY = val('--only', '');
const map = JSON.parse(fs.readFileSync(path.join(ROOT, 'data/shopify-map.json'), 'utf8'));
-const bySku = new Map(map.map(r => [r.sku, r]));
// discover generated glass-counter renders -> match folder back to sku
const OUT = path.join(ROOT, 'output');
@@ -54,17 +53,18 @@ if (!APPLY) {
// ---- LIVE WRITE PATH (only reached with --apply) ----
if (!TOKEN) { console.error('No SHOPIFY_ADMIN_TOKEN'); process.exit(1); }
-const gql = async (query, variables) => {
- const r = await fetch(`https://${SHOP}/admin/api/${API}/graphql.json`, {
- method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
- body: JSON.stringify({ query, variables }),
- });
- return r.json();
-};
// REST image attach with base64 — reliable, no staged-upload handshake.
// Returns the created image id (proof of a real attach), throws with the real body on failure.
+async function alreadyHasJewel(numericId) {
+ const res = await fetch(`https://${SHOP}/admin/api/${API}/products/${numericId}/images.json`, {
+ headers: { 'X-Shopify-Access-Token': TOKEN } });
+ if (!res.ok) return false;
+ const j = await res.json();
+ return (j.images || []).some(im => /jewelry-case/i.test(im.src || ''));
+}
async function attachViaRest(r) {
const numericId = String(r.shopify_id).split('/').pop(); // shopify_id may be a GID
+ if (await alreadyHasJewel(numericId)) return 'skip'; // idempotent / resume-safe
// downscale to a compact JPG to keep the base64 payload small + reliable
const tmp = `/tmp/jc-${numericId}.jpg`;
let src = r.png;
@@ -79,10 +79,14 @@ async function attachViaRest(r) {
if (!res.ok || !j.image?.id) throw new Error(`REST ${res.status}: ${JSON.stringify(j).slice(0, 220)}`);
return j.image.id;
}
-let ok = 0, bad = 0;
+let ok = 0, bad = 0, skip = 0;
for (const r of targets) {
- try { const imgId = await attachViaRest(r); ok++; console.log(` + ${r.handle} (image ${imgId})`); }
+ try {
+ const imgId = await attachViaRest(r);
+ if (imgId === 'skip') { skip++; continue; }
+ ok++; console.log(` + ${r.handle} (image ${imgId})`);
+ }
catch (e) { bad++; console.log(` ! ${r.handle}: ${e.message}`); }
await new Promise(z => setTimeout(z, 600));
}
-console.log(`\nLIVE PUSH done. attached=${ok} failed=${bad}`);
+console.log(`\nLIVE PUSH done. attached=${ok} skipped_existing=${skip} failed=${bad}`);
← 7b71af9 Fix Shopify push: REST base64 attach + extract numeric id fr
·
back to Majilite Jewelry Cases
·
TK-11086: stage reversible removal of jewelry glass-counter af9272b →