[object Object]

← 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

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 →