← back to Designer Wallcoverings
auto-save: 2026-06-25T10:03:24 (4 files) — shopify/scripts/cadence/data/cadence-cursor.json shopify/scripts/cadence/data/cadence-plan-am-2026-06-25.json vendor-scrapers/china-seas-refresh DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/
ff353ac21c7115eaaf8e6598d777465485d70c38 · 2026-06-25 10:03:29 -0700 · Steve Abrams
Files touched
A DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/RESULTS.mdA DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/dwbr_brand_fix.mjsA DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/dwbr_title_check.mjsA DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/dwbr_verify.mjsA DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/koroseal-collection-crawl.mjsA DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/koroseal-dryrun.mjsA DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/koroseal-feed-probe.mjsA DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/koroseal-search-probe.mjsA DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/maya-dryrun.mjsA DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/maya-product-confirm.mjsA DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/rl-unlocker-dryrun.mjsM shopify/scripts/cadence/data/cadence-cursor.jsonM shopify/scripts/cadence/data/cadence-plan-am-2026-06-25.json
Diff
commit ff353ac21c7115eaaf8e6598d777465485d70c38
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Jun 25 10:03:29 2026 -0700
auto-save: 2026-06-25T10:03:24 (4 files) — shopify/scripts/cadence/data/cadence-cursor.json shopify/scripts/cadence/data/cadence-plan-am-2026-06-25.json vendor-scrapers/china-seas-refresh DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/
---
.../dryrun-2026-06-25/RESULTS.md | 9 ++++
.../dryrun-2026-06-25/dwbr_brand_fix.mjs | 55 ++++++++++++++++++++++
.../dryrun-2026-06-25/dwbr_title_check.mjs | 18 +++++++
.../dryrun-2026-06-25/dwbr_verify.mjs | 18 +++++++
.../koroseal-collection-crawl.mjs | 19 ++++++++
.../dryrun-2026-06-25/koroseal-dryrun.mjs | 32 +++++++++++++
.../dryrun-2026-06-25/koroseal-feed-probe.mjs | 24 ++++++++++
.../dryrun-2026-06-25/koroseal-search-probe.mjs | 20 ++++++++
.../dryrun-2026-06-25/maya-dryrun.mjs | 34 +++++++++++++
.../dryrun-2026-06-25/maya-product-confirm.mjs | 16 +++++++
.../dryrun-2026-06-25/rl-unlocker-dryrun.mjs | 40 ++++++++++++++++
shopify/scripts/cadence/data/cadence-cursor.json | 2 +-
.../cadence/data/cadence-plan-am-2026-06-25.json | 12 ++---
13 files changed, 292 insertions(+), 7 deletions(-)
diff --git a/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/RESULTS.md b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/RESULTS.md
new file mode 100644
index 00000000..c2b76ab5
--- /dev/null
+++ b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/RESULTS.md
@@ -0,0 +1,9 @@
+# Scraper Step-1 dry-run results — 2026-06-25 (READ-ONLY, no imports)
+
+| Vendor | Root cause | Fix tested | Result | Cost |
+|---|---|---|---|---|
+| Ralph Lauren | PerimeterX anti-bot (255→144) | Route via BrightData Web Unlocker zone | **BLOCKED — Web Unlocker API token EXPIRED (401 "Token expired")**. Fix path correct but needs credential rotation (Steve-gated). Count NOT yet recovered. | $0.003 (2 probe req) |
+| Koroseal | Pagination/lazy-load truncation (1000→500) | Iterate 121 `/products/wallcoverings/<collection>` pages instead of flat `?page=N` (truncates at ~194) | **RECOVERED — 1,437 unique products across 121 collections** (well past ~1000 target) | $0 (direct) |
+| Maya Romanoff | Stale collection slugs (404s) + image-CDN 403 | Re-derive slugs from sitemap+/collections; use hashed/resize image URLs | **RECOVERED — 91 live slugs, 0 404s**; 17,111 hashed/resize image URLs vs 7,622 403-prone direct. Collection pages 200 w/ specsTable. | $0 (direct) |
+
+NO Shopify writes, NO product creation. Imports remain GATED (Step 2).
diff --git a/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/dwbr_brand_fix.mjs b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/dwbr_brand_fix.mjs
new file mode 100644
index 00000000..fa17d162
--- /dev/null
+++ b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/dwbr_brand_fix.mjs
@@ -0,0 +1,55 @@
+import fs from 'fs';
+import { execSync } from 'child_process';
+
+const TOKEN = execSync(`grep -E '^SHOPIFY_ADMIN_TOKEN=' ${process.env.HOME}/Projects/secrets-manager/.env | head -1 | cut -d= -f2-`).toString().trim();
+const SHOP = 'designer-laboratory-sandbox.myshopify.com';
+const API = '2024-10';
+if (!TOKEN) { console.error('NO TOKEN'); process.exit(1); }
+
+// Parse map: handle|product_id|vendor|new_brand
+const rows = fs.readFileSync('/tmp/item1_map.txt','utf8').trim().split('\n').slice(1)
+ .map(l => { const [handle,pid,vendor,brand] = l.split('|'); return {handle,pid,vendor,brand}; });
+
+const BANNED = /brewster|york/i;
+// Safety: never write a banned value
+for (const r of rows) {
+ if (BANNED.test(r.brand)) { console.error('REFUSE banned target brand:', r.handle, r.brand); process.exit(2); }
+}
+console.log(`Loaded ${rows.length} rows. All target brands clean.`);
+
+async function gql(query, variables) {
+ const res = 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 res.json();
+}
+
+const MUT = `mutation($mf:[MetafieldsSetInput!]!){
+ metafieldsSet(metafields:$mf){
+ metafields{ id namespace key value ownerType }
+ userErrors{ field message }
+ }
+}`;
+
+const mode = process.argv[2]; // 'write'
+let ok=0, fail=0;
+const BATCH=25;
+for (let i=0;i<rows.length;i+=BATCH){
+ const chunk = rows.slice(i,i+BATCH);
+ const mf = chunk.map(r => ({
+ ownerId:`gid://shopify/Product/${r.pid}`,
+ namespace:'global', key:'Brand', type:'single_line_text_field',
+ value: r.brand
+ }));
+ if (mode!=='write'){ console.log(`[DRY] batch ${i/BATCH+1}: ${chunk.length} metafields`); continue; }
+ const j = await gql(MUT,{mf});
+ const ue = j?.data?.metafieldsSet?.userErrors||[];
+ const set = j?.data?.metafieldsSet?.metafields||[];
+ if (ue.length){ console.error('userErrors:', JSON.stringify(ue)); fail+=ue.length; }
+ ok += set.length;
+ console.log(`batch ${i/BATCH+1}: set ${set.length}, errors ${ue.length}`);
+ if (i+BATCH < rows.length){ console.log('sleeping 90s per DW bulk rule...'); await new Promise(r=>setTimeout(r,90000)); }
+}
+console.log(`DONE mode=${mode} ok=${ok} fail=${fail}`);
diff --git a/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/dwbr_title_check.mjs b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/dwbr_title_check.mjs
new file mode 100644
index 00000000..c9e508f3
--- /dev/null
+++ b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/dwbr_title_check.mjs
@@ -0,0 +1,18 @@
+import fs from 'fs';
+import { execSync } from 'child_process';
+const TOKEN = execSync(`grep -E '^SHOPIFY_ADMIN_TOKEN=' ${process.env.HOME}/Projects/secrets-manager/.env | head -1 | cut -d= -f2-`).toString().trim();
+const SHOP='designer-laboratory-sandbox.myshopify.com', API='2024-10';
+const rows = fs.readFileSync('/tmp/item1_map.txt','utf8').trim().split('\n').slice(1)
+ .map(l=>{const[handle,pid,vendor,brand]=l.split('|');return{handle,pid,vendor,brand};});
+async function gql(q,v){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:q,variables:v})});return r.json();}
+const Q=`query($id:ID!){product(id:$id){title vendor}}`;
+let leakTitle=0, leakVendor=0, samples=[];
+for(const r of rows){
+ const j=await gql(Q,{id:`gid://shopify/Product/${r.pid}`});
+ const p=j?.data?.product;
+ const tb=/brewster|york/i.test(p?.title||''); const vb=/brewster|york/i.test(p?.vendor||'');
+ if(tb){leakTitle++; if(samples.length<3) samples.push(`TITLE: ${p.title}`);}
+ if(vb){leakVendor++;}
+}
+console.log(`title leaks: ${leakTitle}/${rows.length}, vendor-field leaks: ${leakVendor}/${rows.length}`);
+samples.forEach(s=>console.log(' -',s));
diff --git a/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/dwbr_verify.mjs b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/dwbr_verify.mjs
new file mode 100644
index 00000000..6d8a88c0
--- /dev/null
+++ b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/dwbr_verify.mjs
@@ -0,0 +1,18 @@
+import fs from 'fs';
+import { execSync } from 'child_process';
+const TOKEN = execSync(`grep -E '^SHOPIFY_ADMIN_TOKEN=' ${process.env.HOME}/Projects/secrets-manager/.env | head -1 | cut -d= -f2-`).toString().trim();
+const SHOP='designer-laboratory-sandbox.myshopify.com', API='2024-10';
+const rows = fs.readFileSync('/tmp/item1_map.txt','utf8').trim().split('\n').slice(1)
+ .map(l=>{const[handle,pid,vendor,brand]=l.split('|');return{handle,pid,vendor,brand};});
+const BANNED=/brewster|york/i;
+async function gql(q,v){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:q,variables:v})});return r.json();}
+const Q=`query($id:ID!){product(id:$id){vendor metafield(namespace:"global",key:"Brand"){value}}}`;
+let pass=0,bad=[];
+for(const r of rows){
+ const j=await gql(Q,{id:`gid://shopify/Product/${r.pid}`});
+ const p=j?.data?.product; const brand=p?.metafield?.value; const vendor=p?.vendor;
+ const okEq = brand===vendor; const okClean = brand && !BANNED.test(brand);
+ if(okEq && okClean) pass++; else bad.push(`${r.handle}: brand="${brand}" vendor="${vendor}" eq=${okEq} clean=${okClean}`);
+}
+console.log(`VERIFIED ${pass}/${rows.length} Brand==vendor AND no brewster/york`);
+if(bad.length){console.log('FAILURES:');bad.forEach(b=>console.log(' -',b));}
diff --git a/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/koroseal-collection-crawl.mjs b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/koroseal-collection-crawl.mjs
new file mode 100644
index 00000000..925a7ae6
--- /dev/null
+++ b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/koroseal-collection-crawl.mjs
@@ -0,0 +1,19 @@
+const UA='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124 Safari/537.36';
+async function get(url){ try{const r=await fetch(url,{headers:{'User-Agent':UA,'Accept':'text/html'}}); return {status:r.status, body:await r.text()};}catch(e){return{status:'ERR',body:''};} }
+const home=await get('https://koroseal.com/products/wallcoverings');
+const cats=[...new Set([...home.body.matchAll(/\/products\/wallcoverings\/[a-z0-9-]+(?=["'/?])/gi)].map(m=>m[0]))]
+ .filter(c=>c!=='/products/wallcoverings');
+console.log(`collections discovered: ${cats.length}`);
+const allProducts=new Set();
+let crawled=0;
+// crawl ALL collections (direct, free) to get the true unique product count
+for(const c of cats){
+ const {status,body}=await get('https://koroseal.com'+c);
+ // products within a collection page = deeper links (collection/product) or colorway swatches
+ const prods=[...body.matchAll(new RegExp(c.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')+'\\/[a-z0-9-]+','gi'))].map(m=>m[0]);
+ prods.forEach(p=>allProducts.add(p));
+ crawled++;
+ if(crawled%30===0) console.log(` ...${crawled}/${cats.length} collections, running unique products=${allProducts.size}`);
+}
+console.log(`\nKOROSEAL COLLECTION-CRAWL DRY-RUN: collections=${cats.length}, unique products=${allProducts.size}`);
+console.log('COST: $0 (local, direct connection)');
diff --git a/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/koroseal-dryrun.mjs b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/koroseal-dryrun.mjs
new file mode 100644
index 00000000..87967ef2
--- /dev/null
+++ b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/koroseal-dryrun.mjs
@@ -0,0 +1,32 @@
+// Koroseal dry-run — direct (no proxy needed per diagnosis). Iterate ?page=N until empty.
+// READ ONLY: counts products, no Shopify, no creation.
+const BASE='https://koroseal.com/products/wallcoverings';
+const UA='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124 Safari/537.36';
+async function get(url){
+ const r=await fetch(url,{headers:{'User-Agent':UA,'Accept':'text/html'}});
+ return {status:r.status, body: await r.text()};
+}
+function countProducts(html){
+ // koroseal product tiles — look for product links + sku patterns
+ const links = new Set([...html.matchAll(/\/products\/wallcoverings\/[a-z0-9-]+\/[a-z0-9-]+/gi)].map(m=>m[0]));
+ const skuish = [...html.matchAll(/data-product|product-card|product-item|product-tile/gi)].length;
+ return { uniqueLinks: links.size, tileMarkers: skuish };
+}
+let page=1, total=0, emptyStreak=0, pages=0;
+const seen=new Set();
+while(page<=60){
+ const url = page===1 ? BASE : `${BASE}?page=${page}`;
+ const {status,body}=await get(url);
+ const {uniqueLinks, tileMarkers}=countProducts(body);
+ // dedupe links across pages
+ const before=seen.size;
+ [...body.matchAll(/\/products\/wallcoverings\/[a-z0-9-]+\/[a-z0-9-]+/gi)].forEach(m=>seen.add(m[0]));
+ const added=seen.size-before;
+ console.log(`page ${page}: HTTP ${status} bytes=${body.length} linksOnPage=${uniqueLinks} newUnique=${added} tileMarkers=${tileMarkers}`);
+ pages++;
+ if(added===0){ emptyStreak++; if(emptyStreak>=2){ console.log(' two empty pages → stop'); break; } }
+ else emptyStreak=0;
+ page++;
+}
+console.log(`\nKOROSEAL DRY-RUN: pages crawled=${pages}, unique product links=${seen.size}`);
+console.log('COST: $0 (local, direct connection — no proxy)');
diff --git a/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/koroseal-feed-probe.mjs b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/koroseal-feed-probe.mjs
new file mode 100644
index 00000000..885efbbc
--- /dev/null
+++ b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/koroseal-feed-probe.mjs
@@ -0,0 +1,24 @@
+// Probe Koroseal's data feed (Akeneo / JSON / infinite-scroll endpoint) for full catalog.
+const UA='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124 Safari/537.36';
+async function get(url, extra={}){ try{
+ const r=await fetch(url,{headers:{'User-Agent':UA,'Accept':'application/json,text/html',...extra}});
+ const ct=r.headers.get('content-type')||''; const body=await r.text();
+ return {status:r.status, ct, body};
+}catch(e){ return {status:'ERR', ct:'', body:String(e)}; } }
+
+// 1) inspect page 1 HTML for the infinite-scroll / ajax endpoint it calls
+const home = await get('https://koroseal.com/products/wallcoverings');
+const hints = [...home.body.matchAll(/(\/[a-z0-9_\-\/]*(?:akeneo|api|ajax|load-?more|products?\.json|search|graphql)[a-z0-9_\-\/\?=&]*)/gi)].map(m=>m[1]);
+const uniqHints=[...new Set(hints)].slice(0,25);
+console.log('endpoint hints in page1 HTML:'); uniqHints.forEach(h=>console.log(' ',h));
+// look for a JS-embedded total / paginate config
+const totalM = home.body.match(/("total"|"count"|"nb_results"|data-total)["=:\s]*([0-9]{2,5})/i);
+console.log('embedded total hint:', totalM? totalM[2] : 'none');
+// common Koroseal/Akeneo feed candidates
+const cands = [
+ 'https://koroseal.com/products/wallcoverings?page=1&format=json',
+ 'https://koroseal.com/api/products?category=wallcoverings&limit=1000',
+ 'https://koroseal.com/products/wallcoverings.json',
+];
+for(const u of cands){ const r=await get(u); console.log(`\nTRY ${u}\n HTTP ${r.status} ct=${r.ct} bytes=${r.body.length} jsonish=${/^[\s]*[\{\[]/.test(r.body)}`); }
+console.log('\nCOST: $0 (local, direct)');
diff --git a/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/koroseal-search-probe.mjs b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/koroseal-search-probe.mjs
new file mode 100644
index 00000000..3a349f03
--- /dev/null
+++ b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/koroseal-search-probe.mjs
@@ -0,0 +1,20 @@
+const UA='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124 Safari/537.36';
+async function get(url){ try{const r=await fetch(url,{headers:{'User-Agent':UA,'Accept':'text/html,application/json'}}); return {status:r.status, body:await r.text()};}catch(e){return{status:'ERR',body:String(e)};} }
+// search-results page — does it paginate the full catalog? try high page + page param patterns
+const tries = [
+ 'https://koroseal.com/search-results?searchtext=wallcovering',
+ 'https://koroseal.com/products/wallcoverings?p=10',
+ 'https://koroseal.com/products/wallcoverings?pagenumber=10',
+ 'https://koroseal.com/products/wallcoverings?page=2&pagesize=500',
+];
+for(const u of tries){
+ const {status,body}=await get(u);
+ const links=new Set([...body.matchAll(/\/products\/wallcoverings\/[a-z0-9-]+\/[a-z0-9-]+/gi)].map(m=>m[0]));
+ console.log(`${u}\n HTTP ${status} bytes=${body.length} uniqueProductLinks=${links.size}`);
+}
+// also: does the home page reference a deeper category tree we should iterate (collections → products)?
+const home=await get('https://koroseal.com/products/wallcoverings');
+const cats=new Set([...home.body.matchAll(/\/products\/wallcoverings\/[a-z0-9-]+(?=["'/?])/gi)].map(m=>m[0]));
+console.log(`\nwallcoverings sub-categories/collections found: ${cats.size}`);
+[...cats].slice(0,15).forEach(c=>console.log(' ',c));
+console.log('\nCOST: $0 (local, direct)');
diff --git a/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/maya-dryrun.mjs b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/maya-dryrun.mjs
new file mode 100644
index 00000000..1f9fd66c
--- /dev/null
+++ b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/maya-dryrun.mjs
@@ -0,0 +1,34 @@
+// Maya Romanoff dry-run — re-derive live collection slugs from /collections + sitemap, confirm fetches.
+const UA='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124 Safari/537.36';
+async function get(url){ try{const r=await fetch(url,{headers:{'User-Agent':UA,'Accept':'text/html,application/xml'}}); return {status:r.status, body:await r.text()};}catch(e){return{status:'ERR',body:String(e)};} }
+
+// 1) sitemap → collection slugs
+const sm=await get('https://www.mayaromanoff.com/sitemap.xml');
+console.log(`sitemap.xml: HTTP ${sm.status} bytes=${sm.body.length}`);
+// shopify-style: collections live in sitemap_collections; else parse /collections page
+let collSlugs=new Set();
+[...sm.body.matchAll(/\/collections\/([a-z0-9-]+)/gi)].forEach(m=>collSlugs.add(m[1]));
+// sub-sitemaps
+const subs=[...sm.body.matchAll(/<loc>([^<]*sitemap[^<]*\.xml)<\/loc>/gi)].map(m=>m[1]);
+for(const s of subs.slice(0,6)){ const r=await get(s); [...r.body.matchAll(/\/collections\/([a-z0-9-]+)/gi)].forEach(m=>collSlugs.add(m[1])); }
+
+// 2) /collections index page
+const ci=await get('https://www.mayaromanoff.com/collections');
+[...ci.body.matchAll(/\/collections\/([a-z0-9-]+)/gi)].forEach(m=>collSlugs.add(m[1]));
+collSlugs.delete('all');
+const slugs=[...collSlugs];
+console.log(`live collection slugs derived: ${slugs.length}`);
+console.log(' sample:', slugs.slice(0,12).join(', '));
+
+// 3) confirm each collection fetches 200 (no 404 from stale hardcoded slugs) + count products + check image URLs
+let ok=0, notfound=0, products=new Set(), resizeImgs=0, directImgs=0;
+for(const s of slugs){
+ const r=await get(`https://www.mayaromanoff.com/collections/${s}`);
+ if(r.status===200) ok++; else { notfound++; continue; }
+ [...r.body.matchAll(/\/products\/([a-z0-9-]+)/gi)].forEach(m=>products.add(m[1]));
+ resizeImgs += [...r.body.matchAll(/_resize|\/cdn\/shop\/.*?\?.*?width=/gi)].length;
+ directImgs += [...r.body.matchAll(/assets\.mayaromanoff\.com\/web\/skus\//gi)].length;
+}
+console.log(`\nMAYA DRY-RUN: collections 200=${ok}, 404=${notfound}, unique products=${products.size}`);
+console.log(` image URLs: hashed/resize=${resizeImgs} (use these), direct-sku=${directImgs} (403-prone, avoid)`);
+console.log('COST: $0 (local, direct connection)');
diff --git a/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/maya-product-confirm.mjs b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/maya-product-confirm.mjs
new file mode 100644
index 00000000..a13a9878
--- /dev/null
+++ b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/maya-product-confirm.mjs
@@ -0,0 +1,16 @@
+const UA='Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124 Safari/537.36';
+async function get(url){ try{const r=await fetch(url,{headers:{'User-Agent':UA,'Accept':'text/html'}}); return {status:r.status, body:await r.text()};}catch(e){return{status:'ERR',body:''};} }
+// confirm the two known-good collection pages from the diagnosis fetch + carry products via JSON
+const checks=['beadazzled','ajiro-burst-of-happiness','precious-metals'];
+let ok=0;
+for(const c of checks){
+ const r=await get(`https://www.mayaromanoff.com/collections/${c}`);
+ // Shopify themes expose products in collection JSON
+ const j=await get(`https://www.mayaromanoff.com/collections/${c}/products.json?limit=250`);
+ let n=0; try{ n=(JSON.parse(j.body).products||[]).length; }catch{}
+ const specs = /specsTable|spec-table|Width|Repeat/i.test(r.body);
+ console.log(`/collections/${c}: page HTTP ${r.status} (specsTable=${specs}) · products.json HTTP ${j.status} products=${n}`);
+ if(r.status===200) ok++;
+}
+console.log(`\nMAYA PRODUCT CONFIRM: ${ok}/${checks.length} collections fetch 200; products available via products.json`);
+console.log('COST: $0 (local, direct)');
diff --git a/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/rl-unlocker-dryrun.mjs b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/rl-unlocker-dryrun.mjs
new file mode 100644
index 00000000..c461f0c8
--- /dev/null
+++ b/DW-Programming/ImportNewSkufromURL/dryrun-2026-06-25/rl-unlocker-dryrun.mjs
@@ -0,0 +1,40 @@
+// RL dry-run via BrightData Web Unlocker REST API — READ ONLY, no Shopify, no product creation.
+import fs from 'fs';
+const env = fs.readFileSync(process.env.HOME+'/Projects/Designer-Wallcoverings/DW-Programming/ImportNewSkufromURL/.env.local','utf8');
+const get = k => (env.match(new RegExp('^'+k+'=(.*)$','m'))||[])[1]?.trim();
+const API_KEY = get('BRIGHTDATA_API_KEY');
+const ZONE = get('BRIGHTDATA_UNLOCKER_ZONE') || 'web_unlocker1';
+if(!API_KEY){console.error('NO API KEY');process.exit(1);}
+
+async function unlock(url){
+ const r = await fetch('https://api.brightdata.com/request',{
+ method:'POST',
+ headers:{'Authorization':`Bearer ${API_KEY}`,'Content-Type':'application/json'},
+ body: JSON.stringify({ zone: ZONE, url, format:'raw' })
+ });
+ const status = r.status;
+ const body = await r.text();
+ return { status, body };
+}
+
+const targets = [
+ 'https://www.ralphlauren.com/home-swatches-wall-coverings',
+];
+let cost = 0; const PER = 0.0015; // est per Web Unlocker request
+let totalProducts = 0, pxHits = 0;
+for(const url of targets){
+ const {status, body} = await unlock(url); cost += PER;
+ const px = /_px|perimeterx|captcha|Access to this page has been denied|px-captcha/i.test(body);
+ if(px) pxHits++;
+ // count product markers (RL PDP links / product tiles)
+ const prodLinks = [...body.matchAll(/\/home-[a-z0-9-]*wall[a-z0-9-]*\/[0-9]+\.html|data-product-id|product-tile|pdp-link/gi)].length;
+ // count swatch/colorway items
+ const swatches = [...body.matchAll(/swatch|colorway|product-name|product__name/gi)].length;
+ console.log(`${url}\n HTTP ${status} bytes=${body.length} PX_wall=${px} productMarkers=${prodLinks} swatchMarkers=${swatches}`);
+ totalProducts += prodLinks;
+ // try to find a total count / pagination hint
+ const totalMatch = body.match(/("totalCount"|"resultsCount"|data-total-results?)["=:\s]*([0-9]{2,5})/i);
+ if(totalMatch) console.log(' declared total:', totalMatch[2]);
+}
+console.log(`\nRL DRY-RUN: PX walls hit=${pxHits}/${targets.length}, productMarkers=${totalProducts}`);
+console.log(`COST: $${cost.toFixed(4)} (BrightData Web Unlocker, ${targets.length} req @ ~$${PER})`);
diff --git a/shopify/scripts/cadence/data/cadence-cursor.json b/shopify/scripts/cadence/data/cadence-cursor.json
index 58ca82ee..37c177e4 100644
--- a/shopify/scripts/cadence/data/cadence-cursor.json
+++ b/shopify/scripts/cadence/data/cadence-cursor.json
@@ -1,5 +1,5 @@
{
- "idx": 7,
+ "idx": 13,
"lastSlot": "am",
"lastRun": "2026-06-25"
}
\ No newline at end of file
diff --git a/shopify/scripts/cadence/data/cadence-plan-am-2026-06-25.json b/shopify/scripts/cadence/data/cadence-plan-am-2026-06-25.json
index 6b6761a4..e608affe 100644
--- a/shopify/scripts/cadence/data/cadence-plan-am-2026-06-25.json
+++ b/shopify/scripts/cadence/data/cadence-plan-am-2026-06-25.json
@@ -1,12 +1,12 @@
[
{
- "vendor": "Thibaut",
- "dw_sku": "DWTT-73450",
- "cost": 56.7,
- "retail": 102.62,
+ "vendor": "Quadrille",
+ "dw_sku": "DWQC-600039",
+ "cost": 0,
+ "retail": 0,
"sample": "4.25",
- "sampleOnly": false,
- "title": "Spencer, Metallic Copper on Charcoal Wallcoverings | Thibaut",
+ "sampleOnly": true,
+ "title": "Climbing Hydrangea Fabric, Lilac on Light Tint Wallcoverings | Quadrille",
"willActivate": true
}
]
\ No newline at end of file
← 9a95b5b4 China Seas mailer: repoint Design Idea Spotlight pills — Nat
·
back to Designer Wallcoverings
·
China Seas mailer: Naturals pill → /collections/grasscloth ( b135b9f7 →