← 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
A mailers/hollywood-room-hero-gen.pngA mailers/hollywood-room-hero.jpgA mailers/upload-hero-to-shopify.jsA shopify/scripts/archive-thibaut-travelers-palm.jsM shopify/scripts/cadence/data/cadence-cursor.jsonM shopify/scripts/cadence/data/cadence-plan-am-2026-06-30.jsonA shopify/scripts/travelers-palm-archive-backup-2026-07-01T00-07-26-478Z.json
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 →