[object Object]

← back to Dw Domain Fleet

scrubVendor: optional inter-token separator ([^a-z0-9]*) catches no-separator handle spellings (wolfgordon, gpjbaker) + add 'g p j baker' per-initial variant + collapse mid-handle residue. Closes the 936 wolfgordon URL-slug leaks (#3 morning digest). Export scrubVendor for tests

1bdbba0a8cfbedd6d03020ca579c420dd71cd832 · 2026-05-30 08:27:08 -0700 · Steve

Files touched

Diff

commit 1bdbba0a8cfbedd6d03020ca579c420dd71cd832
Author: Steve <steve@designerwallcoverings.com>
Date:   Sat May 30 08:27:08 2026 -0700

    scrubVendor: optional inter-token separator ([^a-z0-9]*) catches no-separator handle spellings (wolfgordon, gpjbaker) + add 'g p j baker' per-initial variant + collapse mid-handle residue. Closes the 936 wolfgordon URL-slug leaks (#3 morning digest). Export scrubVendor for tests
---
 shared/render.js | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/shared/render.js b/shared/render.js
index e222209..d8d02c1 100644
--- a/shared/render.js
+++ b/shared/render.js
@@ -90,21 +90,26 @@ function scrubTags(tags) {
  * cleaning the slug never breaks a product link. (2026-05-29 fleet vendor-leak
  * audit: slug/handle + visible card-title vector.) */
 const _VENDOR_SCRUB = VENDORS_DENYLIST
-  .concat(['brunschwig fils', 'lee jofa modern', 'gp j baker',
+  .concat(['brunschwig fils', 'lee jofa modern', 'gp j baker', 'g p j baker',
            'cole and son', 'cole son', 'clarke and clarke', 'clarke clarke',
            'morris and co', 'morris co', 'caroline cecil textiles', 'caroline cecil'])
   .slice().sort((a, b) => b.length - a.length)
-  // Split on ANY non-alphanumeric run and drop empties, so each vendor's word
-  // tokens join with a flexible separator — matches "Brunschwig & Fils" (title)
-  // AND "brunschwig-fils" (slug). (The old \s+ split left an empty token for the
-  // "&" → a double-separator regex that failed on single-hyphen handles.)
-  .map(v => new RegExp('\\b' + v.split(/[^a-z0-9]+/i).filter(Boolean).join('[^a-z0-9]+') + '\\b', 'ig'));
+  // Split on non-alphanumeric runs, drop empties, join with an OPTIONAL separator
+  // ([^a-z0-9]* — zero-or-more) so each vendor matches BOTH the separated form
+  // ("Brunschwig & Fils", "wolf-gordon") AND the concatenated/no-separator
+  // handle spelling ("wolfgordon", "gpjbaker") — the 936 wolfgordon URL-slug
+  // leaks the 2026-05-30 reaudit found. \b boundaries keep it from over-matching
+  // inside unrelated words. Tokens <2 chars are kept literal-only (no optional
+  // sep on a single letter would over-match), so multi-initial vendors use the
+  // explicit "g p j baker" variant above.
+  .map(v => new RegExp('\\b' + v.split(/[^a-z0-9]+/i).filter(Boolean).join('[^a-z0-9]*') + '\\b', 'ig'));
 function scrubVendor(s) {
   if (s == null) return s;
   let o = String(s);
   for (let i = 0; i < _VENDOR_SCRUB.length; i++) o = o.replace(_VENDOR_SCRUB[i], ' ');
   return o
     .replace(/\s{2,}/g, ' ')
+    .replace(/(\s*-\s*)+/g, '-')   // collapse mid-string residue like "x- -1" → "x-1"
     .replace(/-{2,}/g, '-')
     .replace(/^[\s\-–—|·,&]+|[\s\-–—|·,&]+$/g, '')
     .replace(/\s{2,}/g, ' ')
@@ -748,4 +753,4 @@ ${script(cfg)}
 </body></html>`;
 }
 
-module.exports = { homePage, catalogPage, aboutPage, infoPage, productPage, clean, esc, productSlug };
+module.exports = { homePage, catalogPage, aboutPage, infoPage, productPage, clean, esc, productSlug, scrubVendor };

← 0ac4d91 render.js productSlug: guard both slug returns with hasVendo  ·  back to Dw Domain Fleet  ·  Add 2026-05-31 read-only vendor-leak re-verify: slug/chip sc 55a7f0b →