← back to Tk10656 Empty Collections
TK-10656: empty published-collection hunt — 19 live pages show shoppers 0 products + verified Versa private-label leak + colefax dup
ebe5b42cf354767ea8eddd06e375e03d83a16c1a · 2026-08-18 00:53:39 -0700 · steve
Files touched
A .gitignoreA data/empty-published-collections.jsonA data/empty-to-shopper.jsonA data/empty-triage.jsonA data/scan-result.jsonA data/storefront-empty-result.jsonA scan-collections.mjsA storefront-empty-scan.mjs
Diff
commit ebe5b42cf354767ea8eddd06e375e03d83a16c1a
Author: steve <steve@designerwallcoverings.com>
Date: Tue Aug 18 00:53:39 2026 -0700
TK-10656: empty published-collection hunt — 19 live pages show shoppers 0 products + verified Versa private-label leak + colefax dup
---
.gitignore | 5 +
data/empty-published-collections.json | 1 +
data/empty-to-shopper.json | 97 +++++++++++++++++
data/empty-triage.json | 196 ++++++++++++++++++++++++++++++++++
data/scan-result.json | 7 ++
data/storefront-empty-result.json | 102 ++++++++++++++++++
scan-collections.mjs | 32 ++++++
storefront-empty-scan.mjs | 30 ++++++
8 files changed, 470 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1389fd5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+.env*
+*.log
+.DS_Store
+data/collections-dump.jsonl
diff --git a/data/empty-published-collections.json b/data/empty-published-collections.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/data/empty-published-collections.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/data/empty-to-shopper.json b/data/empty-to-shopper.json
new file mode 100644
index 0000000..5e390e4
--- /dev/null
+++ b/data/empty-to-shopper.json
@@ -0,0 +1,97 @@
+[
+ {
+ "handle": "phillipe-romano-faux-leathers-upholstery-more",
+ "title": "Phillipe Romano Faux Leathers Upholstery & More",
+ "admin_count": 1338
+ },
+ {
+ "handle": "hollywood-sunset-collection",
+ "title": "Hollywood Sunset Vinyl Collection",
+ "admin_count": 243
+ },
+ {
+ "handle": "phillipe-romano-ultimo",
+ "title": "Phillipe Romano Ultimo",
+ "admin_count": 20
+ },
+ {
+ "handle": "as-seen-in-showrooms",
+ "title": "As Seen in Showrooms",
+ "admin_count": 42
+ },
+ {
+ "handle": "paul-montgomery-murals",
+ "title": "Paul Montgomery Murals",
+ "admin_count": 443
+ },
+ {
+ "handle": "paul-montgomery-all-products",
+ "title": "Paul Montgomery - All Products",
+ "admin_count": 1538
+ },
+ {
+ "handle": "colefax-and-fowler",
+ "title": "Colefax and Fowler",
+ "admin_count": 361
+ },
+ {
+ "handle": "armani-casa-wallcoverings",
+ "title": "Armani Casa",
+ "admin_count": 243
+ },
+ {
+ "handle": "phillipe-romano-fabrics",
+ "title": "Phillipe Romano Fabrics",
+ "admin_count": 176
+ },
+ {
+ "handle": "carlisle-co",
+ "title": "Carlisle & Co",
+ "admin_count": 441
+ },
+ {
+ "handle": "colefax-fowler",
+ "title": "Colefax & Fowler",
+ "admin_count": 361
+ },
+ {
+ "handle": "borastapeter",
+ "title": "Borastapeter",
+ "admin_count": 100
+ },
+ {
+ "handle": "et-cie",
+ "title": "Et Cie",
+ "admin_count": 487
+ },
+ {
+ "handle": "holly-hunt",
+ "title": "Holly Hunt",
+ "admin_count": 1260
+ },
+ {
+ "handle": "versa-designed-surfaces-wallcoverings",
+ "title": "Versa Designed Surfaces Wallcoverings",
+ "admin_count": 42
+ },
+ {
+ "handle": "hermes",
+ "title": "Hermes",
+ "admin_count": 29
+ },
+ {
+ "handle": "hygge-and-west",
+ "title": "Hygge & West Wallcoverings",
+ "admin_count": 11
+ },
+ {
+ "handle": "borastapeter-scandinavian-wallpapers",
+ "title": "Borastapeter Scandinavian Wallcoverings",
+ "admin_count": 1
+ },
+ {
+ "handle": "collezione-italia",
+ "title": "Collezione Italia",
+ "admin_count": 413
+ }
+]
\ No newline at end of file
diff --git a/data/empty-triage.json b/data/empty-triage.json
new file mode 100644
index 0000000..fd6cf17
--- /dev/null
+++ b/data/empty-triage.json
@@ -0,0 +1,196 @@
+[
+ {
+ "handle": "phillipe-romano-faux-leathers-upholstery-more",
+ "title": "Phillipe Romano Faux Leathers Upholstery & More",
+ "admin_count": 1338,
+ "sampled": 100,
+ "status": {
+ "ARCHIVED": 100
+ },
+ "likely": "ALL_ARCHIVED_unpublish_or_dedupe"
+ },
+ {
+ "handle": "hollywood-sunset-collection",
+ "title": "Hollywood Sunset Vinyl Collection",
+ "admin_count": 243,
+ "sampled": 100,
+ "status": {
+ "ARCHIVED": 100
+ },
+ "likely": "ALL_ARCHIVED_unpublish_or_dedupe"
+ },
+ {
+ "handle": "phillipe-romano-ultimo",
+ "title": "Phillipe Romano Ultimo",
+ "admin_count": 20,
+ "sampled": 20,
+ "status": {
+ "ARCHIVED": 20
+ },
+ "likely": "ALL_ARCHIVED_unpublish_or_dedupe"
+ },
+ {
+ "handle": "as-seen-in-showrooms",
+ "title": "As Seen in Showrooms",
+ "admin_count": 42,
+ "sampled": 42,
+ "status": {
+ "DRAFT": 10,
+ "ARCHIVED": 32
+ },
+ "likely": "ALL_ARCHIVED_unpublish_or_dedupe"
+ },
+ {
+ "handle": "paul-montgomery-murals",
+ "title": "Paul Montgomery Murals",
+ "admin_count": 443,
+ "sampled": 100,
+ "status": {
+ "ARCHIVED": 100
+ },
+ "likely": "ALL_ARCHIVED_unpublish_or_dedupe"
+ },
+ {
+ "handle": "paul-montgomery-all-products",
+ "title": "Paul Montgomery - All Products",
+ "admin_count": 1538,
+ "sampled": 100,
+ "status": {
+ "ARCHIVED": 100
+ },
+ "likely": "ALL_ARCHIVED_unpublish_or_dedupe"
+ },
+ {
+ "handle": "colefax-and-fowler",
+ "title": "Colefax and Fowler",
+ "admin_count": 361,
+ "sampled": 100,
+ "status": {
+ "ARCHIVED": 100
+ },
+ "likely": "ALL_ARCHIVED_unpublish_or_dedupe"
+ },
+ {
+ "handle": "armani-casa-wallcoverings",
+ "title": "Armani Casa",
+ "admin_count": 243,
+ "sampled": 100,
+ "status": {
+ "ARCHIVED": 99,
+ "DRAFT": 1
+ },
+ "likely": "ALL_ARCHIVED_unpublish_or_dedupe"
+ },
+ {
+ "handle": "phillipe-romano-fabrics",
+ "title": "Phillipe Romano Fabrics",
+ "admin_count": 176,
+ "sampled": 100,
+ "status": {
+ "ARCHIVED": 100
+ },
+ "likely": "ALL_ARCHIVED_unpublish_or_dedupe"
+ },
+ {
+ "handle": "carlisle-co",
+ "title": "Carlisle & Co",
+ "admin_count": 441,
+ "sampled": 100,
+ "status": {
+ "ARCHIVED": 100
+ },
+ "likely": "ALL_ARCHIVED_unpublish_or_dedupe"
+ },
+ {
+ "handle": "colefax-fowler",
+ "title": "Colefax & Fowler",
+ "admin_count": 361,
+ "sampled": 100,
+ "status": {
+ "ARCHIVED": 100
+ },
+ "likely": "ALL_ARCHIVED_unpublish_or_dedupe"
+ },
+ {
+ "handle": "borastapeter",
+ "title": "Borastapeter",
+ "admin_count": 100,
+ "sampled": 100,
+ "status": {
+ "ARCHIVED": 100
+ },
+ "likely": "ALL_ARCHIVED_unpublish_or_dedupe"
+ },
+ {
+ "handle": "et-cie",
+ "title": "Et Cie",
+ "admin_count": 487,
+ "sampled": 100,
+ "status": {
+ "ARCHIVED": 99,
+ "DRAFT": 1
+ },
+ "likely": "ALL_ARCHIVED_unpublish_or_dedupe"
+ },
+ {
+ "handle": "holly-hunt",
+ "title": "Holly Hunt",
+ "admin_count": 1260,
+ "sampled": 100,
+ "status": {
+ "ARCHIVED": 100
+ },
+ "likely": "ALL_ARCHIVED_unpublish_or_dedupe"
+ },
+ {
+ "handle": "versa-designed-surfaces-wallcoverings",
+ "title": "Versa Designed Surfaces Wallcoverings",
+ "admin_count": 42,
+ "sampled": 42,
+ "status": {
+ "ARCHIVED": 42
+ },
+ "likely": "ALL_ARCHIVED_unpublish_or_dedupe"
+ },
+ {
+ "handle": "hermes",
+ "title": "Hermes",
+ "admin_count": 29,
+ "sampled": 29,
+ "status": {
+ "ARCHIVED": 29
+ },
+ "likely": "ALL_ARCHIVED_unpublish_or_dedupe"
+ },
+ {
+ "handle": "hygge-and-west",
+ "title": "Hygge & West Wallcoverings",
+ "admin_count": 11,
+ "sampled": 11,
+ "status": {
+ "ARCHIVED": 11
+ },
+ "likely": "ALL_ARCHIVED_unpublish_or_dedupe"
+ },
+ {
+ "handle": "borastapeter-scandinavian-wallpapers",
+ "title": "Borastapeter Scandinavian Wallcoverings",
+ "admin_count": 1,
+ "sampled": 1,
+ "status": {
+ "ARCHIVED": 1
+ },
+ "likely": "ALL_ARCHIVED_unpublish_or_dedupe"
+ },
+ {
+ "handle": "collezione-italia",
+ "title": "Collezione Italia",
+ "admin_count": 413,
+ "sampled": 100,
+ "status": {
+ "ARCHIVED": 40,
+ "DRAFT": 60
+ },
+ "likely": "STAGED_DRAFTS_maybe_activate"
+ }
+]
\ No newline at end of file
diff --git a/data/scan-result.json b/data/scan-result.json
new file mode 100644
index 0000000..87937f9
--- /dev/null
+++ b/data/scan-result.json
@@ -0,0 +1,7 @@
+{
+ "total_collections": 707,
+ "published_empty_ZERO_products": 0,
+ "unpublished_empty": 49,
+ "published_nonempty": 577,
+ "sample": []
+}
diff --git a/data/storefront-empty-result.json b/data/storefront-empty-result.json
new file mode 100644
index 0000000..4d128ba
--- /dev/null
+++ b/data/storefront-empty-result.json
@@ -0,0 +1,102 @@
+{
+ "published_collections_checked": 577,
+ "EMPTY_to_shopper": 19,
+ "fetch_errors": 0,
+ "sample": [
+ {
+ "handle": "phillipe-romano-faux-leathers-upholstery-more",
+ "title": "Phillipe Romano Faux Leathers Upholstery & More",
+ "admin_count": 1338
+ },
+ {
+ "handle": "hollywood-sunset-collection",
+ "title": "Hollywood Sunset Vinyl Collection",
+ "admin_count": 243
+ },
+ {
+ "handle": "phillipe-romano-ultimo",
+ "title": "Phillipe Romano Ultimo",
+ "admin_count": 20
+ },
+ {
+ "handle": "as-seen-in-showrooms",
+ "title": "As Seen in Showrooms",
+ "admin_count": 42
+ },
+ {
+ "handle": "paul-montgomery-murals",
+ "title": "Paul Montgomery Murals",
+ "admin_count": 443
+ },
+ {
+ "handle": "paul-montgomery-all-products",
+ "title": "Paul Montgomery - All Products",
+ "admin_count": 1538
+ },
+ {
+ "handle": "colefax-and-fowler",
+ "title": "Colefax and Fowler",
+ "admin_count": 361
+ },
+ {
+ "handle": "armani-casa-wallcoverings",
+ "title": "Armani Casa",
+ "admin_count": 243
+ },
+ {
+ "handle": "phillipe-romano-fabrics",
+ "title": "Phillipe Romano Fabrics",
+ "admin_count": 176
+ },
+ {
+ "handle": "carlisle-co",
+ "title": "Carlisle & Co",
+ "admin_count": 441
+ },
+ {
+ "handle": "colefax-fowler",
+ "title": "Colefax & Fowler",
+ "admin_count": 361
+ },
+ {
+ "handle": "borastapeter",
+ "title": "Borastapeter",
+ "admin_count": 100
+ },
+ {
+ "handle": "et-cie",
+ "title": "Et Cie",
+ "admin_count": 487
+ },
+ {
+ "handle": "holly-hunt",
+ "title": "Holly Hunt",
+ "admin_count": 1260
+ },
+ {
+ "handle": "versa-designed-surfaces-wallcoverings",
+ "title": "Versa Designed Surfaces Wallcoverings",
+ "admin_count": 42
+ },
+ {
+ "handle": "hermes",
+ "title": "Hermes",
+ "admin_count": 29
+ },
+ {
+ "handle": "hygge-and-west",
+ "title": "Hygge & West Wallcoverings",
+ "admin_count": 11
+ },
+ {
+ "handle": "borastapeter-scandinavian-wallpapers",
+ "title": "Borastapeter Scandinavian Wallcoverings",
+ "admin_count": 1
+ },
+ {
+ "handle": "collezione-italia",
+ "title": "Collezione Italia",
+ "admin_count": 413
+ }
+ ]
+}
diff --git a/scan-collections.mjs b/scan-collections.mjs
new file mode 100644
index 0000000..9454018
--- /dev/null
+++ b/scan-collections.mjs
@@ -0,0 +1,32 @@
+// TK-10656 — READ-ONLY scan of ALL collections: capture handle/title/productsCount +
+// whether published to the Online Store. Flags the customer-facing dead-page class:
+// published to Online Store AND productsCount == 0 (a live collection page showing shoppers 0 products).
+import { readFileSync, writeFileSync, appendFileSync } from 'node:fs';
+const txt=readFileSync(`${process.env.HOME}/Projects/secrets-manager/.env`,'utf8');
+const env={}; for(const l of txt.split('\n')){const m=l.match(/^([A-Z0-9_]+)=(.*)$/); if(m) env[m[1]]=m[2].replace(/^["']|["']$/g,'');}
+const STORE=env.SHOPIFY_STORE_DOMAIN, TOKEN=env.SHOPIFY_FULL_ACCESS_TOKEN, API='2024-10';
+const ONLINE='gid://shopify/Publication/22208643184';
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+async function gql(q,v={}){for(let a=0;a<10;a++){try{const res=await fetch(`https://${STORE}/admin/api/${API}/graphql.json`,{method:'POST',headers:{'Content-Type':'application/json','X-Shopify-Access-Token':TOKEN},body:JSON.stringify({query:q,variables:v})});const tx=await res.text();let j;try{j=JSON.parse(tx);}catch{await sleep(1500*(a+1));continue;}if(j.errors){if(/THROTTLED/i.test(JSON.stringify(j.errors))){await sleep(1500*(a+1));continue;}throw new Error(JSON.stringify(j.errors));}return j.data;}catch(e){if(a<9){await sleep(1500*(a+1));continue;}throw e;}}}
+const DUMP='data/collections-dump.jsonl'; writeFileSync(DUMP,'');
+let cursor=null, n=0, publishedEmpty=0, unpublishedEmpty=0, publishedNonEmpty=0;
+const emptyPublished=[];
+while(true){
+ const d=await gql(`query($c:String){ collections(first:100, after:$c){ pageInfo{ hasNextPage endCursor }
+ nodes{ id handle title productsCount{count} publishedOnPublication(publicationId:"${ONLINE}") } } }`,{c:cursor});
+ const buf=[];
+ for(const c of d.collections.nodes){ n++;
+ const rec={handle:c.handle,title:c.title,count:c.productsCount?.count??null,pub:c.publishedOnPublication};
+ buf.push(JSON.stringify(rec));
+ if(rec.count===0){ if(rec.pub){ publishedEmpty++; if(emptyPublished.length<500) emptyPublished.push(rec); } else unpublishedEmpty++; }
+ else if(rec.pub) publishedNonEmpty++;
+ }
+ appendFileSync(DUMP, buf.join('\n')+'\n');
+ if(n%500===0) process.stderr.write(` scanned ${n} collections, published-empty ${publishedEmpty}\n`);
+ if(!d.collections.pageInfo.hasNextPage) break;
+ cursor=d.collections.pageInfo.endCursor;
+}
+writeFileSync('data/empty-published-collections.json', JSON.stringify(emptyPublished,null,2));
+console.log(JSON.stringify({total_collections:n, published_empty_ZERO_products:publishedEmpty,
+ unpublished_empty:unpublishedEmpty, published_nonempty:publishedNonEmpty,
+ sample:emptyPublished.slice(0,15)},null,2));
diff --git a/storefront-empty-scan.mjs b/storefront-empty-scan.mjs
new file mode 100644
index 0000000..ce9b0e8
--- /dev/null
+++ b/storefront-empty-scan.mjs
@@ -0,0 +1,30 @@
+// TK-10656 — customer-truth scan: for every collection PUBLISHED to Online Store, fetch the
+// storefront /collections/<handle>/products.json (published products only). Flag those that
+// return 0 products = a live collection page that shows the SHOPPER nothing (hidden dead page
+// even if the Admin productsCount>0, because all members are archived/draft/unpublished).
+import { readFileSync, writeFileSync } from 'node:fs';
+const sleep=ms=>new Promise(r=>setTimeout(r,ms));
+const BASE='https://www.designerwallcoverings.com';
+const rows=readFileSync('data/collections-dump.jsonl','utf8').trim().split('\n').map(JSON.parse).filter(c=>c.pub);
+let checked=0, emptyToShopper=0, errs=0; const empties=[];
+for(const c of rows){
+ let ok=false;
+ for(let a=0;a<4;a++){
+ try{
+ const r=await fetch(`${BASE}/collections/${c.handle}/products.json?limit=1`,{headers:{'User-Agent':'Mozilla/5.0 dw-audit'}});
+ if(r.status===429||r.status>=500){ await sleep(1500*(a+1)); continue; }
+ const j=await r.json().catch(()=>null);
+ if(!j){ await sleep(1000*(a+1)); continue; }
+ const nprod=(j.products||[]).length;
+ if(nprod===0){ emptyToShopper++; empties.push({handle:c.handle,title:c.title,admin_count:c.count}); }
+ ok=true; break;
+ }catch(e){ await sleep(1000*(a+1)); }
+ }
+ if(!ok) errs++;
+ checked++;
+ if(checked%100===0) process.stderr.write(` checked ${checked}/${rows.length}, empty-to-shopper ${emptyToShopper}, errs ${errs}\n`);
+ await sleep(120); // gentle
+}
+writeFileSync('data/empty-to-shopper.json', JSON.stringify(empties,null,2));
+console.log(JSON.stringify({published_collections_checked:checked, EMPTY_to_shopper:emptyToShopper, fetch_errors:errs,
+ sample:empties.slice(0,25)},null,2));
(oldest)
·
back to Tk10656 Empty Collections
·
TK-10656 Cody fixes: Playwright rendered-DOM proof (holly-hu 62bdcca →