[object Object]

← back to Healthcarewallpaper

rails: tag-aware via shared rail-match helper + editorial LABELS (fix degenerate single-rail)

f427902a6c0b7c0e27cfd74ca06bffbc12e97f7d · 2026-05-26 10:27:11 -0700 · Steve Abrams

- Replaced aesthetic-only /api/sliders (matched aesthetic field only → 1 rail, commercial=100%)
- Wire buildRails(PRODUCTS_NICHE, SITE_RAILS) + railFacets() from _shared/rail-match.js
- Curated site.config.json rails: vinyl/traditional/stripe/geometric/grasscloth/minimalist
- Added LABELS for all rail keys in public/index.html
- 6 rails fire (70%/34%/28%/26%/12%/14%), each 12 items capped, no vendor names

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files touched

Diff

commit f427902a6c0b7c0e27cfd74ca06bffbc12e97f7d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue May 26 10:27:11 2026 -0700

    rails: tag-aware via shared rail-match helper + editorial LABELS (fix degenerate single-rail)
    
    - Replaced aesthetic-only /api/sliders (matched aesthetic field only → 1 rail, commercial=100%)
    - Wire buildRails(PRODUCTS_NICHE, SITE_RAILS) + railFacets() from _shared/rail-match.js
    - Curated site.config.json rails: vinyl/traditional/stripe/geometric/grasscloth/minimalist
    - Added LABELS for all rail keys in public/index.html
    - 6 rails fire (70%/34%/28%/26%/12%/14%), each 12 items capped, no vendor names
    
    Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---
 package-lock.json | 13 +++++++++++++
 public/index.html |  2 +-
 server.js         | 12 ++++--------
 site.config.json  | 10 +++++-----
 4 files changed, 23 insertions(+), 14 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 5bf47da..e5596b9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
       "name": "healthcarewallpaper",
       "version": "0.1.0",
       "dependencies": {
+        "dotenv": "^17.4.2",
         "express": "^4.21.0",
         "helmet": "^8.1.0"
       }
@@ -172,6 +173,18 @@
         "npm": "1.2.8000 || >= 1.4.16"
       }
     },
+    "node_modules/dotenv": {
+      "version": "17.4.2",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
+      "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://dotenvx.com"
+      }
+    },
     "node_modules/dunder-proto": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
diff --git a/public/index.html b/public/index.html
index 1edd852..65bb94d 100644
--- a/public/index.html
+++ b/public/index.html
@@ -507,7 +507,7 @@ const HOSTKEY = location.hostname.replace(/\./g, '_');
     document.documentElement.dataset.view = state.view;
   } catch(e){}
 })();
-const LABELS = {"all":"All","commercial":"Commercial-Grade"};
+const LABELS = {"all":"All","commercial":"Commercial-Grade","vinyl":"Vinyl & Scrubbable","traditional":"Traditional Patterns","stripe":"Stripe Designs","geometric":"Geometric Patterns","grasscloth":"Grasscloth & Natural","minimalist":"Minimalist","textured":"Textured","contemporary":"Contemporary","beige":"Neutral & Beige","architectural":"Architectural"};
 
 function escAttr(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' }[c])); }
 // Image-URL allowlist: only DW Shopify CDN + designerwallcoverings.com (defends against XSS via crafted products.json)
diff --git a/server.js b/server.js
index 75e5d1c..d93ee39 100644
--- a/server.js
+++ b/server.js
@@ -23,6 +23,7 @@ if (ADMIN_ENABLED) {
 const siteCfg = JSON.parse(fs.readFileSync(path.join(__dirname, 'site.config.json'), 'utf8'));
 const SITE_SLUG = siteCfg.slug || __SITE;
 const SITE_RAILS = Array.isArray(siteCfg.rails) ? siteCfg.rails : [];
+const { buildRails, railFacets } = require('../_shared/rail-match');
 
 function isJunk(p) {
   if (!p.image_url || !p.image_url.trim()) return true;
@@ -179,18 +180,13 @@ app.get('/api/products', (req, res) => {
 });
 
 app.get('/api/sliders', (req, res) => {
-  const out = [];
-  for (const a of SITE_RAILS) {
-    const items = PRODUCTS_NICHE.filter(p => p.aesthetic === a).slice(0, 12);
-    if (items.length >= 4) out.push({ aesthetic: a, items });
-  }
-  res.json({ rails: out });
+  res.json({ rails: buildRails(PRODUCTS_NICHE, SITE_RAILS) });
 });
 
 app.get('/api/facets', (req, res) => {
-  const aesthetics = {}; const vendors = {};
+  const aesthetics = railFacets(PRODUCTS_NICHE, SITE_RAILS);
+  const vendors = {};
   for (const p of PRODUCTS_NICHE) {
-    aesthetics[p.aesthetic] = (aesthetics[p.aesthetic] || 0) + 1;
     vendors[p.vendor] = (vendors[p.vendor] || 0) + 1;
   }
   res.json({ aesthetics, vendors, total: PRODUCTS_NICHE.length });
diff --git a/site.config.json b/site.config.json
index 10df7aa..91975e1 100644
--- a/site.config.json
+++ b/site.config.json
@@ -10,12 +10,12 @@
     "accent": "#80c8b8"
   },
   "rails": [
-    "commercial",
     "vinyl",
-    "architectural",
-    "textured",
-    "contemporary",
-    "beige"
+    "traditional",
+    "stripe",
+    "geometric",
+    "grasscloth",
+    "minimalist"
   ],
   "port": 9859
 }

← e19a6c9 rails: editorial labels for firing aesthetic buckets  ·  back to Healthcarewallpaper  ·  rails: tag-aware facet filter (productInRail) so rail drill- 03acc23 →