[object Object]

← back to Designer Wallcoverings

auto-save: 2026-06-30T17:12:11 (9 files) — pending-approval/boost-filter-consolidation-2026-06-25 shopify/scripts/cadence/data/cadence-cursor.json shopify/scripts/cadence/data/cadence-plan-am-2026-06-30.json vendor-scrapers/china-seas-refresh mailers/hollywood-room-hero-gen.png

38a5c6b24435b3f93f7c859f1261f7c093312ed3 · 2026-06-30 17:12:17 -0700 · Steve Abrams

Files touched

Diff

commit 38a5c6b24435b3f93f7c859f1261f7c093312ed3
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Jun 30 17:12:17 2026 -0700

    auto-save: 2026-06-30T17:12:11 (9 files) — pending-approval/boost-filter-consolidation-2026-06-25 shopify/scripts/cadence/data/cadence-cursor.json shopify/scripts/cadence/data/cadence-plan-am-2026-06-30.json vendor-scrapers/china-seas-refresh mailers/hollywood-room-hero-gen.png
---
 mailers/hollywood-room-hero-gen.png                | Bin 0 -> 883627 bytes
 mailers/hollywood-room-hero.jpg                    | Bin 0 -> 288368 bytes
 mailers/upload-hero-to-shopify.js                  |  38 +++++++++++
 shopify/scripts/archive-thibaut-travelers-palm.js  |  69 +++++++++++++++++++
 shopify/scripts/cadence/data/cadence-cursor.json   |   4 +-
 .../cadence/data/cadence-plan-am-2026-06-30.json   |  76 +--------------------
 ...lm-archive-backup-2026-07-01T00-07-26-478Z.json |  20 ++++++
 7 files changed, 132 insertions(+), 75 deletions(-)

