← back to Wallco Ai
theme-gallery: Set Live promotes a variant to take over the bare /design/:id PDP (global, file-backed, ?classic=1 bypass)
6b5a6a16d844485259d482ee8a90865b99d7b6f4 · 2026-05-31 17:29:10 -0700 · Steve Abrams
Files touched
M .deploy.confM public/admin/theme-gallery.html
Diff
commit 6b5a6a16d844485259d482ee8a90865b99d7b6f4
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun May 31 17:29:10 2026 -0700
theme-gallery: Set Live promotes a variant to take over the bare /design/:id PDP (global, file-backed, ?classic=1 bypass)
---
.deploy.conf | 2 +-
public/admin/theme-gallery.html | 76 +++++++++++++++++++++++++++++++++++++++--
2 files changed, 74 insertions(+), 4 deletions(-)
diff --git a/.deploy.conf b/.deploy.conf
index a18dbb4..1caf4db 100644
--- a/.deploy.conf
+++ b/.deploy.conf
@@ -13,4 +13,4 @@ HEALTH_URL="https://wallco.ai/health"
# all WRITE to these files. Pre-2026-05-24 the deploy was rsync'ing local stale
# copies over them and wiping fresh prod flags between scan + cleanup (lost the
# 4 cactus-pine-scenic flags from the 2026-05-25 prod scan that way).
-RSYNC_EXTRA_EXCLUDES="data/generated data/generated_pre_seamless_backup data/images data/rooms data/spoonflower-pulled data/fliepaper-bugs public/uploads public/marketplace/uploads data/designs.json data/marketplace/events.jsonl data/ghost-scan-flagged.jsonl data/ghost-scan-results.jsonl data/ghost-scan-triage.jsonl data/ghost-scan-flagged.pre-*.jsonl data/ghost-scan-flagged.post-*.jsonl data/ghost-scan-results.pre-*.jsonl data/bad-aesthetic-patterns.jsonl data/bad-aesthetic-patterns.*.jsonl data/ghost-purge.jsonl data/ghost-labels.jsonl data/fixes-feed.jsonl data/.rescan-current.txt data/.rescan-ready.txt"
+RSYNC_EXTRA_EXCLUDES="data/generated data/generated_pre_seamless_backup data/images data/rooms data/spoonflower-pulled data/fliepaper-bugs public/uploads public/marketplace/uploads data/designs.json data/marketplace/events.jsonl data/ghost-scan-flagged.jsonl data/ghost-scan-results.jsonl data/ghost-scan-triage.jsonl data/ghost-scan-flagged.pre-*.jsonl data/ghost-scan-flagged.post-*.jsonl data/ghost-scan-results.pre-*.jsonl data/bad-aesthetic-patterns.jsonl data/bad-aesthetic-patterns.*.jsonl data/ghost-purge.jsonl data/ghost-labels.jsonl data/fixes-feed.jsonl data/.rescan-current.txt data/.rescan-ready.txt data/pdp-theme.json"
diff --git a/public/admin/theme-gallery.html b/public/admin/theme-gallery.html
index 87155cf..eddfe69 100644
--- a/public/admin/theme-gallery.html
+++ b/public/admin/theme-gallery.html
@@ -69,6 +69,13 @@
<button data-w="390" data-h="760">Mobile</button>
</span>
</div>
+ <div class="tg-row" style="margin-top:10px">
+ <span class="chip" id="liveChip" title="The variant currently serving every customer /design/:id page">
+ 🟢 Live PDP theme: <strong id="liveTxt" style="margin-left:4px">— monolith (default) —</strong>
+ </span>
+ <button id="clearLiveBtn" title="Revert all PDPs to the built-in monolith template">Revert to monolith</button>
+ <span class="sub" id="setMsg" style="color:var(--accent)"></span>
+ </div>
</header>
<main>
<div class="grid" id="grid"></div>
@@ -99,7 +106,56 @@ const VARIANTS = [
['roomshow', 'V17 · Room-Left Showcase'],
];
const $ = s => document.querySelector(s);
+const LABELS = Object.fromEntries(VARIANTS);
let curId = null;
+let liveTheme = null; // the variant slug currently serving all customer PDPs (null = monolith)
+
+async function loadLiveTheme(){
+ try {
+ const r = await fetch('/api/pdp-theme');
+ const j = await r.json();
+ liveTheme = j && j.theme || null;
+ } catch(e){ liveTheme = null; }
+ renderLiveChip();
+ markLive();
+}
+function renderLiveChip(){
+ $('#liveTxt').textContent = liveTheme
+ ? (LABELS[liveTheme] || liveTheme)
+ : '— monolith (default) —';
+ $('#clearLiveBtn').style.display = liveTheme ? '' : 'none';
+}
+function markLive(){
+ document.querySelectorAll('.tcard').forEach(card => {
+ const isLive = card.dataset.slug === liveTheme;
+ const badge = card.querySelector('.livebadge');
+ if (badge) badge.hidden = !isLive;
+ card.style.outline = isLive ? '2px solid #0a7d3a' : '';
+ const btn = card.querySelector('.setlive');
+ if (btn){ btn.textContent = isLive ? '✓ Live' : 'Set Live'; btn.disabled = isLive;
+ btn.classList.toggle('primary', false); }
+ });
+}
+async function setLive(slug){
+ const msg = $('#setMsg');
+ msg.textContent = 'Setting…';
+ try {
+ const r = await fetch('/api/admin/pdp-theme', {
+ method:'POST', headers:{'Content-Type':'application/json'},
+ credentials:'same-origin', body: JSON.stringify({ theme: slug })
+ });
+ const j = await r.json();
+ if (!r.ok || !j.ok) throw new Error(j.error || ('HTTP '+r.status));
+ liveTheme = j.theme || null;
+ renderLiveChip(); markLive();
+ msg.textContent = slug ? ('✓ Live → '+(LABELS[slug]||slug)+'. Every /design/:id now serves it.')
+ : '✓ Reverted to monolith.';
+ setTimeout(()=>{ msg.textContent=''; }, 4000);
+ } catch(e){
+ msg.textContent = '✗ '+e.message; msg.style.color = '#b00';
+ setTimeout(()=>{ msg.textContent=''; msg.style.color='var(--accent)'; }, 5000);
+ }
+}
let logicalW = +(localStorage.getItem('tg-vw') || 1280);
let logicalH = +(localStorage.getItem('tg-vh') || 1700);
@@ -114,14 +170,18 @@ function buildGrid(id){
curId = id;
const grid = $('#grid'); grid.innerHTML = '';
VARIANTS.forEach(([slug,label]) => {
- const card = document.createElement('div'); card.className = 'tcard';
+ const card = document.createElement('div'); card.className = 'tcard'; card.dataset.slug = slug;
const url = `/design/${id}/${slug}`;
card.innerHTML =
- `<h2><span>${label} <span class="slug">/${slug}</span></span>`+
- `<a class="btn" href="${url}" target="_blank" rel="noopener" style="padding:3px 9px;font-size:11px">open ↗</a></h2>`+
+ `<h2><span>${label} <span class="slug">/${slug}</span><span class="livebadge" hidden style="margin-left:8px;color:#0a7d3a;font-weight:700;font-size:11px">● LIVE</span></span>`+
+ `<span style="display:flex;gap:6px">`+
+ `<button class="setlive" data-slug="${slug}" style="padding:3px 9px;font-size:11px">Set Live</button>`+
+ `<a class="btn" href="${url}" target="_blank" rel="noopener" style="padding:3px 9px;font-size:11px">open ↗</a>`+
+ `</span></h2>`+
`<div class="frame" data-url="${url}"></div>`;
grid.appendChild(card);
});
+ markLive();
rescale();
// lazy-mount iframes as cards scroll into view
const io = new IntersectionObserver((ents,obs)=>{
@@ -237,10 +297,20 @@ syncSliderUI();
$('#loadBtn').addEventListener('click', ()=>go($('#idIn').value));
$('#idIn').addEventListener('keydown', e=>{ if(e.key==='Enter') go($('#idIn').value); });
$('#newestBtn').addEventListener('click', newest);
+// Delegated Set-Live clicks (cards are rebuilt on every design load)
+$('#grid').addEventListener('click', e=>{
+ const b = e.target.closest('.setlive'); if(!b) return;
+ e.preventDefault();
+ if (confirm('Make "'+(LABELS[b.dataset.slug]||b.dataset.slug)+'" the LIVE theme for EVERY customer /design/:id page?')) setLive(b.dataset.slug);
+});
+$('#clearLiveBtn').addEventListener('click', ()=>{
+ if (confirm('Revert ALL customer PDPs to the built-in monolith template?')) setLive(null);
+});
window.addEventListener('resize', ()=>{ clearTimeout(window._rt); window._rt=setTimeout(rescale,150); });
// init: ?id= > last-used > newest
(function init(){
+ loadLiveTheme();
const q = new URLSearchParams(location.search).get('id');
const last = localStorage.getItem('tg-id');
if(q) go(q);
← 606f45c designer-studio compose: async background generation — retur
·
back to Wallco Ai
·
Fix two more PDP runtime JS errors (hint TDZ + mailto newlin 47df49c →