← back to Designerwallcoverings
TK-12129: custom.sold_per canary write + unpublished preview theme render proof
760ec45e6ffa6f15715287d45e913df6d3fdf193 · 2026-09-24 10:27:58 -0700 · Steve Abrams
Files touched
A data/tk12129-preview-edit.mjsA data/tk12129-preview-edit2.mjsA data/tk12129-preview-manifest.jsonA data/tk12129-rollback-soldper.mjsA data/tk12129-screenshots.mjsA data/tk12129-soldper-write.mjs
Diff
commit 760ec45e6ffa6f15715287d45e913df6d3fdf193
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Sep 24 10:27:58 2026 -0700
TK-12129: custom.sold_per canary write + unpublished preview theme render proof
---
data/tk12129-preview-edit.mjs | 58 ++++++++++++++++++++++++++++++
data/tk12129-preview-edit2.mjs | 58 ++++++++++++++++++++++++++++++
data/tk12129-preview-manifest.json | 20 +++++++++++
data/tk12129-rollback-soldper.mjs | 28 +++++++++++++++
data/tk12129-screenshots.mjs | 44 +++++++++++++++++++++++
data/tk12129-soldper-write.mjs | 73 ++++++++++++++++++++++++++++++++++++++
6 files changed, 281 insertions(+)
diff --git a/data/tk12129-preview-edit.mjs b/data/tk12129-preview-edit.mjs
new file mode 100644
index 0000000..f85adc3
--- /dev/null
+++ b/data/tk12129-preview-edit.mjs
@@ -0,0 +1,58 @@
+// TK-12129 STEP 2 — edit the PREVIEW (UNPUBLISHED) theme's price snippet ONLY.
+// Appends a data-gated "length · sold per" line directly under the PDP price.
+// HARD RAIL: writes ONLY to theme 145897783347 (the unpublished preview). Never the live theme.
+import fs from 'node:fs';
+const envPath = '/Users/macstudio3/Projects/secrets-manager/.env';
+const raw = fs.readFileSync(envPath, 'utf8'); const env = {};
+for (const line of raw.split('\n')) { const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/); if (m) env[m[1]] = m[2].replace(/^["']|["']$/g,''); }
+const DOMAIN='designer-laboratory-sandbox.myshopify.com', VER='2024-10', TOKEN=env.SHOPIFY_ADMIN_TOKEN;
+const PREVIEW_THEME='145897783347'; // UNPUBLISHED preview — never the live theme
+const KEY='snippets/section.flex-pdp.price.liquid';
+
+const APPLY = process.argv.includes('--apply');
+const BASE=`https://${DOMAIN}/admin/api/${VER}/themes/${PREVIEW_THEME}/assets.json`;
+const H={'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'};
+
+// SAFETY: refuse to run against the live theme id.
+if (PREVIEW_THEME === '145556635699') { console.error('ABORT: target is the LIVE theme'); process.exit(1); }
+
+// 1) GET current preview asset
+const g = await fetch(`${BASE}?asset[key]=${encodeURIComponent(KEY)}`, {headers:H});
+const gj = await g.json();
+let cur = gj.asset?.value;
+if (!cur) { console.error('ABORT: could not read', KEY, JSON.stringify(gj).slice(0,300)); process.exit(1); }
+
+const MARKER='TK-12129 roll-length + sold-per';
+if (cur.includes(MARKER)) { console.log('Already edited (marker present) — no-op.'); process.exit(0); }
+
+// 2) Build the appended block. Data-gate = custom.length present.
+// Matches theme markup by rendering through element.text (variant body-sm, as div).
+const block = `
+
+{%- comment -%} ${MARKER} (TK-12129): price-area-only line, gated on custom.length so murals/unmeasured SKUs show nothing. Does NOT touch the Specifications block. {%- endcomment -%}
+{%- liquid
+ assign tk_len = product.metafields.custom.length.value | default: product.metafields.custom.length
+ assign tk_soldper = product.metafields.custom.sold_per.value | default: product.metafields.custom.sold_per
+-%}
+{%- if tk_len != blank -%}
+ {%- capture tk_roll_line -%}{{ tk_len }}{%- if tk_soldper != blank %} · sold per {{ tk_soldper | downcase }}{%- endif -%}{%- endcapture -%}
+ {% render 'element.text', variant: 'body-sm', as: 'div', classlist: 'tk-roll-length', style: 'margin-top:0.35rem;opacity:0.72;', text: tk_roll_line %}
+{%- endif -%}
+`;
+
+const next = cur.replace(/\s*$/,'') + '\n' + block;
+
+fs.writeFileSync('/tmp/tk12129-theme/section.flex-pdp.price.EDITED.liquid', next);
+console.log('--- APPENDED BLOCK ---'); console.log(block);
+if (!APPLY) { console.log('DRY RUN — wrote EDITED copy locally, did NOT PUT. Re-run with --apply.'); process.exit(0); }
+
+// 3) PUT to preview theme only
+const p = await fetch(BASE, {method:'PUT', headers:H, body:JSON.stringify({asset:{key:KEY, value:next}})});
+const pj = await p.json();
+if (pj.errors) { console.error('PUT ERROR', JSON.stringify(pj.errors)); process.exit(1); }
+console.log('PUT ok ->', pj.asset?.key, 'theme', PREVIEW_THEME, 'updated_at', pj.asset?.updated_at);
+
+// 4) Verify read-back contains marker
+const v = await fetch(`${BASE}?asset[key]=${encodeURIComponent(KEY)}`, {headers:H});
+const vj = await v.json();
+console.log('VERIFY marker present in preview asset:', (vj.asset?.value||'').includes(MARKER));
diff --git a/data/tk12129-preview-edit2.mjs b/data/tk12129-preview-edit2.mjs
new file mode 100644
index 0000000..d95acbc
--- /dev/null
+++ b/data/tk12129-preview-edit2.mjs
@@ -0,0 +1,58 @@
+// TK-12129 STEP 2 (corrected) — edit the PREVIEW (UNPUBLISHED) theme ONLY.
+// A) REVERT the earlier wrong-path edit to snippets/section.flex-pdp.price.liquid (that snippet
+// is NOT on this "Grid" theme's PDP render path — dead code; restore original).
+// B) Add the data-gated "length · sold per" line to the REAL PDP price snippet
+// snippets/product-form-content.liquid, directly under <p class="product-price">.
+// HARD RAIL: writes ONLY to theme 145897783347 (unpublished preview). Never the live theme 145556635699.
+import fs from 'node:fs';
+const envPath='/Users/macstudio3/Projects/secrets-manager/.env';
+const raw=fs.readFileSync(envPath,'utf8'); const env={};
+for(const line of raw.split('\n')){const m=line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/); if(m)env[m[1]]=m[2].replace(/^["']|["']$/g,'');}
+const DOMAIN='designer-laboratory-sandbox.myshopify.com', VER='2024-10', TOKEN=env.SHOPIFY_ADMIN_TOKEN;
+const PREVIEW='145897783347', LIVE='145556635699';
+if(PREVIEW===LIVE){console.error('ABORT live');process.exit(1);}
+const APPLY=process.argv.includes('--apply');
+const BASE=`https://${DOMAIN}/admin/api/${VER}/themes/${PREVIEW}/assets.json`;
+const H={'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'};
+const get=async k=>{const r=await fetch(`${BASE}?asset[key]=${encodeURIComponent(k)}`,{headers:H});return (await r.json()).asset?.value;};
+const put=async (k,v)=>{const r=await fetch(BASE,{method:'PUT',headers:H,body:JSON.stringify({asset:{key:k,value:v}})});return r.json();};
+
+const MARKER='TK-12129 roll-length + sold-per';
+
+// ---- A) revert flex-pdp snippet to the saved original ----
+const ORIG_FLEX=fs.readFileSync('/tmp/tk12129-theme/snippets_section.flex-pdp.price.liquid','utf8');
+if(ORIG_FLEX.includes(MARKER)){console.error('ABORT: saved flex original already contains marker');process.exit(1);}
+
+// ---- B) build the real edit for product-form-content.liquid ----
+const KEY='snippets/product-form-content.liquid';
+let cur=await get(KEY);
+if(!cur){console.error('ABORT: cannot read',KEY);process.exit(1);}
+if(cur.includes(MARKER)){console.log('product-form-content already edited — no-op on B');}
+const ANCHOR=` </p>\n\n {% assign variant_for_unit_price = selected_variant %}`;
+if(!cur.includes(ANCHOR)){console.error('ABORT: anchor not found in',KEY);process.exit(1);}
+const INSERT=` </p>
+
+ {%- comment -%} ${MARKER} (TK-12129): price-area-only line under the PDP price, gated on custom.length so murals/unmeasured SKUs show nothing. Does NOT touch the Specifications block. {%- endcomment -%}
+ {%- liquid
+ assign tk_len = product.metafields.custom.length.value | default: product.metafields.custom.length
+ assign tk_soldper = product.metafields.custom.sold_per.value | default: product.metafields.custom.sold_per
+ -%}
+ {%- if tk_len != blank -%}
+ <div class="product-price product__roll-length" style="margin-top:0.35rem;font-size:0.85em;opacity:0.72;">{{ tk_len }}{%- if tk_soldper != blank %} · sold per {{ tk_soldper | downcase }}{%- endif -%}</div>
+ {%- endif -%}
+
+ {% assign variant_for_unit_price = selected_variant %}`;
+const nextPFC=cur.replace(ANCHOR, INSERT);
+fs.writeFileSync('/tmp/tk12129-theme/product-form-content.EDITED.liquid', nextPFC);
+console.log('--- INSERT into product-form-content.liquid (directly under <p class=product-price>) ---');
+console.log(INSERT.split('\n').slice(2,10).join('\n'));
+
+if(!APPLY){console.log('\nDRY RUN — no PUT. Re-run with --apply.');process.exit(0);}
+
+// A) revert
+const ra=await put('snippets/section.flex-pdp.price.liquid', ORIG_FLEX);
+console.log('\nREVERT flex-pdp ->', ra.errors?JSON.stringify(ra.errors):'ok', ra.asset?.updated_at||'');
+// B) apply real edit
+const rb=await put(KEY, nextPFC);
+console.log('PUT product-form-content ->', rb.errors?JSON.stringify(rb.errors):'ok', rb.asset?.updated_at||'');
+if(rb.errors){process.exit(1);}
diff --git a/data/tk12129-preview-manifest.json b/data/tk12129-preview-manifest.json
new file mode 100644
index 0000000..ba49aaa
--- /dev/null
+++ b/data/tk12129-preview-manifest.json
@@ -0,0 +1,20 @@
+{
+ "ticket": "TK-12129",
+ "agent": "vp-dw-commerce",
+ "store": "designer-laboratory-sandbox.myshopify.com",
+ "live_theme": { "id": 145556635699, "name": "DW Sample-Shipping DEV", "role": "main", "status": "UNTOUCHED (verified clean, no marker)" },
+ "preview_theme": { "id": 145897783347, "name": "TK-12129 roll-length preview", "role": "UNPUBLISHED" },
+ "edited_snippet": "snippets/product-form-content.liquid (the REAL Grid-theme PDP price render path; flex-pdp.price.liquid was the wrong path and was reverted)",
+ "metafields_written": { "custom.length": "10.05 m (33 ft)", "custom.sold_per": "Single Roll" },
+ "render_rule": "under <p class=product-price>: {{ custom.length }} · sold per {{ custom.sold_per | downcase }} ; gated on custom.length present",
+ "preview_urls": {
+ "gardinum_DWHF-70238": "https://www.designerwallcoverings.com/products/dwhf-70238-sample-designer-wallcoverings-los-angeles?preview_theme_id=145897783347&pb=0",
+ "mural_eternaloak_DWHF-70006": "https://www.designerwallcoverings.com/products/dwhf-70006-sample-designer-wallcoverings-los-angeles?preview_theme_id=145897783347&pb=0"
+ },
+ "screenshots": {
+ "gardinum_pricearea": "data/tk12129-gardinum-pricearea.png",
+ "gardinum_fullpage": "data/tk12129-gardinum-fullpage.png",
+ "mural_pricearea": "data/tk12129-mural-eternaloak-pricearea.png",
+ "mural_fullpage": "data/tk12129-mural-eternaloak-fullpage.png"
+ }
+}
diff --git a/data/tk12129-rollback-soldper.mjs b/data/tk12129-rollback-soldper.mjs
new file mode 100644
index 0000000..aab04fd
--- /dev/null
+++ b/data/tk12129-rollback-soldper.mjs
@@ -0,0 +1,28 @@
+// TK-12129 rollback — DELETE the 10 custom.sold_per metafields written on the Harlequin
+// canary batch (they were ABSENT before the write, so rollback = delete).
+// Usage: node tk12129-rollback-soldper.mjs --apply (dry-run without --apply)
+import fs from 'node:fs';
+const envPath = '/Users/macstudio3/Projects/secrets-manager/.env';
+const raw = fs.readFileSync(envPath, 'utf8');
+const env = {};
+for (const line of raw.split('\n')) { const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/); if (m) env[m[1]] = m[2].replace(/^["']|["']$/g,''); }
+const DOMAIN='designer-laboratory-sandbox.myshopify.com', VER='2024-10', TOKEN=env.SHOPIFY_ADMIN_TOKEN;
+const APPLY = process.argv.includes('--apply');
+const TARGETS = [
+ { sku:'DWHF-70238', ownerId:'gid://shopify/Product/6962223218739' },
+ { sku:'DWHF-335000', ownerId:'gid://shopify/Product/7787420450867' },
+ { sku:'DWHF-335003', ownerId:'gid://shopify/Product/7787420844083' },
+ { sku:'DWHF-335004', ownerId:'gid://shopify/Product/7787420909619' },
+ { sku:'DWHF-335005', ownerId:'gid://shopify/Product/7787421007923' },
+ { sku:'DWHF-335006', ownerId:'gid://shopify/Product/7787421106227' },
+ { sku:'DWHF-335007', ownerId:'gid://shopify/Product/7787421204531' },
+ { sku:'DWHF-335008', ownerId:'gid://shopify/Product/7787421335603' },
+ { sku:'DWHF-335009', ownerId:'gid://shopify/Product/7787421433907' },
+ { sku:'DWHF-335010', ownerId:'gid://shopify/Product/7787421499443' },
+];
+async function gql(query, variables){ const r=await fetch(`https://${DOMAIN}/admin/api/${VER}/graphql.json`,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query,variables})}); return r.json(); }
+const M=`mutation($mf:[MetafieldIdentifierInput!]!){ metafieldsDelete(metafields:$mf){ deletedMetafields{ ownerId namespace key } userErrors{ field message } } }`;
+const ids = TARGETS.map(t=>({ ownerId:t.ownerId, namespace:'custom', key:'sold_per' }));
+if(!APPLY){ console.log('DRY RUN — would delete custom.sold_per on:', TARGETS.map(t=>t.sku).join(', '), '\nRe-run with --apply'); process.exit(0); }
+const res = await gql(M,{mf:ids});
+console.log(JSON.stringify(res.data?.metafieldsDelete ?? res, null, 2));
diff --git a/data/tk12129-screenshots.mjs b/data/tk12129-screenshots.mjs
new file mode 100644
index 0000000..a3135b0
--- /dev/null
+++ b/data/tk12129-screenshots.mjs
@@ -0,0 +1,44 @@
+// TK-12129 STEP 3 — screenshot both PDPs on the UNPUBLISHED preview theme (145897783347).
+// Preview cookie is set from ?preview_theme_id on first load (primary domain).
+import pw from '/Users/macstudio3/Projects/animals/node_modules/playwright/index.js';
+const { chromium } = pw;
+const PREV='145897783347';
+const OUT='/Users/macstudio3/Projects/designerwallcoverings/data';
+const shots=[
+ { name:'gardinum', handle:'dwhf-70238-sample-designer-wallcoverings-los-angeles', expect:true, label:'DWHF-70238 Gardinum (should show 10.05 m (33 ft) · sold per single roll)' },
+ { name:'mural-eternaloak', handle:'dwhf-70006-sample-designer-wallcoverings-los-angeles', expect:false, label:'DWHF-70006 Eternal Oak mural (should show NOTHING extra)' },
+];
+const browser=await chromium.launch({ executablePath:'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' });
+const ctx=await browser.newContext({ viewport:{width:1280,height:2200}, deviceScaleFactor:2 });
+const page=await ctx.newPage();
+const results=[];
+for(const s of shots){
+ const url=`https://www.designerwallcoverings.com/products/${s.handle}?preview_theme_id=${PREV}&pb=0`;
+ await page.goto(url,{waitUntil:'domcontentloaded',timeout:60000});
+ await page.waitForSelector('.product__price',{timeout:30000}).catch(()=>{});
+ await page.waitForTimeout(2500);
+ // confirm preview theme is the one rendered
+ const themeName=await page.evaluate(()=>window.Shopify&&window.Shopify.theme?window.Shopify.theme.name+' / '+window.Shopify.theme.role:'?');
+ const hasLine=await page.$('.product__roll-length');
+ const lineText=hasLine?(await hasLine.innerText()).trim():null;
+ // full-page screenshot
+ const fp=`${OUT}/tk12129-${s.name}-fullpage.png`;
+ await page.screenshot({path:fp,fullPage:true});
+ // price-area cropped screenshot
+ let crop=null;
+ const priceEl=await page.$('.product__price');
+ if(priceEl){ crop=`${OUT}/tk12129-${s.name}-pricearea.png`;
+ // scroll into view then clip a padded box around the price block
+ await priceEl.scrollIntoViewIfNeeded();
+ const box=await priceEl.boundingBox();
+ if(box){ const pad=24; await page.screenshot({path:crop, clip:{ x:Math.max(0,box.x-pad), y:Math.max(0,box.y-pad), width:Math.min(1280-Math.max(0,box.x-pad), box.width+pad*2), height:box.height+pad*2 }}); }
+ }
+ results.push({ ...s, themeName, lineText, fullpage:fp, pricearea:crop });
+ console.log(`\n[${s.name}] theme=${themeName}`);
+ console.log(` roll-length line: ${lineText?JSON.stringify(lineText):'(none)'} expect=${s.expect?'PRESENT':'ABSENT'} -> ${(!!lineText)===s.expect?'PASS':'FAIL'}`);
+ console.log(` fullpage: ${fp}`);
+ console.log(` pricearea: ${crop||'(price el not found)'}`);
+}
+await browser.close();
+console.log('\n=== SUMMARY ===');
+console.log(JSON.stringify(results,null,2));
diff --git a/data/tk12129-soldper-write.mjs b/data/tk12129-soldper-write.mjs
new file mode 100644
index 0000000..d64d2f9
--- /dev/null
+++ b/data/tk12129-soldper-write.mjs
@@ -0,0 +1,73 @@
+// TK-12129 STEP 1 — snapshot + write + verify custom.sold_per on the 10 Harlequin canary products.
+// Steve chose a PRICE-AREA-ONLY carrier: custom.sold_per (single_line_text_field) = "Single Roll".
+// Reversible: the key is expected ABSENT pre-write on all 10 -> rollback = metafieldsDelete.
+// Usage: node tk12129-soldper-write.mjs (snapshot + verify only, DRY, no write)
+// node tk12129-soldper-write.mjs --apply (snapshot -> write -> verify read-back)
+import fs from 'node:fs';
+const envPath = '/Users/macstudio3/Projects/secrets-manager/.env';
+const raw = fs.readFileSync(envPath, 'utf8');
+const env = {};
+for (const line of raw.split('\n')) { const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/); if (m) env[m[1]] = m[2].replace(/^["']|["']$/g,''); }
+const DOMAIN='designer-laboratory-sandbox.myshopify.com', VER='2024-10', TOKEN=env.SHOPIFY_ADMIN_TOKEN;
+const APPLY = process.argv.includes('--apply');
+const NS='custom', KEY='sold_per', TYPE='single_line_text_field', VALUE='Single Roll';
+const TARGETS = [
+ { sku:'DWHF-70238', ownerId:'gid://shopify/Product/6962223218739' },
+ { sku:'DWHF-335000', ownerId:'gid://shopify/Product/7787420450867' },
+ { sku:'DWHF-335003', ownerId:'gid://shopify/Product/7787420844083' },
+ { sku:'DWHF-335004', ownerId:'gid://shopify/Product/7787420909619' },
+ { sku:'DWHF-335005', ownerId:'gid://shopify/Product/7787421007923' },
+ { sku:'DWHF-335006', ownerId:'gid://shopify/Product/7787421106227' },
+ { sku:'DWHF-335007', ownerId:'gid://shopify/Product/7787421204531' },
+ { sku:'DWHF-335008', ownerId:'gid://shopify/Product/7787421335603' },
+ { sku:'DWHF-335009', ownerId:'gid://shopify/Product/7787421433907' },
+ { sku:'DWHF-335010', ownerId:'gid://shopify/Product/7787421499443' },
+];
+async function gql(query, variables){ const r=await fetch(`https://${DOMAIN}/admin/api/${VER}/graphql.json`,{method:'POST',headers:{'X-Shopify-Access-Token':TOKEN,'Content-Type':'application/json'},body:JSON.stringify({query,variables})}); return r.json(); }
+const READ=`query($id:ID!){ product(id:$id){ id title handle status mf:metafield(namespace:"${NS}",key:"${KEY}"){ id namespace key type value } len:metafield(namespace:"custom",key:"length"){ value } } }`;
+const SET=`mutation($mf:[MetafieldsSetInput!]!){ metafieldsSet(metafields:$mf){ metafields{ id namespace key type value ownerType } userErrors{ field message } } }`;
+
+async function readAll(tag){
+ const out=[];
+ for(const t of TARGETS){
+ const r=await gql(READ,{id:t.ownerId});
+ const p=r.data?.product;
+ out.push({ sku:t.sku, gid:t.ownerId, title:p?.title, status:p?.status,
+ sold_per: p?.mf?.value ?? null, length: p?.len?.value ?? null });
+ }
+ return out;
+}
+
+// 1) SNAPSHOT (expect sold_per ABSENT on all 10)
+const before = await readAll('before');
+const ts = new Date().toISOString().replace(/[:.]/g,'-');
+const snapPath = `/Users/macstudio3/Projects/designerwallcoverings/data/tk12129-soldper-snapshot-${ts}.json`;
+const snapshot = {
+ ticket:'TK-12129', ts:new Date().toISOString(), agent:'vp-dw-commerce',
+ store:DOMAIN, api_version:VER, token_used:'SHOPIFY_ADMIN_TOKEN (…6755)',
+ metafield:{ namespace:NS, key:KEY, type:TYPE, value:VALUE },
+ note:'custom.sold_per is expected ABSENT before write -> rollback = metafieldsDelete the 10 custom.sold_per. Snapshot records the pre-write value (null=absent).',
+ products: before.map(b=>({ dw_sku:b.sku, gid:b.gid, title:b.title, status:b.status, before:{ 'custom.sold_per': b.sold_per, 'custom.length': b.length } })),
+};
+fs.writeFileSync(snapPath, JSON.stringify(snapshot,null,2));
+console.log('SNAPSHOT ->', snapPath);
+const anyPresent = before.filter(b=>b.sold_per!==null);
+console.log(`pre-write custom.sold_per present on ${anyPresent.length}/10`, anyPresent.map(b=>b.sku).join(',')||'(none — clean, rollback=delete)');
+
+if(!APPLY){ console.log('\nDRY RUN — snapshot written, NO metafield written. Re-run with --apply to write.'); process.exit(0); }
+
+// 2) WRITE
+const inputs = TARGETS.map(t=>({ ownerId:t.ownerId, namespace:NS, key:KEY, type:TYPE, value:VALUE }));
+const res = await gql(SET,{mf:inputs});
+const errs = res.data?.metafieldsSet?.userErrors ?? [];
+console.log('\nWRITE userErrors:', JSON.stringify(errs));
+console.log('WRITE set count:', res.data?.metafieldsSet?.metafields?.length ?? 0, '(expect 10)');
+if(errs.length){ console.error('ABORT: userErrors present'); process.exit(1); }
+
+// 3) VERIFY read-back
+const after = await readAll('after');
+let ok=0;
+console.log('\nVERIFY read-back:');
+for(const a of after){ const good=a.sold_per===VALUE; if(good) ok++; console.log(` ${good?'OK ':'XX '} ${a.sku.padEnd(11)} sold_per=${JSON.stringify(a.sold_per)} length=${JSON.stringify(a.length)}${a.sku==='DWHF-70238'?' <-- Gardinum (screenshot product)':''}`); }
+console.log(`\nVERIFY: ${ok}/10 have custom.sold_per="${VALUE}"`);
+process.exit(ok===10?0:1);
← 3ee5362 auto-data-snapshot: 2026-09-24T10:15:55 (3 data files) — dat
·
back to Designerwallcoverings
·
auto-data-snapshot: 2026-09-24T10:46:49 (1 data files) — dat 2c63e0a →