[object Object]

← back to Mylarwallpaper

sku-redact: wire shared vendor-id display scrubber

1b9fcdf65569773397070796efa3e596a4e77cf5 · 2026-05-26 15:16:03 -0700 · Steve Abrams

Files touched

Diff

commit 1b9fcdf65569773397070796efa3e596a4e77cf5
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue May 26 15:16:03 2026 -0700

    sku-redact: wire shared vendor-id display scrubber
---
 public/index.html    |   1 +
 public/sku-redact.js | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 106 insertions(+)

diff --git a/public/index.html b/public/index.html
index 8909ea9..c1a31f5 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1182,5 +1182,6 @@ async function renderRailSections(){
         defer onerror="this.remove()"></script>
 
 <script src="/corner-nav.js" defer></script>
+<script src="/sku-redact.js" defer></script>
 </body>
 </html>
diff --git a/public/sku-redact.js b/public/sku-redact.js
new file mode 100644
index 0000000..333def3
--- /dev/null
+++ b/public/sku-redact.js
@@ -0,0 +1,105 @@
+// _shared/sku-redact.js — DISPLAY-ONLY vendor scrub for product identifiers.
+//
+// Why: customer-facing card meta (.ven), list rows (.r-sku) and the detail modal
+// ([data-d-brand]) render the product's sku || handle. For vendor-imported rows
+// that identifier carries a 3rd-party vendor name baked into the slug, e.g.
+//   "wolfgordonwallcovering_dwwg_dnuv-512-jpg", "...-jeffrey-stevens",
+//   "dwk-31477-koroseal-arte-type-2-...", "agra-okra-arte-international".
+// That violates the hard rule "DW vendor names NEVER in customer-facing UI".
+//
+// The sku/handle DATA stays intact (the sample-order flow + product links depend
+// on them) — this only sanitizes what is SHOWN. When a displayed identifier
+// segment carries a non-house vendor token, that segment is dropped; the DW brand
+// label and any price segment are kept. House brands (Hollywood, Phillipe Romano,
+// Retro Walls, Designer Wallcoverings, Maison Lustre) are never touched.
+//
+// Runs on load + MutationObserver because cards hydrate client-side and the grid
+// infinite-scrolls; the detail modal updates an existing node's text on open.
+// Idempotent (only writes when the text actually changes) so the observer can't loop.
+//
+// Deploy like corner-nav.js: copied into each site's public/, loaded via
+//   <script src="/sku-redact.js" defer></script>
+// Canonical source is this file; keep per-site public/ copies in sync.
+(function () {
+  // 3rd-party vendor tokens (mirrors LEAD_VENDOR in _shared/api-vendor-redact.js).
+  var VENDORS = [
+    'wolf gordon', 'nina campbell', 'jeffrey stevens', 'versace',
+    'arte international', 'lee jofa modern', 'lee jofa', 'innovations usa',
+    'innovations', 'koroseal', 'schumacher', 'candice olson', 'thibaut',
+    'maya romanoff', 'scalamandre', 'dedar', 'carnegie', 'cole and son',
+    'kravet', 'clarke and clarke', 'brunschwig', 'gp j baker', 'sandberg',
+    'designers guild', 'graham and brown', 'harlequin', 'andrew martin',
+    'ronald redding', 'blithfield', 'coordonne', 'fentucci', 'sister parish',
+    'phillip jeffries', 'fromental', 'westport', 'breegan', 'les ensembliers',
+    'roger thomas', 'stacy garcia'
+  ];
+  function norm(s) { return String(s == null ? '' : s).toLowerCase().replace(/[^a-z0-9]+/g, ''); }
+  var VEN_NORM = VENDORS.map(norm).filter(Boolean);
+
+  // True if the identifier text carries a 3rd-party vendor token.
+  function hasVendor(idPart) {
+    var n = norm(idPart);
+    if (!n) return false;
+    for (var i = 0; i < VEN_NORM.length; i++) {
+      if (n.indexOf(VEN_NORM[i]) >= 0) return true;
+    }
+    return false;
+  }
+
+  // "Brand · id · price" → drop only the segment(s) carrying a vendor token.
+  // The first segment (brand) and clean segments (e.g. price) are kept.
+  function cleanMeta(text) {
+    if (text.indexOf('·') < 0) {        // no "·" separator: bare value
+      return hasVendor(text) ? '' : text;
+    }
+    var parts = text.split('·');
+    var out = [];
+    for (var i = 0; i < parts.length; i++) {
+      var seg = parts[i].trim();
+      if (i === 0 || !hasVendor(seg)) out.push(seg);
+    }
+    return out.join(' · ');
+  }
+
+  function scrubEl(el) {
+    if (!el || el.nodeType !== 1) return;
+    var t = el.textContent;
+    if (!t) return;
+    // Bare-identifier cell (list view): replace a vendor-y value with an em dash.
+    if (el.classList && el.classList.contains('r-sku')) {
+      if (hasVendor(t)) { if (el.textContent !== '—') el.textContent = '—'; }
+      return;
+    }
+    // Brand/meta line: drop vendor-y segments, keep brand + price.
+    var cleaned = cleanMeta(t);
+    if (cleaned !== t) el.textContent = cleaned;
+  }
+
+  var SEL = '.ven, .r-sku, [data-d-brand], .d-brand';
+  function scrubAll(root) {
+    if (!root || !root.querySelectorAll) return;
+    if (root.matches && root.matches(SEL)) scrubEl(root);
+    var els = root.querySelectorAll(SEL);
+    for (var i = 0; i < els.length; i++) scrubEl(els[i]);
+  }
+
+  function boot() {
+    scrubAll(document);
+    if (!window.MutationObserver) return;
+    var mo = new MutationObserver(function (muts) {
+      for (var i = 0; i < muts.length; i++) {
+        var m = muts[i];
+        if (m.type === 'characterData') {
+          var p = m.target && m.target.parentElement;
+          if (p && p.closest) { var host = p.closest(SEL); if (host) scrubEl(host); }
+        } else {
+          for (var j = 0; j < m.addedNodes.length; j++) scrubAll(m.addedNodes[j]);
+        }
+      }
+    });
+    mo.observe(document.body, { childList: true, subtree: true, characterData: true });
+  }
+
+  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot);
+  else boot();
+})();

← 93ae824 rails: editorial labels for firing aesthetic buckets  ·  back to Mylarwallpaper  ·  frontend: cleanSku() strips vendor names from displayed sku 56d3553 →