[object Object]

← back to Dw Photo Capture

All-Shopify search covers collections too: parallel product + collection-products query, merged/deduped, across every vendor; results carry their collections

3cbfbc0bd328bc1cd0f6cb39e694c0485996da10 · 2026-06-24 19:16:31 -0700 · steve@designerwallcoverings.com

Files touched

Diff

commit 3cbfbc0bd328bc1cd0f6cb39e694c0485996da10
Author: steve@designerwallcoverings.com <steve@designerwallcoverings.com>
Date:   Wed Jun 24 19:16:31 2026 -0700

    All-Shopify search covers collections too: parallel product + collection-products query, merged/deduped, across every vendor; results carry their collections
---
 data/build.json   |  5 +++--
 public/index.html |  2 +-
 server.js         | 34 +++++++++++++++++++++++-----------
 3 files changed, 27 insertions(+), 14 deletions(-)

diff --git a/data/build.json b/data/build.json
index e19e8a3..03da503 100644
--- a/data/build.json
+++ b/data/build.json
@@ -1,5 +1,5 @@
 {
-  "next": 18,
+  "next": 19,
   "map": {
     "578af86f": 2,
     "bc95fdb0": 3,
@@ -16,6 +16,7 @@
     "3a1be30f": 14,
     "d0f0df55": 15,
     "9905b54e": 16,
-    "48c9e659": 17
+    "48c9e659": 17,
+    "779f6d1f": 18
   }
 }
\ No newline at end of file
diff --git a/public/index.html b/public/index.html
index dfb8f18..b202f65 100644
--- a/public/index.html
+++ b/public/index.html
@@ -600,7 +600,7 @@ $('#sort').addEventListener('change',e=>{setLS('sort',e.target.value);render();}
 document.querySelectorAll('.chip').forEach(c=>c.addEventListener('click',()=>{
   document.querySelectorAll('.chip').forEach(z=>z.classList.remove('on'));c.classList.add('on');filter=c.dataset.f;setLS('filter',filter);
   if(filter==='any'){ $('#q').placeholder='🔎 SKU, model #, name, or collection…'; $('#q').focus(); doLookup(); }
-  else if(filter==='shop'){ $('#q').placeholder='🌐 Any Shopify SKU, name, or vendor…'; $('#q').focus(); doShopSearch(); }
+  else if(filter==='shop'){ $('#q').placeholder='🌐 Any Shopify SKU, name, vendor, or collection…'; $('#q').focus(); doShopSearch(); }
   else if(filter==='new'){ $('#q').placeholder='🔎 Filter the new SKUs…'; loadNew(); }
   else { $('#q').placeholder='🔎 Search name or model #…'; render(); }
 }));
diff --git a/server.js b/server.js
index 8c72562..2fe7589 100644
--- a/server.js
+++ b/server.js
@@ -444,21 +444,33 @@ function nodeToItem(n, extra) {
 // Live store-wide search: one GraphQL call, wildcard match on sku/title/vendor.
 // keep_images:true is stamped on every result so a photo update on an arbitrary
 // live product ADDS as featured without wiping existing imagery.
+const _PNODE = `legacyResourceId title status vendor
+  imgs:images(first:1){edges{node{src}}}
+  cols:collections(first:6){edges{node{title}}}
+  mf:metafield(namespace:"custom",key:"manufacturer_sku"){value}
+  mf2:metafield(namespace:"dwc",key:"manufacturer_sku"){value}
+  v:variants(first:5){edges{node{sku title price}}}`;
 async function shopifySearch(q) {
   const terms = q.split(/\s+/).filter(Boolean).slice(0, 4)
     .map(t => t.replace(/["\\():*]/g, '')).filter(Boolean);
   if (!terms.length) return [];
-  const qstr = terms.map(t => `(sku:*${t}* OR title:*${t}* OR vendor:*${t}*)`).join(' AND ');
-  const Q = `query($q:String){ products(first:60, query:$q){
-    edges{node{ legacyResourceId title status vendor
-      imgs:images(first:1){edges{node{src}}}
-      mf:metafield(namespace:"custom",key:"manufacturer_sku"){value}
-      mf2:metafield(namespace:"dwc",key:"manufacturer_sku"){value}
-      v:variants(first:5){edges{node{sku title price}}} } } } }`;
-  const r = await gql(Q, { q: qstr });
-  const edges = (r.data && r.data.products && r.data.products.edges) || [];
-  return edges.map(e => e.node).filter(n => n.status !== 'ARCHIVED')
-    .map(n => nodeToItem(n, { keep_images: true, scope: 'shopify' }));
+  // 1) product search (sku/title/vendor) + 2) products inside any matching COLLECTION — in parallel
+  const pstr = terms.map(t => `(sku:*${t}* OR title:*${t}* OR vendor:*${t}*)`).join(' AND ');
+  const cstr = terms.map(t => `title:*${t}*`).join(' AND ');
+  const PQ = `query($q:String){ products(first:50, query:$q){ edges{node{ ${_PNODE} } } } }`;
+  const CQ = `query($q:String){ collections(first:5, query:$q){ edges{node{ title products(first:40){edges{node{ ${_PNODE} }}} }}} }`;
+  const [pr, cr] = await Promise.all([gql(PQ, { q: pstr }), gql(CQ, { q: cstr })]);
+  const byId = new Map();
+  const add = (n, fromCol) => {
+    if (!n || n.status === 'ARCHIVED') return;
+    let it = byId.get(n.legacyResourceId);
+    if (!it) { it = nodeToItem(n, { keep_images: true, scope: 'shopify' }); it.collections = (n.cols ? n.cols.edges.map(e => e.node.title) : []); byId.set(n.legacyResourceId, it); }
+    if (fromCol && !it.collections.includes(fromCol)) it.collections.push(fromCol);
+  };
+  for (const e of ((pr.data && pr.data.products && pr.data.products.edges) || [])) add(e.node);
+  for (const ce of ((cr.data && cr.data.collections && cr.data.collections.edges) || []))
+    for (const pe of ce.node.products.edges) add(pe.node, ce.node.title);
+  return Array.from(byId.values()).slice(0, 80);
 }
 
 server.listen(PORT, '0.0.0.0', () => {

← 418e972 Add collections to search: each product carries its Shopify  ·  back to Dw Photo Capture  ·  Add ⭐ Favorites + 🕘 Recent views: star any card to pin SKUs 3c7d0a2 →