← 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 →