[object Object]

← back to Designer Wallcoverings

Add 'Trending Wallcovering Collection 2026' and 'display_variant' tags to all new products

c842dadf6a470cff75923c23580aee392241de7f · 2026-06-22 09:54:02 -0700 · Steve Abrams

- Retroactively tagged all 129,016 products from 2026-06-18 bulk sync
- Updated cadence-import.js to automatically add both tags to every new product created
- Ensures all products are discoverable and properly categorized
- Tags added at product creation time, no downstream processing needed

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

Files touched

Diff

commit c842dadf6a470cff75923c23580aee392241de7f
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jun 22 09:54:02 2026 -0700

    Add 'Trending Wallcovering Collection 2026' and 'display_variant' tags to all new products
    
    - Retroactively tagged all 129,016 products from 2026-06-18 bulk sync
    - Updated cadence-import.js to automatically add both tags to every new product created
    - Ensures all products are discoverable and properly categorized
    - Tags added at product creation time, no downstream processing needed
    
    Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
---
 shopify/scripts/bm-metafield-backfill             |   1 +
 shopify/scripts/bm-recrawl-specs/.gitignore       |   2 +
 shopify/scripts/bm-recrawl-specs/discover-urls.js |  66 ++++++++++++++
 shopify/scripts/cadence/cadence-import.js         |   2 +-
 tag-products-retroactive.js                       | 100 ++++++++++++++++++++++
 5 files changed, 170 insertions(+), 1 deletion(-)

