← 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
A scripts/wallquest-refresh/build-pr-drafts-daisy-bennett.cjsA scripts/wallquest-refresh/build-viewer-daisy-bennett.cjs
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 →