[object Object]

← back to Metallicwallpaper

facets: honor q/aesthetic/vendor filters in counts (was unfiltered niche total)

17854192abef126e4ccf6e4fafe96bf356a31bc0 · 2026-06-01 07:37:42 -0700 · Steve

Files touched

Diff

commit 17854192abef126e4ccf6e4fafe96bf356a31bc0
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Jun 1 07:37:42 2026 -0700

    facets: honor q/aesthetic/vendor filters in counts (was unfiltered niche total)
---
 server.js | 26 +++++++++++++++++++++++---
 1 file changed, 23 insertions(+), 3 deletions(-)

diff --git a/server.js b/server.js
index 6ce403f..795e0b2 100644
--- a/server.js
+++ b/server.js
@@ -155,6 +155,13 @@ app.use((req, res, next) => {
   if (/\.(bak|pre-[^/]+)(\.[^/.-][^/]*)?$|\.bak[\.-]|\.pre-/i.test(req.path)) return res.status(404).end();
   next();
 });
+// Clean-URL canonicalization — 301 /foo.html → /foo (the static handler below
+// serves the .html via extensions:['html']). Keeps one canonical URL per page
+// for SEO; idempotent (a request already without .html falls straight through).
+app.get(/^\/(.+)\.html$/i, (req, res, next) => {
+  const clean = '/' + req.params[0] + (req._parsedUrl && req._parsedUrl.search ? req._parsedUrl.search : '');
+  return res.redirect(301, clean);
+});
 app.use(express.static(path.join(__dirname, 'public'), { extensions: ['html'] }));
 
 app.get('/api/products', (req, res) => {
@@ -185,12 +192,25 @@ app.get('/api/sliders', (req, res) => {
 });
 
 app.get('/api/facets', (req, res) => {
+  // Counts must reflect the FILTERED result set (q/aesthetic/vendor), not the
+  // unfiltered niche catalog — otherwise facet badges over-report after a
+  // search/filter is applied. Each facet's own dimension is excluded from its
+  // filter so the user can still see sibling option counts (standard facet UX).
+  const { q, aesthetic, vendor } = req.query;
+  const matchQ = (p) => !q || (p.title || '').toLowerCase().includes(q.toLowerCase()) || (p.tags || []).some(t => t.toLowerCase().includes(q.toLowerCase()));
+  const matchAesthetic = (p) => !aesthetic || aesthetic === 'all' || p.aesthetic === aesthetic;
+  const matchVendor = (p) => !vendor || vendor === 'all' || p.vendor === vendor;
+
   const aesthetics = {}; const vendors = {};
   for (const p of PRODUCTS_NICHE) {
-    aesthetics[p.aesthetic] = (aesthetics[p.aesthetic] || 0) + 1;
-    vendors[p.vendor] = (vendors[p.vendor] || 0) + 1;
+    // aesthetic counts: apply q + vendor, but NOT the aesthetic filter itself
+    if (matchQ(p) && matchVendor(p)) aesthetics[p.aesthetic] = (aesthetics[p.aesthetic] || 0) + 1;
+    // vendor counts: apply q + aesthetic, but NOT the vendor filter itself
+    if (matchQ(p) && matchAesthetic(p)) vendors[p.vendor] = (vendors[p.vendor] || 0) + 1;
   }
-  res.json({ aesthetics, vendors, total: PRODUCTS_NICHE.length });
+  // total reflects the fully-filtered set (all three filters applied)
+  const total = PRODUCTS_NICHE.filter(p => matchQ(p) && matchAesthetic(p) && matchVendor(p)).length;
+  res.json({ aesthetics, vendors, total });
 });
 
 app.get('/api/health', (req, res) => res.json({ status: 'ok', count: PRODUCTS_NICHE.length, dropped: DROPPED }));

← 378c8bf fix: remove leaked bak file from public/, tighten 404 guard  ·  back to Metallicwallpaper  ·  fleet rebuild: dw-header (hamburgers UL + centered name) + u c29e354 →