β back to Wallco Ai
feat(design-curator): π Room setting button β generate room mockup, show below design in chip
a717efcee10e23cff91f2ca7a5a58116c8dfb50e Β· 2026-06-09 15:45:11 -0700 Β· Steve Abrams
Per-chip 'Room setting' button POSTs to existing /api/room (admin via q() token),
shows a spinner, then injects the rendered room image + caption in a .roomgen
box directly below the design image. Reuses the auth-gated renderer; honest error
surfacing on failure. No server change β static curator page.
Files touched
M public/admin/design-curator.html
Diff
commit a717efcee10e23cff91f2ca7a5a58116c8dfb50e
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jun 9 15:45:11 2026 -0700
feat(design-curator): π Room setting button β generate room mockup, show below design in chip
Per-chip 'Room setting' button POSTs to existing /api/room (admin via q() token),
shows a spinner, then injects the rendered room image + caption in a .roomgen
box directly below the design image. Reuses the auth-gated renderer; honest error
surfacing on failure. No server change β static curator page.
---
public/admin/design-curator.html | 39 +++++++++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+)
diff --git a/public/admin/design-curator.html b/public/admin/design-curator.html
index 2bd7472..e52cdba 100644
--- a/public/admin/design-curator.html
+++ b/public/admin/design-curator.html
@@ -109,6 +109,14 @@
.acts .unpub { border-color:#3a3f44; color:#a8b3bc; }
.acts .bakebtn { border-color:#1f4a5a; color:#7fe3ff; background:#13262d; }
.acts .bakebtn:disabled { opacity:.6; }
+ .acts .roombtn { border-color:#1f5a3a; color:#9af0c2; background:#102a1d; }
+ .acts .roombtn:disabled { opacity:.6; }
+ .roomgen { display:none; }
+ .roomgen.show { display:block; padding:6px 8px 2px; }
+ .roomgen-img { width:100%; border-radius:8px; display:block; }
+ .roomgen-cap { font:600 11px/1.4 inherit; color:#9af0c2; padding:3px 0 2px; }
+ .roomgen-status { font:600 12px/1.4 inherit; color:#9aa; text-align:center; padding:8px; }
+ .roomgen-status.err { color:#ff9a9a; }
/* Haiku-vision verdict chip β appears on cards where the haiku-weekend pass flagged a defect */
.b.haiku { background:#4a1f5a; color:#f2c8ff; font-weight:700; }
@@ -456,6 +464,30 @@ function pickRoomKey(rm){
: keys.includes('office') ? 'office'
: keys[0];
}
+// π On-demand room-setting: POST /api/room β inject the rendered room image
+// below the design image in this chip. Reuses the auth-gated $/call renderer
+// (admin token via q()); shows a spinner, then the image + caption.
+async function renderRoomSetting(id, btn){
+ const box = document.getElementById('roomgen-' + id);
+ const orig = btn.textContent;
+ btn.disabled = true; btn.textContent = 'π Renderingβ¦';
+ if (box){ box.classList.add('show'); box.innerHTML = '<div class="roomgen-status">π Rendering roomβ¦ (~15s)</div>'; }
+ try {
+ const r = await fetch(q('/api/room'), {
+ method:'POST', headers:{'Content-Type':'application/json'},
+ body: JSON.stringify({ design_id: id, roomType: 'bedroom' })
+ });
+ const j = await r.json().catch(()=>({}));
+ if (!r.ok || !j.ok) throw new Error(j.message || j.error || ('HTTP ' + r.status));
+ if (box) box.innerHTML =
+ `<img class="roomgen-img" src="${j.image_url}" alt="room setting" loading="lazy">` +
+ `<div class="roomgen-cap">π ${ (j.roomType||'room').replace(/_/g,' ') } mockup</div>`;
+ btn.textContent = 'π Room β';
+ } catch (e){
+ if (box) box.innerHTML = `<div class="roomgen-status err">β ${e.message}</div>`;
+ btn.disabled = false; btn.textContent = orig;
+ }
+}
function roomImgHTML(d){
const k = pickRoomKey(d.room_mockups);
if (!k) return '';
@@ -517,6 +549,7 @@ function card(d, idx){
<a class="dbg-btn" href="${q('/admin/seam-debug/'+d.id)}" target="_blank" rel="noopener noreferrer" title="open seam-defect debugger β see where the mid-line breaks">π¬ Debug seams</a>
<div class="state" style="${stateLabel?'display:block':''}">${stateLabel}</div>
</div>
+ <div class="roomgen" id="roomgen-${d.id}"></div>
<div class="meta">
<div class="cw" title="${d.category||''}">${colorway(d.category)} <span class="sub">#${d.id}</span></div>
<div class="scores">
@@ -533,6 +566,7 @@ function card(d, idx){
<button class="live" data-a="live" title="Publish β go live in the web viewer (4)">β Publish <kbd>4</kbd></button>
<button class="etsy" data-a="etsy" title="Send to Etsy bucket (5) β removes from new-design pool, queues for digital download listing">π Etsy <kbd>5</kbd></button>
<button class="unpub" data-a="unpublish" title="Take down from live catalog (6) β sets is_published=FALSE, reversible via Publish">π΄ Unpublish <kbd>6</kbd></button>
+ <button class="roombtn" data-room="${d.id}" style="grid-column:1/-1" title="Generate a room-setting mockup for this design and show it below the image (~15s, ~$0.10).">π Room setting</button>
<button class="bakebtn" data-bake="${d.id}" style="grid-column:1/-1" title="Bake the current π¨ Adjust settings onto this image as a NEW unpublished design (original untouched). Set the Adjust sliders off neutral first.">β€ Bake adjustment</button>
</div>`;
@@ -564,6 +598,11 @@ function card(d, idx){
e.stopPropagation();
bakeAdjustment(d.id, e.currentTarget);
});
+ // π Room setting β generate a room mockup on demand, show it below the design image
+ el.querySelector('.acts [data-room]')?.addEventListener('click', e => {
+ e.stopPropagation();
+ renderRoomSetting(d.id, e.currentTarget);
+ });
// SHIFT+drag on the image β draw a dotted "bad area" box (id BAD elements for fixing)
const thumb = el.querySelector('.thumb');
thumb.addEventListener('mousedown', e => {
β e3ccb0e cactus scale-sample: 28 gens, seam-PASS 76-90% per window vs
Β·
back to Wallco Ai
Β·
harden settlement vision fallback (debugger pass): reject no a3e8048 β