[object Object]

← 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

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 β†’