← back to Wallco Ai
feat(/designs surprise-me): defensive null-id handling + 'Try this prompt again' + 'Open full page' link
a18829223134162afadbfcb5641c2a8123aa65f3 · 2026-05-14 09:39:28 -0700 · SteveStudio2
When prod schema drift causes the spoon_all_designs INSERT to silently
fail, the chat handler returns design.id=null. The client used to:
- Render a 'Design #null' label
- Set element ID 'home-review-null'
- Fire interior-designer review with design_id:null → 404 → ugly error
Now:
- Number.isFinite() guards every id-dependent action
- '(unsaved)' label + amber notice explains what's wrong
- Interior-designer review skipped on null id (button reset still runs)
- 'Open full page →' gold pill added when id IS valid (was missing)
- 'Try this prompt again' button (re-renders SAME prompt; previously only random)
- Element IDs now use a per-result random nonce instead of did, so multiple
successive re-renders don't collide on 'home-review-null'
The retry handler shows a stripped-down result card (just image + links) so
the page doesn't grow unbounded if the user mashes 'Try again'.
Files touched
Diff
commit a18829223134162afadbfcb5641c2a8123aa65f3
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Thu May 14 09:39:28 2026 -0700
feat(/designs surprise-me): defensive null-id handling + 'Try this prompt again' + 'Open full page' link
When prod schema drift causes the spoon_all_designs INSERT to silently
fail, the chat handler returns design.id=null. The client used to:
- Render a 'Design #null' label
- Set element ID 'home-review-null'
- Fire interior-designer review with design_id:null → 404 → ugly error
Now:
- Number.isFinite() guards every id-dependent action
- '(unsaved)' label + amber notice explains what's wrong
- Interior-designer review skipped on null id (button reset still runs)
- 'Open full page →' gold pill added when id IS valid (was missing)
- 'Try this prompt again' button (re-renders SAME prompt; previously only random)
- Element IDs now use a per-result random nonce instead of did, so multiple
successive re-renders don't collide on 'home-review-null'
The retry handler shows a stripped-down result card (just image + links) so
the page doesn't grow unbounded if the user mashes 'Try again'.
---
server.js | 46 +++++++++++++++++++++++++++++++++++++++-------
1 file changed, 39 insertions(+), 7 deletions(-)
diff --git a/server.js b/server.js
index cba3050..67c1246 100644
--- a/server.js
+++ b/server.js
@@ -3738,6 +3738,9 @@ ${cat === 'mural-scenic' ? `
var d = (j.actions || []).find(function(a){ return a.type === 'new_design'; });
if (d && d.design) {
var did = d.design.id;
+ var idValid = Number.isFinite(did);
+ var idLabel = idValid ? '#' + did : '(unsaved)';
+ var nonce = Math.random().toString(36).slice(2, 9);
var imgUrl = d.design.image_url;
var hex = d.design.dominant_hex || '#888';
var promptText = String(d.design.prompt || '').replace(/[<>]/g, '');
@@ -3747,22 +3750,47 @@ ${cat === 'mural-scenic' ? `
'<div style="aspect-ratio:1;background:url(\\''+imgUrl+'\\') center/cover #f2efe8;border-radius:10px;border:1px solid var(--line);max-width:520px"></div>' +
'<div style="margin-top:10px;font:13px/1.5 var(--sans);color:var(--ink-soft)">' +
'<span style="display:inline-block;width:11px;height:11px;border-radius:50%;background:'+hex+';vertical-align:middle;margin-right:6px;border:1px solid rgba(0,0,0,.12)"></span>' +
- '<b style="color:var(--ink)">Design #'+did+'</b> · '+pick[0]+' · '+motif+' · '+hex +
+ '<b style="color:var(--ink)">Design '+idLabel+'</b> · '+pick[0]+' · '+motif+' · '+hex +
'</div>' +
'</div>' +
'<div style="display:flex;flex-direction:column;gap:14px;min-width:0">' +
(promptText ? '<details><summary style="cursor:pointer;font:11px/1 var(--sans);color:var(--ink-faint);text-transform:uppercase;letter-spacing:.08em">Prompt ▾</summary><div style="margin-top:8px;font:11px/1.5 ui-monospace,Menlo,monospace;color:var(--ink-soft);max-height:180px;overflow-y:auto;padding:8px;background:var(--card-bg);border:1px solid var(--line);border-radius:6px">'+promptText+'</div></details>' : '') +
- '<div id="home-review-'+did+'" style="padding:14px;border:1px solid var(--line);border-radius:8px;background:var(--card-bg)"><div style="font:11px/1 var(--sans);color:var(--ink-faint);text-transform:uppercase;letter-spacing:.08em">Interior-designer review · #'+did+'</div><div style="margin-top:8px;font:13px/1.5 var(--sans);color:var(--ink-soft)"><span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--gold);animation:rev-pulse 1.2s ease-in-out infinite;margin-right:6px;vertical-align:middle"></span>Running…</div></div>' +
+ (idValid
+ ? '<div id="home-review-'+did+'" style="padding:14px;border:1px solid var(--line);border-radius:8px;background:var(--card-bg)"><div style="font:11px/1 var(--sans);color:var(--ink-faint);text-transform:uppercase;letter-spacing:.08em">Interior-designer review · #'+did+'</div><div style="margin-top:8px;font:13px/1.5 var(--sans);color:var(--ink-soft)"><span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--gold);animation:rev-pulse 1.2s ease-in-out infinite;margin-right:6px;vertical-align:middle"></span>Running…</div></div>'
+ : '<div style="padding:12px;border:1px dashed var(--line);border-radius:8px;background:#fff8e8;font:12px/1.5 var(--sans);color:#7a5f10">Image generated, but the row didn\\'t persist (DB schema drift on prod). Image is available to download — review unavailable until persisted.</div>') +
'<div style="display:flex;flex-direction:column;gap:6px;margin-top:auto">' +
+ (idValid ? '<a href="/design/'+did+'" style="display:block;text-align:center;padding:8px 14px;background:var(--gold,#c9a14b);color:var(--accent,#0d0d0d);text-decoration:none;font-size:13px;font-weight:500;border-radius:6px">Open full page →</a>' : '') +
'<a href="'+imgUrl+'" download style="display:block;text-align:center;padding:8px 14px;border:1px solid var(--ink);color:var(--ink);text-decoration:none;font-size:13px;border-radius:6px">Download pattern</a>' +
- '<button id="home-again-'+did+'" type="button" style="padding:8px 14px;background:var(--ink);color:var(--bg);border:0;border-radius:6px;cursor:pointer;font:13px var(--sans)">Surprise me again</button>' +
+ '<button id="home-retry-'+nonce+'" type="button" style="padding:8px 14px;background:transparent;color:var(--ink);border:1px solid var(--line);border-radius:6px;cursor:pointer;font:13px var(--sans)" data-prompt="'+promptText.replace(/"/g,'"')+'">Try this prompt again</button>' +
+ '<button id="home-again-'+nonce+'" type="button" style="padding:8px 14px;background:var(--ink);color:var(--bg);border:0;border-radius:6px;cursor:pointer;font:13px var(--sans)">Surprise me again</button>' +
'</div>' +
'</div>' +
'</div>' +
'<style>@keyframes rev-pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:.4;transform:scale(.6)} }</style>';
- var againBtn = document.getElementById('home-again-'+did);
+ var againBtn = document.getElementById('home-again-'+nonce);
if (againBtn) againBtn.addEventListener('click', function(){ btn.click(); });
- fetch('/api/studio/interior-design-review', {
+ var retryBtn = document.getElementById('home-retry-'+nonce);
+ if (retryBtn) retryBtn.addEventListener('click', async function(){
+ var p = retryBtn.getAttribute('data-prompt') || promptText;
+ if (!p) return;
+ box.innerHTML = '<div style="font:13px/1.5 -apple-system,sans-serif;color:var(--ink-soft)">Re-rendering same prompt… (~30s)</div>';
+ btn.disabled = true; btn.textContent = '… generating';
+ try {
+ var rr = await fetch('/api/chat/catalog', { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({message: p}) });
+ var jj = await rr.json();
+ var dd = (jj.actions||[]).find(function(a){return a.type==='new_design';});
+ if (dd && dd.design) {
+ var ddid = dd.design.id;
+ var ddidValid = Number.isFinite(ddid);
+ var u = dd.design.image_url, h = dd.design.dominant_hex || '#888';
+ box.innerHTML = '<div style="display:flex;gap:18px;align-items:start;flex-wrap:wrap"><div style="aspect-ratio:1;width:520px;max-width:100%;background:url(\\''+u+'\\') center/cover #f2efe8;border-radius:10px;border:1px solid var(--line)"></div><div style="font:13px var(--sans);color:var(--ink-soft)"><span style="display:inline-block;width:11px;height:11px;border-radius:50%;background:'+h+';margin-right:6px;border:1px solid rgba(0,0,0,.12);vertical-align:middle"></span>Same prompt, fresh render '+(ddidValid?'· #'+ddid:'(unsaved)')+'<br><br>'+(ddidValid?'<a href="/design/'+ddid+'" style="color:var(--gold)">Open full page →</a><br>':'')+'<a href="'+u+'" download style="color:var(--ink)">Download pattern</a></div></div>';
+ } else {
+ box.innerHTML = '<div style="color:#b03a2e">Re-render failed: ' + (jj.error || 'unknown').slice(0,300) + '</div>';
+ }
+ } catch (e) { box.innerHTML = '<div style="color:#b03a2e">Error: ' + e.message + '</div>'; }
+ btn.disabled = false; btn.textContent = orig;
+ });
+ if (idValid) fetch('/api/studio/interior-design-review', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({ design_id: did })
}).then(function(r){ return r.json(); }).then(function(rj){
@@ -3963,10 +3991,14 @@ ${FOOTER}
var f = buildFilter();
var imgs = document.querySelectorAll('.design-card .card-img');
for (var i=0;i<imgs.length;i++) imgs[i].style.filter = f;
- // Fix precedence trap — prior version was `state.hue||0!==0` which parses
- // as `state.hue || (0!==0)` so `changed` could be a number when hue!=0
+ // Fix precedence trap — prior version had state.hue||0!==0 which parses
+ // as state.hue || (0!==0) so 'changed' could be a number when hue!=0
// and was false when hue was 0 regardless of other sliders. Use explicit
// per-key comparison against defaults so summary always reflects truth.
+ // NEVER put backticks in inline comments in this file — the /designs
+ // response is one big template literal; a comment backtick closes it
+ // and parses every downstream line as top-level JS (broke startup
+ // for ~3 minutes on 2026-05-14 commit 109dda6).
var changed = Object.keys(defaults).some(function(k){ return state[k] !== defaults[k]; });
if (summary) summary.textContent = changed ? 'edited' : 'defaults';
}
← 7bbdf26 feat(/designs): keyboard shortcut '/' focuses search (GH/X/S
·
back to Wallco Ai
·
db: add 003_schema_parity migration to bring prod in line wi 628c81f →