[object Object]

← back to Designer Wallcoverings

Add REGEN mode to room generator: replace our 557 rooms at corrected scale

144635ea45518444dddc5482a9b426f25ef445c3 · 2026-06-23 13:20:52 -0700 · Steve

REGEN=1 re-does ONLY the rooms we generated (gated on _done.ledger + a
'Room Setting' alt/url match) so the ~35 original-cohort _interior1 vendor
photos are never touched. Per product: delete the old wrong-scale room image,
regenerate at the repeat-driven scale (reads custom.pattern_repeat), upload the
replacement. Separate _regen.ledger for resume. Dry-run verified.

Files touched

Diff

commit 144635ea45518444dddc5482a9b426f25ef445c3
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Jun 23 13:20:52 2026 -0700

    Add REGEN mode to room generator: replace our 557 rooms at corrected scale
    
    REGEN=1 re-does ONLY the rooms we generated (gated on _done.ledger + a
    'Room Setting' alt/url match) so the ~35 original-cohort _interior1 vendor
    photos are never touched. Per product: delete the old wrong-scale room image,
    regenerate at the repeat-driven scale (reads custom.pattern_repeat), upload the
    replacement. Separate _regen.ledger for resume. Dry-run verified.
---
 shopify/scripts/tres-tintas-room-settings.js | 40 +++++++++++++++++++++-------
 1 file changed, 31 insertions(+), 9 deletions(-)

diff --git a/shopify/scripts/tres-tintas-room-settings.js b/shopify/scripts/tres-tintas-room-settings.js
index b610af32..3402aaaa 100644
--- a/shopify/scripts/tres-tintas-room-settings.js
+++ b/shopify/scripts/tres-tintas-room-settings.js
@@ -30,6 +30,9 @@ const { execFileSync } = require('child_process');
 const STORE = 'designer-laboratory-sandbox.myshopify.com';
 const API = '2024-01';
 const APPLY = process.env.APPLY === '1';
+// REGEN=1: re-generate rooms for products that ALREADY have a room image, at the
+// corrected repeat-driven scale — deletes the old room image, uploads the new.
+const REGEN = process.env.REGEN === '1';
 const LIMIT = process.env.LIMIT ? parseInt(process.env.LIMIT, 10) : Infinity;
 const ROOM = process.env.ROOM || 'living_room';
 const REPLICATE_VERSION = 'a5b13068cc81a89a4fbeefeccc774869fcb34df4dbc92c1555e0f2771d49dde7';
