← back to Designer Wallcoverings
Scrub residual Brewster/York from 44 product descriptions (body_html leak vector)
64278b9240f5191e9e28a584b7aaa613ba1485fc · 2026-06-25 08:30:24 -0700 · Steve
Title/vendor/tag fix missed body_html — descriptions said 'collection by Brewster
& York', which also propagated into meta/og/twitter/JSON-LD. Caught by the new
dw-leak-scanner --pdp pass on its first run. Each description's brand mention
replaced with the product's own house-line vendor (LA Walls/Malibu).
Files touched
A scripts/fix-brewster-york-descriptions.mjs
Diff
commit 64278b9240f5191e9e28a584b7aaa613ba1485fc
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Jun 25 08:30:24 2026 -0700
Scrub residual Brewster/York from 44 product descriptions (body_html leak vector)
Title/vendor/tag fix missed body_html — descriptions said 'collection by Brewster
& York', which also propagated into meta/og/twitter/JSON-LD. Caught by the new
dw-leak-scanner --pdp pass on its first run. Each description's brand mention
replaced with the product's own house-line vendor (LA Walls/Malibu).
---
scripts/fix-brewster-york-descriptions.mjs | 59 ++++++++++++++++++++++++++++++
1 file changed, 59 insertions(+)
diff --git a/scripts/fix-brewster-york-descriptions.mjs b/scripts/fix-brewster-york-descriptions.mjs
new file mode 100644
index 00000000..55a87673
--- /dev/null
+++ b/scripts/fix-brewster-york-descriptions.mjs
@@ -0,0 +1,59 @@
+#!/usr/bin/env node
+// Scrub residual Brewster/York brand mentions from product DESCRIPTIONS (body_html)
+// — the leak vector that survives a title/vendor/tag fix and propagates into the
+// meta/og/twitter/JSON-LD tags. Replaces the brand mention with the product's OWN
+// house-line vendor (LA Walls stays LA Walls; Malibu stays Malibu). Reads the
+// dw-leak-scanner PDP result for the body_html-flagged handles. DRY unless APPLY=1.
+import fs from 'node:fs';
+
+const env = Object.fromEntries(
+ fs.readFileSync(new URL('../.env', import.meta.url), 'utf8')
+ .split('\n').filter(l => l.includes('=') && !l.trim().startsWith('#'))
+ .map(l => { const i = l.indexOf('='); return [l.slice(0, i).trim(), l.slice(i + 1).trim()]; })
+);
+const SHOP = env.SHOPIFY_STORE_DOMAIN, TOKEN = env.SHOPIFY_ADMIN_TOKEN, VER = env.SHOPIFY_ADMIN_API_VERSION || '2024-01';
+const APPLY = process.env.APPLY === '1';
+const SCAN = `${process.env.HOME}/.claude/skills/dw-leak-scanner/data/latest.json`;
+
+async function gql(query, variables = {}) {
+ const r = await fetch(`https://${SHOP}/admin/api/${VER}/graphql.json`, {
+ method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' },
+ body: JSON.stringify({ query, variables }),
+ });
+ const j = await r.json();
+ if (j.errors) throw new Error(JSON.stringify(j.errors));
+ return j.data;
+}
+
+// Replace the banned brand with the product's own vendor; never touch "New York".
+function scrub(html, vendor) {
+ return String(html)
+ .replace(/Brewster\s*&(?:amp;)*\s*York/gi, vendor) // "Brewster & York" (any &-encoding)
+ .replace(/\bBrewster\b/gi, vendor) // any standalone Brewster
+ .replace(/\bYork Wallcoverings?\b/gi, vendor) // "York Wallcovering(s)"
+ .replace(/\bby York\b/gi, `by ${vendor}`); // "by York"
+}
+
+const scan = JSON.parse(fs.readFileSync(SCAN, 'utf8'));
+const handles = [...new Set(scan.results
+ .filter(r => r.fields_hit && r.fields_hit.body_html)
+ .map(r => r.url.split('/products/')[1]))];
+console.log(`\n=== ${APPLY ? 'APPLY' : 'DRY RUN'} — ${handles.length} description leaks (store ${SHOP}) ===\n`);
+
+let fixed = 0, skipped = 0;
+for (const h of handles) {
+ const d = await gql(`query($q:String!){products(first:1,query:$q){nodes{id vendor descriptionHtml}}}`, { q: `handle:${h}` });
+ const p = d.products.nodes[0];
+ if (!p) { console.log(` skip ${h} — not found`); skipped++; continue; }
+ const next = scrub(p.descriptionHtml, p.vendor);
+ if (next === p.descriptionHtml) { console.log(` noop ${h} — nothing matched`); skipped++; continue; }
+ const before = (p.descriptionHtml.match(/brewster|york/gi) || []).length;
+ const after = (next.match(/\bbrewster\b|\byork\b/gi) || []).filter(x => true).length;
+ if (!APPLY) { console.log(` DRY [${p.vendor}] ${h} (brewster/york mentions ${before}→checking)`); fixed++; continue; }
+ const u = await gql(`mutation($input:ProductInput!){productUpdate(input:$input){product{id} userErrors{message}}}`,
+ { input: { id: p.id, descriptionHtml: next } });
+ const errs = u.productUpdate.userErrors;
+ console.log(errs.length ? ` ERR ${h} :: ${JSON.stringify(errs)}` : ` ✓ [${p.vendor}] ${h}`);
+ if (!errs.length) fixed++;
+}
+console.log(`\n=== ${APPLY ? 'fixed' : 'would fix'} ${fixed}, skipped ${skipped} ===\n`);
← c3b4e551 Anna French scraper: full-page capture (all specs+cost+care+
·
back to Designer Wallcoverings
·
Anna French STEP2+3: local Full-Monte enrichment + interior- 785b0977 →