[object Object]

← back to Wallco Ai

C-bridge (DTD interim): patchDesignsSnapshot() persists curator publishes into designs.json (is_published/user_removed/force-tag + resolve real filename from PG) + reload, so grid+PDP+persistence all agree. Wired into bulk + single publish paths

300315d0152c03fafe80df337eae8bc37d8a25b6 · 2026-05-29 13:15:06 -0700 · Steve

Files touched

Diff

commit 300315d0152c03fafe80df337eae8bc37d8a25b6
Author: Steve <steve@designerwallcoverings.com>
Date:   Fri May 29 13:15:06 2026 -0700

    C-bridge (DTD interim): patchDesignsSnapshot() persists curator publishes into designs.json (is_published/user_removed/force-tag + resolve real filename from PG) + reload, so grid+PDP+persistence all agree. Wired into bulk + single publish paths
---
 server.js | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 51 insertions(+)

diff --git a/server.js b/server.js
index f69cc26..64ddfdb 100644
--- a/server.js
+++ b/server.js
@@ -2053,6 +2053,50 @@ function addToIndex(ids) {
   return n;
 }
 
+// C-bridge (DTD 2026-05-29, interim until the single-read-model refactor):
+// persist a curator publish into the prod designs.json snapshot so it survives
+// restart AND every read-path agrees after a reload (addToIndex is only the
+// in-memory immediate surface). Surgical flag-flip for the given ids — NOT a
+// regen from the sparse prod PG, so the catastrophe guard (refuse if the file
+// looks truncated) is just a safety belt. Atomic write via tmp+rename.
+// CAVEAT: the next Mac2 ./deploy-kamatera.sh overwrites designs.json — full
+// persistence still needs the Mac2 reconciliation (the deferred A refactor).
+function patchDesignsSnapshot(ids) {
+  const set = new Set((Array.isArray(ids) ? ids : [ids]).map(n => parseInt(n, 10)).filter(Number.isFinite));
+  if (!set.size) return 0;
+  const p = path.join(__dirname, 'data', 'designs.json');
+  let arr;
+  try { arr = JSON.parse(fs.readFileSync(p, 'utf8')); } catch (e) { console.warn('[patchSnapshot] read failed:', e.message); return 0; }
+  if (!Array.isArray(arr) || arr.length < 100) { console.warn('[patchSnapshot] catastrophe guard — snapshot too small, skipping'); return 0; }
+  let n = 0;
+  for (const d of arr) {
+    if (!set.has(d.id)) continue;
+    d.is_published = true; d.user_removed = false;
+    // designHasImage() keys off d.filename. Snapshot entries often have an empty
+    // filename + a by-id image_url with no local_path → resolve the real filename
+    // from PG all_designs.local_path so the grid file-existence filter can match.
+    if (!d.filename) {
+      if (d.image_url && /^\/designs\/img\/[^/]+\.(?:png|jpe?g|webp)$/i.test(d.image_url)) {
+        d.filename = d.image_url.split('/').pop();
+      } else {
+        try {
+          const lp = (psqlQuery(`SELECT COALESCE(local_path,'') FROM all_designs WHERE id=${d.id} LIMIT 1;`) || '').trim();
+          const base = lp.split('/').pop();
+          if (base) { d.filename = base; d.image_url = '/designs/img/' + base; }
+        } catch (e) {}
+      }
+    }
+    d.tags = (Array.isArray(d.tags) ? d.tags.filter(t => t !== 'curator-force-publish') : []).concat('curator-force-publish');
+    n++;
+  }
+  if (n) {
+    const tmp = p + '.tmp';
+    fs.writeFileSync(tmp, JSON.stringify(arr));
+    fs.renameSync(tmp, p);
+  }
+  return n;
+}
+
 // Apply one cactus verdict. body { action: 'bad'|'digital'|'fix'|'live'|'unpublish' }.
 function applyCactusDecision(id, action, opts = {}) {
   const raw = psqlQuery(`SELECT row_to_json(t) FROM (SELECT id, local_path FROM all_designs WHERE id=${id}) t;`);
@@ -2143,6 +2187,8 @@ app.post('/api/cactus-decision/:id', express.json({ limit: '2kb' }), (req, res,
     // gate-held → 200 with gate_held:true so the curator records the verdict and
     // offers "Publish anyway" instead of dropping the vote (see in-fn comment).
     if (r && r.gate_held) return res.json({ ok: true, id, action, gate_held: true, reasons: r.reasons, checks: r.checks });
+    // C-bridge: persist a single publish into the snapshot + reload too.
+    if (action === 'live') { try { patchDesignsSnapshot([id]); loadDesigns(); } catch (e) { console.warn('[publish persist]', e.message); } }
     res.json({ ok: true, id, action });
   } catch (e) {
     res.status(e.message === 'design not found' ? 404 : 500).json({ error: e.message });
@@ -2184,6 +2230,11 @@ app.post('/api/cactus-decision/bulk', express.json({ limit: '256kb' }), (req, re
       }
       catch (e) { results.push({ id, ok: false, error: e.message.slice(0, 200) }); err++; }
     }
+    // C-bridge: persist the published ids into the snapshot + reload so the grid
+    // (designHasImage) + every read-path agree and survive restart. One patch +
+    // one reload for the whole batch (not per-id).
+    const pubIds = results.filter(r => r.ok && !r.gate_held).map(r => r.id);
+    if (pubIds.length) { try { patchDesignsSnapshot(pubIds); loadDesigns(); } catch (e) { console.warn('[bulk publish persist]', e.message); } }
     return res.json({ ok: true, total: ids.length, applied: ok, gate_held: held, errored: err, action, results });
   }
 

← c090fe6 addToIndex: derive filename from image_url so designHasImage  ·  back to Wallco Ai  ·  revert: theme1/theme2 toggle on /designs admin (was super-zo 8c1ed72 →