@@ -49,9 +52,12 @@ const H = { 'X-Shopify-Access-Token': TOKEN, 'Content-Type': 'application/json'
 const TMP = '/tmp/tt-rooms';
 fs.mkdirSync(TMP, { recursive: true });
 const BACKUP_DIR = path.join(__dirname, '..', 'tres-tintas-room-backups');
-const LEDGER = path.join(BACKUP_DIR, '_done.ledger');
+const LEDGER = path.join(BACKUP_DIR, REGEN ? '_regen.ledger' : '_done.ledger');
 fs.mkdirSync(BACKUP_DIR, { recursive: true });
 const done = new Set(fs.existsSync(LEDGER) ? fs.readFileSync(LEDGER, 'utf8').split('\n').filter(Boolean) : []);
+// In REGEN we ONLY re-do rooms WE generated (the original _done.ledger) — never
+// the ~35 original-cohort products that carry real vendor _interior1 photos.
+const GENERATED = REGEN ? new Set((fs.existsSync(path.join(BACKUP_DIR, '_done.ledger')) ? fs.readFileSync(path.join(BACKUP_DIR, '_done.ledger'), 'utf8').split('\n').filter(Boolean) : [])) : null;
 
 const sleep = (ms) => new Promise(r => setTimeout(r, ms));
 // Per-request timeout so a half-open socket can never wedge the batch (the
@@ -96,6 +102,10 @@ async function replicateInpaint(initPath, maskPath, outPath) {
   throw new Error('replicate poll timeout');
 }
 
+async function deleteImage(productId, imageId) {
+  const r = await fetchRetry(`https://${STORE}/admin/api/${API}/products/${productId}/images/${imageId}.json`, { method: 'DELETE', headers: H }, 3, 60000);
+  if (!r.ok && r.status !== 404) throw new Error(`delete image ${imageId}: HTTP ${r.status}`);
+}
 async function uploadImage(productId, filePath, alt) {
   const b64 = fs.readFileSync(filePath).toString('base64');
   const r = await fetchRetry(`https://${STORE}/admin/api/${API}/products/${productId}/images.json`, {
@@ -107,11 +117,11 @@ async function uploadImage(productId, filePath, alt) {
 
 async function main() {
   if (!TOKEN || !RTOKEN) throw new Error('missing SHOPIFY_DRAFT_TOKEN or REPLICATE_API_TOKEN');
-  console.log(`MODE: ${APPLY ? 'APPLY (live)' : 'DRY RUN'}  room=${ROOM}${LIMIT !== Infinity ? `  LIMIT=${LIMIT}` : ''}  est $${COST_PER_IMG}/img`);
+  console.log(`MODE: ${APPLY ? 'APPLY (live)' : 'DRY RUN'}${REGEN ? '  REGEN (replace existing rooms)' : ''}  room=${ROOM}${LIMIT !== Infinity ? `  LIMIT=${LIMIT}` : ''}  est $${COST_PER_IMG}/img`);
   // first:100 (matches the desc/spec scripts that enumerate all 592 cleanly).
   // first:60 hit a Shopify frozen-endCursor pagination bug -> only saw 120.
   const q = `query($c:String){products(first:100,query:"vendor:'Tres Tintas'",after:$c){pageInfo{hasNextPage endCursor}
-    edges{node{id title images(first:10){edges{node{url}}}} } }}`;
+    edges{node{id title images(first:10){edges{node{id url altText}}}} } }}`;
 
   let cursor = null, scanned = 0, need = 0, processed = 0, ok = 0, errs = 0, spend = 0;
   while (true) {
@@ -119,14 +129,25 @@ async function main() {
     for (const e of conn.edges) {
       const n = e.node; scanned++;
       const id = n.id.split('/').pop();
-      const imgs = n.images.edges.map(x => x.node.url);
-      const hasRoom = imgs.some(u => /interior|_int|room|ambiente|setting/i.test(u));
-      if (imgs.length > 1 || hasRoom) continue;   // already has a 2nd/room image
+      const imgEdges = n.images.edges.map(x => x.node);
+      const imgs = imgEdges.map(x => x.url);
+      const roomImg = imgEdges.find(x => /interior|_int|room|ambiente|setting/i.test(x.url + (x.altText || '')));
+      const hasRoom = imgs.length > 1 || !!roomImg;
+
+      // REGEN: only the rooms WE generated (in _done.ledger) that have a "Room
+      // Setting"-tagged image — never the original-cohort _interior1 photos.
+      // NORMAL: only products that LACK a room image (add one).
+      if (REGEN) {
+        const ourRoom = roomImg && /room-setting|[-_]room[-_]/i.test((roomImg.altText || '') + roomImg.url);
+        if (!GENERATED.has(id) || !ourRoom) continue;
+      } else if (hasRoom) continue;
       need++;
       if (done.has(id)) continue;
       if (processed >= LIMIT) continue;
 
-      const patternUrl = imgs[0];
+      // pattern = the FIRST non-room image (the swatch)
+      const patternUrl = (imgEdges.find(x => x !== roomImg) || imgEdges[0] || {}).url;
+      const oldRoomId = roomImg ? roomImg.id.split('/').pop() : null;
       if (!patternUrl) { console.log(`  !! ${id} no pattern image — SKIP`); continue; }
 
       try {
@@ -144,14 +165,15 @@ async function main() {
         execFileSync(PY, [COMPOSITE_PY, patPath, initPath, maskPath, String(rep)], { stdio: 'pipe' });
 
         processed++;
-        if (!APPLY) { console.log(`  [dry] ${id} ${n.title}  repeat=${rep}in  -> would inpaint + upload`); continue; }
+        if (!APPLY) { console.log(`  [dry] ${id} ${n.title}  repeat=${rep}in${REGEN ? `  (regen, del old ${oldRoomId})` : ''}  -> would inpaint + upload`); continue; }
 
         await replicateInpaint(initPath, maskPath, outPath);
         spend += COST_PER_IMG;
+        if (REGEN && oldRoomId) await deleteImage(id, oldRoomId);   // remove the old wrong-scale room
         const imgId = await uploadImage(id, outPath, `Room Setting - ${n.title.replace(/\s*\|.*$/, '')}`);
         ok++;
         fs.appendFileSync(LEDGER, id + '\n'); done.add(id);
-        console.log(`  ✓ ${id} ${n.title}  img=${imgId}  [${ok} done · ~$${spend.toFixed(2)}]`);
+        console.log(`  ✓ ${id} ${n.title}  img=${imgId}${REGEN ? ` (replaced ${oldRoomId})` : ''}  [${ok} done · ~$${spend.toFixed(2)}]`);
         await sleep(300);
       } catch (err) {
         errs++;

← ffd94410 Graft native-grid infinite scroll onto LIVE theme (newwall-i  ·  back to Designer Wallcoverings  ·  hero manifest finalizer: socket-timeout hardening reuse + pr 8bb35f8f →