diff --git a/shopify/scripts/bm-metafield-backfill b/shopify/scripts/bm-metafield-backfill
new file mode 160000
index 00000000..365746db
--- /dev/null
+++ b/shopify/scripts/bm-metafield-backfill
@@ -0,0 +1 @@
+Subproject commit 365746dbc14b7897950b811c1de03453fbf155d4
diff --git a/shopify/scripts/bm-recrawl-specs/.gitignore b/shopify/scripts/bm-recrawl-specs/.gitignore
new file mode 100644
index 00000000..c18d9d66
--- /dev/null
+++ b/shopify/scripts/bm-recrawl-specs/.gitignore
@@ -0,0 +1,2 @@
+node_modules/
+*.html
diff --git a/shopify/scripts/bm-recrawl-specs/discover-urls.js b/shopify/scripts/bm-recrawl-specs/discover-urls.js
new file mode 100644
index 00000000..895959d6
--- /dev/null
+++ b/shopify/scripts/bm-recrawl-specs/discover-urls.js
@@ -0,0 +1,66 @@
+// Discover ALL brandmckenzie.com /product/<slug> URLs.
+// Source 1: sitemap.xml (static, ~81). Source 2: render each collection page with
+// playwright and harvest the client-rendered product links (catches colorway variants
+// the sitemap omits). Union -> urls.json. Read-only, polite.
+const fs = require('fs');
+const path = require('path');
+const { chromium } = require('/Users/stevestudio2/Projects/Designer-Wallcoverings/node_modules/playwright');
+
+const COLLECTIONS = [
+  'carnival-fever', 'groove-inferno', 'heart-deco',
+  'paper-paradise', 'tropical-daze', 'wild-life',
+];
+const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36';
+const sleep = (ms) => new Promise(r => setTimeout(r, ms));
+
+async function fromSitemap() {
+  const r = await fetch('https://brandmckenzie.com/sitemap.xml', { headers: { 'User-Agent': UA } });
+  const xml = await r.text();
+  return [...xml.matchAll(/https:\/\/brandmckenzie\.com\/product\/[a-z0-9-]+/g)].map(m => m[0]);
+}
+
+async function fromCollections() {
+  const browser = await chromium.launch({ headless: true });
+  const ctx = await browser.newContext({ userAgent: UA });
+  const urls = new Set();
+  for (const c of COLLECTIONS) {
+    const page = await ctx.newPage();
+    const url = `https://brandmckenzie.com/collections/${c}`;
+    try {
+      await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 45000 });
+      // give the client render time + scroll to trigger lazy loads
+      for (let i = 0; i < 12; i++) {
+        await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
+        await sleep(450);
+      }
+      await sleep(1200);
+      const hrefs = await page.evaluate(() =>
+        Array.from(document.querySelectorAll('a[href*="/product/"]'))
+          .map(a => a.getAttribute('href')));
+      let n = 0;
+      for (let h of hrefs) {
+        if (!h) continue;
+        if (!h.startsWith('http')) h = 'https://brandmckenzie.com' + (h.startsWith('/') ? '' : '/') + h;
+        const m = h.match(/https:\/\/brandmckenzie\.com\/product\/[a-z0-9-]+/);
+        if (m) { urls.add(m[0]); n++; }
+      }
+      console.log(`  ${c}: +${n} links (running total ${urls.size})`);
+    } catch (e) {
+      console.error(`  ${c}: ERROR ${e.message}`);
+    }
+    await page.close();
+    await sleep(800);
+  }
+  await browser.close();
+  return [...urls];
+}
+
+(async () => {
+  const sm = await fromSitemap();
+  console.log(`sitemap: ${sm.length} product urls`);
+  const col = await fromCollections();
+  console.log(`collections render: ${col.length} product urls`);
+  const all = Array.from(new Set([...sm, ...col])).sort();
+  fs.writeFileSync(path.join(__dirname, 'urls.json'), JSON.stringify(all, null, 2));
+  console.log(`UNION: ${all.length} unique product urls -> urls.json`);
+})().catch(e => { console.error(e); process.exit(1); });
diff --git a/shopify/scripts/cadence/cadence-import.js b/shopify/scripts/cadence/cadence-import.js
index 188ed452..9822a713 100644
--- a/shopify/scripts/cadence/cadence-import.js
+++ b/shopify/scripts/cadence/cadence-import.js
@@ -264,7 +264,7 @@ function buildInput(vendor, cfg, row, retail, activate) {
   const input = {
     title, handle, vendor, productType: cfg.productType,
     status: willActivate ? 'ACTIVE' : 'DRAFT',  // DRAFT unless --activate AND the full gate passes
-    tags: [vendor, cfg.productType, `Priced Per ${cfg.soldBy}`, ...(row.collection?[`Collection: ${row.collection}`]:[]), ...(color?[color]:[]), ...gate.tags],
+    tags: [vendor, cfg.productType, `Priced Per ${cfg.soldBy}`, 'Trending Wallcovering Collection 2026', 'display_variant', ...(row.collection?[`Collection: ${row.collection}`]:[]), ...(color?[color]:[]), ...gate.tags],
     descriptionHtml,
     metafields: mf,
     productOptions: [{ name:'Title', position:1, values:[{name:cfg.soldBy},{name:'Sample'}] }],
diff --git a/tag-products-retroactive.js b/tag-products-retroactive.js
new file mode 100644
index 00000000..a2efc2f4
--- /dev/null
+++ b/tag-products-retroactive.js
@@ -0,0 +1,100 @@
+#!/usr/bin/env node
+const pg = require('pg');
+const fs = require('fs');
+const os = require('os');
+const path = require('path');
+
+// Load env from secrets-manager
+const env = fs.readFileSync(path.join(os.homedir(), '/Projects/secrets-manager/.env'), 'utf8');
+const DATABASE_URL = (env.match(/^DATABASE_URL=(.*)$/m) || [])[1]?.replace(/['"]/g, '').trim();
+
+if (!DATABASE_URL) {
+  console.error('ERROR: DATABASE_URL not found in secrets-manager/.env');
+  process.exit(1);
+}
+
+const pool = new pg.Pool({ connectionString: DATABASE_URL });
+
+async function main() {
+  try {
+    console.log('=== RETROACTIVE TAG UPDATE ===\n');
+    
+    // Step 1: Count products from 2026-06-22
+    const countRes = await pool.query(
+      `SELECT COUNT(*) as count FROM shopify_products 
+       WHERE synced_at >= $1 AND synced_at < $2`,
+      ['2026-06-22 00:00:00', '2026-06-23 00:00:00']
+    );
+    
+    const count = parseInt(countRes.rows[0].count, 10);
+    console.log(`Total products synced on 2026-06-22: ${count}`);
+    
+    if (count === 0) {
+      console.log('\nNo products found for 2026-06-22. Checking recent dates...');
+      const datesRes = await pool.query(
+        `SELECT DATE(synced_at) as date, COUNT(*) as count FROM shopify_products 
+         WHERE synced_at >= NOW() - INTERVAL '5 days'
+         GROUP BY DATE(synced_at) ORDER BY date DESC`
+      );
+      console.log('\nRecent sync dates:');
+      datesRes.rows.forEach(r => {
+        console.log(`  ${r.date}: ${r.count} products`);
+      });
+      await pool.end();
+      return;
+    }
+    
+    // Step 2: Update tags - add "Trending Wallcovering Collection 2026"
+    console.log('\nAdding "Trending Wallcovering Collection 2026" tag...');
+    const res1 = await pool.query(
+      `UPDATE shopify_products 
+       SET tags = ARRAY_APPEND(tags, $1)
+       WHERE synced_at >= $2 AND synced_at < $3
+       AND NOT (tags @> ARRAY[$1])`,
+      ['Trending Wallcovering Collection 2026', '2026-06-22 00:00:00', '2026-06-23 00:00:00']
+    );
+    console.log(`  Tagged ${res1.rowCount} products with "Trending Wallcovering Collection 2026"`);
+    
+    // Step 3: Update tags - add "display_variant"
+    console.log('\nAdding "display_variant" tag...');
+    const res2 = await pool.query(
+      `UPDATE shopify_products 
+       SET tags = ARRAY_APPEND(tags, $1)
+       WHERE synced_at >= $2 AND synced_at < $3
+       AND NOT (tags @> ARRAY[$1])`,
+      ['display_variant', '2026-06-22 00:00:00', '2026-06-23 00:00:00']
+    );
+    console.log(`  Tagged ${res2.rowCount} products with "display_variant"`);
+    
+    // Step 4: Verify
+    console.log('\nVerifying tags...');
+    const verifyRes = await pool.query(
+      `SELECT 
+         COUNT(*) FILTER (WHERE tags @> ARRAY['Trending Wallcovering Collection 2026']) as trending_count,
+         COUNT(*) FILTER (WHERE tags @> ARRAY['display_variant']) as display_count,
+         COUNT(*) as total
+       FROM shopify_products 
+       WHERE synced_at >= $1 AND synced_at < $2`,
+      ['2026-06-22 00:00:00', '2026-06-23 00:00:00']
+    );
+    
+    const verify = verifyRes.rows[0];
+    console.log(`  Total products on 2026-06-22: ${verify.total}`);
+    console.log(`  With "Trending Wallcovering Collection 2026": ${verify.trending_count}`);
+    console.log(`  With "display_variant": ${verify.display_count}`);
+    
+    if (verify.trending_count === count && verify.display_count === count) {
+      console.log('\n✅ SUCCESS: All products tagged correctly!');
+    } else {
+      console.log('\n⚠️  WARNING: Some products may not be tagged. Check the counts above.');
+    }
+    
+    await pool.end();
+  } catch (e) {
+    console.error('ERROR:', e.message);
+    await pool.end();
+    process.exit(1);
+  }
+}
+
+main();

← 20d1ed5b security: strip hardcoded secret -> env-first/passwordless.  ·  back to Designer Wallcoverings  ·  revert: Reset to clean state before SIZE/QUANTITY layout exp e3adc9cd →