diff --git a/mailers/hollywood-room-hero-gen.png b/mailers/hollywood-room-hero-gen.png
new file mode 100644
index 00000000..b46bdbe4
Binary files /dev/null and b/mailers/hollywood-room-hero-gen.png differ
diff --git a/mailers/hollywood-room-hero.jpg b/mailers/hollywood-room-hero.jpg
new file mode 100644
index 00000000..4774e73a
Binary files /dev/null and b/mailers/hollywood-room-hero.jpg differ
diff --git a/mailers/upload-hero-to-shopify.js b/mailers/upload-hero-to-shopify.js
new file mode 100644
index 00000000..40536dd0
--- /dev/null
+++ b/mailers/upload-hero-to-shopify.js
@@ -0,0 +1,38 @@
+// Upload the generated Hollywood room-setting hero to Shopify Files → prints the CDN URL.
+// Run:  node upload-hero-to-shopify.js
+// Reads SHOPIFY_ADMIN_TOKEN + SHOPIFY_STORE_DOMAIN from the secrets-manager .env.
+const fs = require('fs');
+const env = Object.fromEntries(
+  fs.readFileSync('/Users/stevestudio2/Projects/secrets-manager/.env', 'utf8')
+    .split('\n').filter(l => l.includes('=')).map(l => { const i = l.indexOf('='); return [l.slice(0, i).trim(), l.slice(i + 1).trim()]; })
+);
+const STORE = env.SHOPIFY_STORE_DOMAIN || 'designer-laboratory-sandbox.myshopify.com';
+const TOKEN = env.SHOPIFY_ADMIN_TOKEN;
+const API = `https://${STORE}/admin/api/2024-10/graphql.json`;
+const file = __dirname + '/hollywood-room-hero.jpg';
+const bytes = fs.statSync(file).size;
+const gql = (q, v) => fetch(API, { method: 'POST', headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json' }, body: JSON.stringify({ query: q, variables: v }) }).then(r => r.json());
+(async () => {
+  const s = await gql(`mutation($i:[StagedUploadInput!]!){stagedUploadsCreate(input:$i){stagedTargets{url resourceUrl parameters{name value}} userErrors{message}}}`,
+    { i: [{ resource: 'FILE', filename: 'hollywood-room-hero.jpg', mimeType: 'image/jpeg', httpMethod: 'POST', fileSize: String(bytes) }] });
+  const t = s.data?.stagedUploadsCreate?.stagedTargets?.[0];
+  if (!t) { console.log('staged err', JSON.stringify(s)); process.exit(1); }
+  const fd = new FormData();
+  for (const p of t.parameters) fd.append(p.name, p.value);
+  fd.append('file', new Blob([fs.readFileSync(file)], { type: 'image/jpeg' }), 'hollywood-room-hero.jpg');
+  const up = await fetch(t.url, { method: 'POST', body: fd });
+  if (up.status >= 400) { console.log('upload POST failed', up.status, (await up.text()).slice(0, 300)); process.exit(1); }
+  const c = await gql(`mutation($f:[FileCreateInput!]!){fileCreate(files:$f){files{... on MediaImage{id image{url} fileStatus}} userErrors{message}}}`,
+    { f: [{ originalSource: t.resourceUrl, contentType: 'IMAGE', alt: 'Hollywood Wallcoverings Artisma Croco room setting' }] });
+  const id = c.data?.fileCreate?.files?.[0]?.id;
+  if (!id) { console.log('fileCreate err', JSON.stringify(c)); process.exit(1); }
+  console.log('fileCreate ok id', id);
+  for (let i = 0; i < 14; i++) {
+    await new Promise(r => setTimeout(r, 2500));
+    const q = await gql(`query($id:ID!){node(id:$id){... on MediaImage{fileStatus image{url}}}}`, { id });
+    const n = q.data?.node;
+    if (n?.image?.url) { console.log('\n=== READY — paste this URL back to Claude ===\n' + n.image.url + '\n'); process.exit(0); }
+    process.stdout.write(`.${n?.fileStatus || '?'}`);
+  }
+  console.log('\nstill processing — re-run query later for id', id);
+})();
diff --git a/shopify/scripts/archive-thibaut-travelers-palm.js b/shopify/scripts/archive-thibaut-travelers-palm.js
new file mode 100644
index 00000000..d34c96f9
--- /dev/null
+++ b/shopify/scripts/archive-thibaut-travelers-palm.js
@@ -0,0 +1,69 @@
+#!/usr/bin/env node
+/**
+ * Archive Thibaut "Travelers Palm" (all colorways) on Shopify + tag 'settlement'.
+ *
+ * Steve 2026-06-30: "remove traveler's palm by thibaut and archive it with settlement tag. all."
+ * Travelers Palm is a tropical palm-frond botanical — settlement-implicated (must not be sold live).
+ * Targets Thibaut vendor + title/handle matching traveler(s)/traveller palm (3 colorways: Green T10127,
+ * Grey T10129, Spa Blue T10128). Sets status=ARCHIVED (off the storefront) and adds tags settlement +
+ * settlement-archived. Writes a reversible backup (prior status+tags) before committing.
+ *
+ *   node archive-thibaut-travelers-palm.js            # DRY-RUN: show what would change
+ *   node archive-thibaut-travelers-palm.js --commit   # LIVE: archive + tag on Shopify
+ */
+const https = require('https');
+const fs = require('fs');
+const os = require('os');
+const { execFileSync } = require('child_process');
+const COMMIT = process.argv.includes('--commit');
+const TOKEN = (fs.readFileSync(os.homedir() + '/Projects/secrets-manager/.env', 'utf8').match(/^SHOPIFY_ADMIN_TOKEN=(.*)$/m) || [])[1].replace(/['"]/g, '').trim();
+const STORE = 'designer-laboratory-sandbox.myshopify.com', API = '2024-10';
+const TAGS = ['settlement', 'settlement-archived'];
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+function psql(sql) { return execFileSync('psql', ['-At', '-F', '\t', '-d', 'dw_unified', '-c', sql], { encoding: 'utf8', maxBuffer: 1 << 28 }).trim(); }
+function gql(query, variables) {
+  return new Promise(res => {
+    const data = JSON.stringify({ query, variables });
+    const req = https.request({ host: STORE, path: `/admin/api/${API}/graphql.json`, method: 'POST',
+      headers: { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) } },
+      r => { let d = ''; r.on('data', c => d += c); r.on('end', () => { try { res({ status: r.statusCode, json: JSON.parse(d) }); } catch { res({ status: r.statusCode, raw: d.slice(0, 300) }); } }); });
+    req.on('error', e => res({ status: 0, err: e.message })); req.write(data); req.end();
+  });
+}
+async function gqlRetry(query, v) { for (let a = 0; a < 5; a++) { const r = await gql(query, v); const t = r.json && r.json.errors && JSON.stringify(r.json.errors).includes('THROTTLED'); if (r.status === 429 || t) { await sleep(2000 * (a + 1)); continue; } await sleep(400); return r; } return { status: 429 }; }
+(async () => {
+  if (!TOKEN) { console.error('no SHOPIFY_ADMIN_TOKEN'); process.exit(1); }
+  // The 3 Thibaut Travelers Palm colorways (creative match: apostrophe/spelling/spacing variants).
+  const rows = psql(`SELECT shopify_id, status, title, tags FROM shopify_products
+    WHERE vendor ILIKE '%thibaut%'
+      AND (title ILIKE '%traveler%palm%' OR title ILIKE '%traveller%palm%' OR handle ILIKE '%traveler%palm%')
+    ORDER BY title;`).split('\n').filter(Boolean).map(l => { const [sid, status, title, tags] = l.split('\t'); return { sid, status, title, tags }; });
+  console.log(`Thibaut Travelers Palm products found: ${rows.length}`);
+  rows.forEach(r => console.log(`  ${r.status.padEnd(8)} ${r.title}  (${r.sid})`));
+  if (!rows.length) { console.log('nothing to archive.'); return; }
+  if (!COMMIT) {
+    console.log(`\nDRY-RUN — would set status=ARCHIVED + add tags [${TAGS.join(', ')}] on the ${rows.length} above. Re-run with --commit.`);
+    return;
+  }
+  // Reversible backup (prior status + tags) BEFORE any write.
+  const stamp = new Date().toISOString().replace(/[:.]/g, '-');
+  const bak = `${__dirname}/travelers-palm-archive-backup-${stamp}.json`;
+  fs.writeFileSync(bak, JSON.stringify(rows, null, 2));
+  console.log(`backup written: ${bak}`);
+  const mUpd = `mutation($input:ProductInput!){productUpdate(input:$input){product{id status} userErrors{field message}}}`;
+  const mTag = `mutation($id:ID!,$tags:[String!]!){tagsAdd(id:$id,tags:$tags){userErrors{field message}}}`;
+  let ok = 0, fail = 0;
+  for (const r of rows) {
+    const gid = r.sid.startsWith('gid://') ? r.sid : `gid://shopify/Product/${r.sid}`;
+    const r1 = await gqlRetry(mUpd, { input: { id: gid, status: 'ARCHIVED' } });
+    const r2 = await gqlRetry(mTag, { id: gid, tags: TAGS });
+    const e1 = r1.json?.data?.productUpdate?.userErrors, e2 = r2.json?.data?.tagsAdd?.userErrors;
+    if ((e1 && e1.length) || (e2 && e2.length) || r1.status !== 200) { fail++; console.log(`  ✗ ${r.title} ${JSON.stringify(e1 || e2 || r1).slice(0, 140)}`); }
+    else {
+      ok++; console.log(`  ✓ ARCHIVED + tagged  ${r.title}`);
+      // mirror the truth locally so the catalog/grids reflect it immediately (Shopify is source-of-truth).
+      try { psql(`UPDATE shopify_products SET status='ARCHIVED', tags = CASE WHEN tags ILIKE '%settlement%' THEN tags ELSE tags || ', ${TAGS.join(', ')}' END WHERE shopify_id='${r.sid.replace(/'/g, "''")}';`); } catch (_) {}
+    }
+  }
+  console.log(`\nDONE: archived ${ok}, failed ${fail}. Reversible from ${bak}.`);
+})();
diff --git a/shopify/scripts/cadence/data/cadence-cursor.json b/shopify/scripts/cadence/data/cadence-cursor.json
index 46a820f9..79f2be2c 100644
--- a/shopify/scripts/cadence/data/cadence-cursor.json
+++ b/shopify/scripts/cadence/data/cadence-cursor.json
@@ -1,5 +1,5 @@
 {
- "idx": 6,
- "lastSlot": "pm",
+ "idx": 0,
+ "lastSlot": "am",
  "lastRun": "2026-06-30"
 }
\ No newline at end of file
diff --git a/shopify/scripts/cadence/data/cadence-plan-am-2026-06-30.json b/shopify/scripts/cadence/data/cadence-plan-am-2026-06-30.json
index 8272957f..1883bb47 100644
--- a/shopify/scripts/cadence/data/cadence-plan-am-2026-06-30.json
+++ b/shopify/scripts/cadence/data/cadence-plan-am-2026-06-30.json
@@ -1,7 +1,7 @@
 [
  {
   "vendor": "Phillip Jeffries",
-  "dw_sku": "DWJP-14511",
+  "dw_sku": "DWPJ-14511",
   "cost": 0,
   "retail": 0,
   "sample": "4.25",
@@ -11,7 +11,7 @@
  },
  {
   "vendor": "Phillip Jeffries",
-  "dw_sku": "DWJP-14512",
+  "dw_sku": "DWPJ-14512",
   "cost": 0,
   "retail": 0,
   "sample": "4.25",
@@ -21,82 +21,12 @@
  },
  {
   "vendor": "Phillip Jeffries",
-  "dw_sku": "DWJP-14513",
+  "dw_sku": "DWPJ-14513",
   "cost": 0,
   "retail": 0,
   "sample": "4.25",
   "sampleOnly": true,
   "title": "Extra Fine Arrowroot, Wheat Wallcoverings | Phillip Jeffries",
   "willActivate": false
- },
- {
-  "vendor": "Phillip Jeffries",
-  "dw_sku": "DWJP-14514",
-  "cost": 0,
-  "retail": 0,
-  "sample": "4.25",
-  "sampleOnly": true,
-  "title": "Extra Fine Arrowroot, Khaki Wallcoverings | Phillip Jeffries",
-  "willActivate": false
- },
- {
-  "vendor": "Phillip Jeffries",
-  "dw_sku": "DWJP-14515",
-  "cost": 0,
-  "retail": 0,
-  "sample": "4.25",
-  "sampleOnly": true,
-  "title": "Extra Fine Arrowroot, Tobacco Wallcoverings | Phillip Jeffries",
-  "willActivate": false
- },
- {
-  "vendor": "Phillip Jeffries",
-  "dw_sku": "DWJP-14516",
-  "cost": 0,
-  "retail": 0,
-  "sample": "4.25",
-  "sampleOnly": true,
-  "title": "Japanese Woven Jute, Natural Wallcoverings | Phillip Jeffries",
-  "willActivate": false
- },
- {
-  "vendor": "Phillip Jeffries",
-  "dw_sku": "DWJP-14517",
-  "cost": 0,
-  "retail": 0,
-  "sample": "4.25",
-  "sampleOnly": true,
-  "title": "Japanese Woven Jute, Cream Wallcoverings | Phillip Jeffries",
-  "willActivate": false
- },
- {
-  "vendor": "Phillip Jeffries",
-  "dw_sku": "DWJP-14518",
-  "cost": 0,
-  "retail": 0,
-  "sample": "4.25",
-  "sampleOnly": true,
-  "title": "Classic Linen, Douglas Ivory Wallcoverings | Phillip Jeffries",
-  "willActivate": false
- },
- {
-  "vendor": "Phillip Jeffries",
-  "dw_sku": "DWJP-14519",
-  "cost": 0,
-  "retail": 0,
-  "sample": "4.25",
-  "sampleOnly": true,
-  "title": "Gaucho Cloth, Twilight Blue Wallcoverings | Phillip Jeffries",
-  "willActivate": false
- },
- {
-  "vendor": "Phillip Jeffries",
-  "dw_sku": "DWJP-14520",
-  "cost": 0,
-  "retail": 0,
-  "sample": "4.25",
-  "sampleOnly": true,
-  "title": "Abaca Breeze, Cotton Wallcoverings | Phillip Jeffries",
-  "willActivate": false
  }
 ]
\ No newline at end of file
diff --git a/shopify/scripts/travelers-palm-archive-backup-2026-07-01T00-07-26-478Z.json b/shopify/scripts/travelers-palm-archive-backup-2026-07-01T00-07-26-478Z.json
new file mode 100644
index 00000000..bc8eec60
--- /dev/null
+++ b/shopify/scripts/travelers-palm-archive-backup-2026-07-01T00-07-26-478Z.json
@@ -0,0 +1,20 @@
+[
+  {
+    "sid": "gid://shopify/Product/4656361013299",
+    "status": "ACTIVE",
+    "title": "Travelers Palm Green - Green Wallcovering | Thibaut",
+    "tags": "Architectural, Bedroom, Botanical, color:Forest Green, Commercial, Dining Room, Emerald Green, Exotic, Green, Large Scale, Leaf, Living Room, Lush, Non-Woven, Olive Green, Paper, Pattern, Refreshing, Sage Green, T10127, Thibaut, Thibaut Wallcoverings, TRAVELERS PALM, Tropical, Wallcovering, White"
+  },
+  {
+    "sid": "gid://shopify/Product/4656361668659",
+    "status": "ACTIVE",
+    "title": "Travelers Palm Grey - Grey Wallcovering | Thibaut",
+    "tags": "Architectural, Bedroom, Beige, Blue-Gray, Botanical, Calm, color:Ash, Commercial, Contemporary, Entryway, Gray, Leaf, Living Room, Medium Scale, Multi, Non-Woven, Paper, Pattern, Serene, T10129, Taupe, Thibaut, Thibaut Wallcoverings, TRAVELERS PALM, Tropical, Wallcovering, White"
+  },
+  {
+    "sid": "gid://shopify/Product/4656362160179",
+    "status": "ACTIVE",
+    "title": "Travelers Palm Spa Blue - Spa Blue Wallcovering | Thibaut",
+    "tags": "Architectural, Bedroom, Blue, Botanical, Calm, Celadon, Coastal, color:Cerulean, Commercial, Entryway, Gray, Leaf, Living Room, Medium Scale, Multi, Non-Woven, Paper, Pattern, Serene, Spa Blue, T10128, Thibaut, Thibaut Wallcoverings, TRAVELERS PALM, Tropical, Wallcovering, White"
+  }
+]
\ No newline at end of file

← e1981b2e cadence(PJ): switch net-new prefix DWJP->DWPJ (Steve, regist  ·  back to Designer Wallcoverings  ·  auto-save: 2026-06-30T17:42:20 (5 files) — pending-approval/ 77e1c281 →