[object Object]

← back to Designer Wallcoverings

Daisy Bennett: review viewer + Shopify draft payloads (63 products, draft-only, leak+gate PASS)

0862568a63e1f785747c527e24594cdad417e5aa · 2026-07-06 11:18:55 -0700 · Steve

Files touched

Diff

commit 0862568a63e1f785747c527e24594cdad417e5aa
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Jul 6 11:18:55 2026 -0700

    Daisy Bennett: review viewer + Shopify draft payloads (63 products, draft-only, leak+gate PASS)
---
 .../build-pr-drafts-daisy-bennett.cjs              | 91 ++++++++++++++++++++++
 .../build-viewer-daisy-bennett.cjs                 | 90 +++++++++++++++++++++
 2 files changed, 181 insertions(+)

diff --git a/scripts/wallquest-refresh/build-pr-drafts-daisy-bennett.cjs b/scripts/wallquest-refresh/build-pr-drafts-daisy-bennett.cjs
new file mode 100644
index 00000000..c7c9c149
--- /dev/null
+++ b/scripts/wallquest-refresh/build-pr-drafts-daisy-bennett.cjs
@@ -0,0 +1,91 @@
+// Phase 8 (DRAFT ONLY) — transform enriched Daisy Bennett naturals → Phillipe Romano
+// private-label Shopify draft payloads, PRESERVING real vendor pattern names (no beach-city
+// remap; Abatha/Boccali/Venezia are neutral). NOTHING is pushed here — writes draft JSON +
+// a human preview for Steve's approval gate. SKUs are NOT registered until an approved push.
+const fs = require('fs');
+const IN = process.env.IN || '/tmp/wq-daisy-bennett-enriched.json';
+const OUT = '/tmp/daisy-bennett-PR-drafts.json';
+const PREVIEW = '/tmp/daisy-bennett-PR-preview.txt';
+
+const cleanWallpaper = (s) => !s ? s : s.replace(/\bWalls Wallpaper\b/gi, 'Wallcoverings').replace(/\bWallpapers\b/gi, 'Wallcoverings').replace(/\bWallpaper\b/gi, 'Wallcovering');
+const SMALL = new Set(['a','an','the','and','but','or','for','in','on','at','of','to','with','&']);
+const toTitleCase = (s) => !s ? s : s.split(/\s+/).map((w, i) => (i > 0 && SMALL.has(w.toLowerCase())) ? w.toLowerCase() : w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
+const BAD_NAME = /^\s*$|unknown|page not found|404|not found|undefined|null/i;
+
+const recs = JSON.parse(fs.readFileSync(IN, 'utf8'));
+const drafts = [];
+for (const r of recs) {
+  const family = (r.pattern || '').split(/\s+/).slice(-1)[0] || 'Naturals'; // Grass, Weave, Cloth…
+  let patternName = toTitleCase(r.pattern || '');
+  if (BAD_NAME.test(patternName)) patternName = r.mfr_sku; // NEVER "Unknown" (DW rule)
+  const color = toTitleCase(r.color || r.ai_dominant_color || '');
+  const title = cleanWallpaper(`${patternName}${color ? ' ' + color : ''} | Phillipe Romano`);
+  const tags = [
+    family, 'Natural Texture', 'Grasscloth & Naturals', 'Phillipe Romano',
+    ...(r.ai_styles || []), ...(r.ai_colors || []), color,
+  ].filter(Boolean).filter((v, i, a) => a.indexOf(v) === i);
+
+  drafts.push({
+    dw_sku: r.dw_sku,
+    title,
+    vendor: 'Phillipe Romano',                 // private label — WallQuest hidden
+    product_type: 'Wallcovering',
+    status: 'draft',                            // draft until an approved push
+    body_html: cleanWallpaper(r.ai_description || ''),
+    tags: tags.map(cleanWallpaper),
+    collection: family,
+    price: '0.00',
+    options: [{ name: 'Size' }],
+    variants: [
+      { option1: 'Sample', sku: `${r.dw_sku}-Sample`, price: '4.25', inventory_management: null, inventory_policy: 'deny' },
+      { option1: 'Standard', sku: r.dw_sku, price: String(r.our_price ?? ''), inventory_quantity: 2026, inventory_policy: 'continue' },
+    ],
+    images: r.images || [],
+    metafields: [
+      { namespace: 'custom', key: 'manufacturer_sku', value: r.mfr_sku, type: 'single_line_text_field' },
+      { namespace: 'dwc', key: 'manufacturer_sku', value: r.mfr_sku, type: 'single_line_text_field' },
+      { namespace: 'private_label', key: 'real_vendor_name', value: 'WallQuest', type: 'single_line_text_field' },
+      { namespace: 'private_label', key: 'source_book', value: 'Daisy Bennett - Natural Wallcoverings', type: 'single_line_text_field' },
+      { namespace: 'custom', key: 'color', value: color, type: 'single_line_text_field' },
+      { namespace: 'custom', key: 'color_hex', value: r.color_hex || '', type: 'single_line_text_field' },
+      { namespace: 'global', key: 'width', value: r.width || '', type: 'single_line_text_field' },
+      { namespace: 'specs', key: 'material', value: r.material || family, type: 'single_line_text_field' },
+      { namespace: 'specs', key: 'repeat_v', value: r.repeat || '', type: 'single_line_text_field' },
+      { namespace: 'specs', key: 'match', value: r.match || '', type: 'single_line_text_field' },
+    ],
+    _cost: r.price_retail, _mfr: r.mfr_sku, _src_url: r.product_url,
+  });
+}
+
+fs.writeFileSync(OUT, JSON.stringify(drafts, null, 2));
+
+// leak self-check: no forbidden vendor words in customer-facing fields
+const FORBIDDEN = /wallquest|chesapeake|brewster|nextwall|seabrook|voyage|miyabi/i;
+let leaks = 0;
+for (const d of drafts) {
+  const cust = [d.title, d.body_html, ...d.tags, d.collection].join(' | ');
+  if (FORBIDDEN.test(cust)) { leaks++; console.error('⚠ LEAK in', d.dw_sku, '→', cust.match(FORBIDDEN)[0]); }
+}
+// activation-gate self-check (DW rule): specs(width) + description + >=1 image + sample variant
+let gateFail = 0;
+for (const d of drafts) {
+  const okWidth = d.metafields.some(m => m.key === 'width' && m.value);
+  const okDesc = d.body_html && !BAD_NAME.test(d.body_html);
+  const okImg = (d.images || []).length >= 1;
+  const okSample = d.variants.some(v => /-Sample$/.test(v.sku));
+  if (!(okWidth && okDesc && okImg && okSample)) gateFail++;
+}
+
+const byFam = {};
+for (const d of drafts) (byFam[d.collection] = byFam[d.collection] || []).push(d);
+let out = `DAISY BENNETT NATURAL WALLCOVERINGS → PHILLIPE ROMANO — ${drafts.length} DRAFT PRODUCTS (NOT PUSHED)\n`;
+out += `leak-check: ${leaks === 0 ? 'PASS (0 forbidden vendor names customer-facing)' : leaks + ' LEAKS'}\n`;
+out += `activation-gate (width+desc+image+sample): ${gateFail === 0 ? 'PASS all ' + drafts.length : gateFail + ' FAIL'}\n\n`;
+for (const [fam, ds] of Object.entries(byFam).sort()) {
+  out += `── ${fam} (${ds.length}) ──\n`;
+  for (const d of ds) out += `  ${d.dw_sku}  ${d.title.replace(' | Phillipe Romano','')}  | cost $${d._cost} → std $${d.variants[1].price} | ${d.images.length} imgs\n`;
+}
+fs.writeFileSync(PREVIEW, out);
+console.log(out);
+console.log(`DRAFTS → ${OUT}\nPREVIEW → ${PREVIEW}`);
+console.log(leaks === 0 ? 'LEAK-CHECK PASS ✓' : `LEAK-CHECK FAIL: ${leaks}`);
diff --git a/scripts/wallquest-refresh/build-viewer-daisy-bennett.cjs b/scripts/wallquest-refresh/build-viewer-daisy-bennett.cjs
new file mode 100644
index 00000000..790a49cd
--- /dev/null
+++ b/scripts/wallquest-refresh/build-viewer-daisy-bennett.cjs
@@ -0,0 +1,90 @@
+// Build a self-contained web viewer for the Daisy Bennett Natural Wallcoverings onboard.
+// Reads enriched (or normalized) records; PRESERVES the real vendor pattern names (Abatha,
+// Boccali, Venezia — neutral, no vendor leak) rather than remapping. Product grid + sort +
+// density slider (Steve's standing rules). Images referenced from the public WallQuest CDN.
+const fs = require('fs');
+const OUTDIR = '/tmp/daisy-bennett-viewer';
+const ENR = '/tmp/wq-daisy-bennett-enriched.json';
+const NORM = '/tmp/wq-daisy-bennett-normalized.json';
+const src = fs.existsSync(ENR) ? ENR : NORM;
+const recs = JSON.parse(fs.readFileSync(src, 'utf8'));
+
+const data = recs.map(r => ({
+  dw_sku: r.dw_sku, mfr_sku: r.mfr_sku,
+  pattern: r.pattern || '', color: r.color || '',
+  family: (r.pattern || '').split(/\s+/).slice(-1)[0] || 'Naturals',
+  cost: r.price_retail, retail: r.our_price,
+  hex: r.color_hex || '', desc: r.ai_description || '',
+  styles: r.ai_styles || [],
+  img: (r.images || []).find(u => /_650\./.test(u)) || (r.images || [])[0] || '',
+  src_url: r.product_url,
+}));
+data.sort((a, b) => a.pattern.localeCompare(b.pattern) || a.color.localeCompare(b.color));
+
+fs.mkdirSync(OUTDIR, { recursive: true });
+const enrichedN = data.filter(d => d.desc).length;
+const html = `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
+<title>Daisy Bennett Natural Wallcoverings → Phillipe Romano — Onboard Preview</title>
+<style>
+:root{--cols:4;--bg:#faf8f4;--ink:#1a1a1a;--mut:#8a8377;--line:#e7e2d8;--accent:#3d5a4c}
+*{box-sizing:border-box}body{margin:0;font:15px/1.5 -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;background:var(--bg);color:var(--ink)}
+header{padding:22px 28px;border-bottom:1px solid var(--line);background:#fff;position:sticky;top:0;z-index:5}
+h1{margin:0 0 3px;font-size:19px;letter-spacing:.14em;text-transform:uppercase;font-weight:600}
+.sub{color:var(--mut);font-size:12.5px;letter-spacing:.03em}
+.controls{display:flex;gap:18px;align-items:center;flex-wrap:wrap;margin-top:14px}
+.controls label{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--mut);margin-right:6px}
+select{padding:5px 8px;border:1px solid var(--line);border-radius:6px;background:#fff;font-size:13px}
+.pl{margin-left:auto;font-size:11px;color:var(--mut);border:1px solid var(--line);padding:4px 9px;border-radius:20px}
+.grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:16px;padding:22px 28px}
+.card{background:#fff;border:1px solid var(--line);border-radius:10px;overflow:hidden;display:flex;flex-direction:column}
+.ph{aspect-ratio:1/1;background:#efeae0 center/cover no-repeat;position:relative}
+.mat{position:absolute;top:8px;left:8px;background:rgba(255,255,255,.92);font-size:10px;letter-spacing:.06em;text-transform:uppercase;padding:3px 8px;border-radius:20px;color:var(--accent);font-weight:600}
+.body{padding:11px 13px 13px}
+.t{font-size:13.5px;font-weight:600;line-height:1.3}
+.c{font-size:12px;color:var(--mut);margin-top:2px}
+.meta{display:flex;justify-content:space-between;align-items:center;margin-top:9px;font-size:11.5px;color:var(--mut)}
+.sku{font-family:ui-monospace,Menlo,monospace;font-size:11px}
+.px{color:var(--accent);font-weight:600}
+.dot{display:inline-block;width:10px;height:10px;border-radius:50%;border:1px solid rgba(0,0,0,.15);vertical-align:-1px;margin-right:5px}
+.desc{font-size:11.5px;color:#5c574d;margin-top:8px;display:none}
+body.showdesc .desc{display:block}
+</style></head><body>
+<header>
+<h1>Daisy Bennett · Natural Wallcoverings</h1>
+<div class="sub">Private-label onboard preview → <b>Phillipe Romano</b> · real pattern names · ${data.length} products · <b>DWAY-</b> SKUs (provisional) · <b>NOT PUBLISHED</b> (staged for approval)</div>
+<div class="controls">
+<div><label>Sort</label><select id="sort" onchange="render()">
+<option value="pattern">Pattern → Color</option>
+<option value="family">Family</option>
+<option value="color">Color A→Z</option>
+<option value="sku">SKU A→Z</option>
+<option value="pricelow">Price ↑</option>
+<option value="pricehigh">Price ↓</option>
+</select></div>
+<div><label>Density</label><input type="range" id="dens" min="2" max="7" value="4" oninput="setCols(this.value)"></div>
+<div><label><input type="checkbox" id="dc" onchange="document.body.classList.toggle('showdesc',this.checked)"> descriptions</label></div>
+<span class="pl">leak-safe: no WallQuest on cards · enriched ${enrichedN}/${data.length}</span>
+</div></header>
+<div class="grid" id="grid"></div>
+<script>
+const DATA=${JSON.stringify(data)};
+function setCols(n){document.documentElement.style.setProperty('--cols',n);localStorage.dbCols=n}
+function render(){
+ const s=document.getElementById('sort').value;const d=[...DATA];
+ const by={pattern:(a,b)=>a.pattern.localeCompare(b.pattern)||a.color.localeCompare(b.color),
+  family:(a,b)=>a.family.localeCompare(b.family)||a.color.localeCompare(b.color),
+  color:(a,b)=>a.color.localeCompare(b.color),sku:(a,b)=>a.dw_sku.localeCompare(b.dw_sku),
+  pricelow:(a,b)=>a.retail-b.retail,pricehigh:(a,b)=>b.retail-a.retail};
+ d.sort(by[s]);
+ document.getElementById('grid').innerHTML=d.map(x=>\`<div class="card">
+  <div class="ph" style="background-image:url('\${x.img}')"><span class="mat">\${x.family}</span></div>
+  <div class="body"><div class="t">\${x.pattern}</div>
+  <div class="c">\${x.hex?\`<span class="dot" style="background:\${x.hex}"></span>\`:''}\${x.color||'—'}</div>
+  <div class="meta"><span class="sku">\${x.dw_sku}</span><span><span class="px">$\${(x.retail||0).toFixed(2)}</span> · cost $\${(x.cost||0).toFixed(0)}</span></div>
+  \${x.desc?\`<div class="desc">\${x.desc}</div>\`:''}</div></div>\`).join('');
+}
+if(localStorage.dbCols)setCols(localStorage.dbCols);
+render();
+</script></body></html>`;
+fs.writeFileSync(`${OUTDIR}/index.html`, html);
+console.log(`viewer → ${OUTDIR}/index.html  (${data.length} products, enriched ${enrichedN})`);

← 95ef94b5 Onboard Daisy Bennett Naturals Vol 2 (WallQuest→Phillipe Rom  ·  back to Designer Wallcoverings  ·  Add Daisy Bennett Vol 2 internal staging viewer (sort+densit ad9acbf8 →