← back to Silkwallpaper
sku-redact: wire shared vendor-id display scrubber
152e54ef849db9ae1c2e99ccc8fadf1289ded0cf · 2026-05-26 15:16:06 -0700 · Steve Abrams
Files touched
M public/index.htmlA public/sku-redact.js
Diff
commit 152e54ef849db9ae1c2e99ccc8fadf1289ded0cf
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue May 26 15:16:06 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 0fcc435..39378b4 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1370,5 +1370,6 @@ 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();
+})();
← 8a54f67 rails: editorial labels for firing aesthetic buckets
·
back to Silkwallpaper
·
frontend: cleanSku() strips vendor names from displayed sku 165bdac →