← back to Interiordesignershowroom
fix: graceful-degrade broken guide-hero + room-scene images (interim)
63069ecaaa671bdbfce6449174d95c1ff7b74007 · 2026-08-09 17:21:29 -0700 · Steve Abrams
Interim mitigation for TK-10390 (150 auto-rooms + some guide heroes point to
/img/rooms/*.png files that don't exist -> broken-image icons on /room/:slug,
/guides, and home). Root-cause regeneration stays claude-email's auto-room lane.
- imgfallback.js: data-hide-on-error now hides the whole figure.room-scene-wrap
when a .room-scene img fails (so the hotspot layer doesn't float over an empty
box); other opt-in imgs hide themselves as before.
- sceneFigure (render.js) + 3 guide-hero <img> sites (home/guides/detail): added
data-hide-on-error.
- No-op for images that load; complementary to regeneration (not conflicting).
Verified in real Chrome: broke a scene src -> figure display:none, roomThumbs
rail stays visible, valid images unaffected. Deploy GATED -> pending-approval.
Files touched
M lib/render.jsM public/js/imgfallback.jsM server.js
Diff
commit 63069ecaaa671bdbfce6449174d95c1ff7b74007
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Sun Aug 9 17:21:29 2026 -0700
fix: graceful-degrade broken guide-hero + room-scene images (interim)
Interim mitigation for TK-10390 (150 auto-rooms + some guide heroes point to
/img/rooms/*.png files that don't exist -> broken-image icons on /room/:slug,
/guides, and home). Root-cause regeneration stays claude-email's auto-room lane.
- imgfallback.js: data-hide-on-error now hides the whole figure.room-scene-wrap
when a .room-scene img fails (so the hotspot layer doesn't float over an empty
box); other opt-in imgs hide themselves as before.
- sceneFigure (render.js) + 3 guide-hero <img> sites (home/guides/detail): added
data-hide-on-error.
- No-op for images that load; complementary to regeneration (not conflicting).
Verified in real Chrome: broke a scene src -> figure display:none, roomThumbs
rail stays visible, valid images unaffected. Deploy GATED -> pending-approval.
---
lib/render.js | 2 +-
public/js/imgfallback.js | 9 +++++++--
server.js | 6 +++---
3 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/lib/render.js b/lib/render.js
index 65f6310..270ba37 100644
--- a/lib/render.js
+++ b/lib/render.js
@@ -175,7 +175,7 @@ function sceneFigure({ scene_image, title, hotspots = [] }) {
</div>`;
}).join('');
return `<figure class="room-scene-wrap${hotspots.length ? ' has-spots' : ''}">
- <img class="room-scene" src="${esc(scene_image)}" alt="AI-generated scene for ${esc(title)}">
+ <img class="room-scene" data-hide-on-error src="${esc(scene_image)}" alt="AI-generated scene for ${esc(title)}">
<div class="hotspot-layer">${spots}</div>
${hotspots.length ? '<span class="scene-hint" aria-hidden="true">Tap a dot to shop a piece</span>' : ''}
</figure><script src="${v('/js/hotspots.js')}" defer></script>`;
diff --git a/public/js/imgfallback.js b/public/js/imgfallback.js
index 0084912..ac44c99 100644
--- a/public/js/imgfallback.js
+++ b/public/js/imgfallback.js
@@ -14,6 +14,11 @@ document.addEventListener('error', function (e) {
el.parentElement.classList.add('noimg-fallback');
return;
}
- // JS-rendered thumbs (build/moodboard/cart) opt in with data-hide-on-error
- if (el.hasAttribute('data-hide-on-error')) el.style.display = 'none';
+ // JS-rendered thumbs (build/moodboard) + server-rendered guide heroes / room scenes
+ // opt in with data-hide-on-error. A missing room-scene hides its whole <figure> so the
+ // hotspot layer doesn't float over an empty box (the roomThumbs rail beside it stays).
+ if (el.hasAttribute('data-hide-on-error')) {
+ var scope = (el.classList.contains('room-scene') && el.closest) ? el.closest('figure.room-scene-wrap') : null;
+ (scope || el).style.display = 'none';
+ }
}, true);
diff --git a/server.js b/server.js
index cc96924..1af4d32 100644
--- a/server.js
+++ b/server.js
@@ -133,7 +133,7 @@ app.get('/', async (_req, res, next) => {
`<a class="room-tile" href="/rooms/${slug}"><span>${esc(label)}</span></a>`).join('');
const guideCards = guides.map((g) =>
`<a class="guide-card" href="/guides/${esc(g.slug)}">
- ${g.hero_image ? `<img loading="lazy" src="${esc(g.hero_image)}" alt="${esc(g.title)}">` : ''}
+ ${g.hero_image ? `<img loading="lazy" data-hide-on-error src="${esc(g.hero_image)}" alt="${esc(g.title)}">` : ''}
<h3>${esc(g.title)}</h3><p>${esc(g.dek || '')}</p></a>`).join('');
const body = `
<section class="hero">
@@ -257,7 +257,7 @@ app.get('/guides', async (_req, res, next) => {
const { rows } = await db.query(`SELECT slug,title,dek,hero_image FROM guides WHERE published ORDER BY created_at DESC`);
const cards = rows.map((g) =>
`<a class="guide-card" href="/guides/${esc(g.slug)}">
- ${g.hero_image ? `<img loading="lazy" src="${esc(g.hero_image)}" alt="${esc(g.title)}">` : ''}
+ ${g.hero_image ? `<img loading="lazy" data-hide-on-error src="${esc(g.hero_image)}" alt="${esc(g.title)}">` : ''}
<h3>${esc(g.title)}</h3><p>${esc(g.dek || '')}</p></a>`).join('');
const crumbs = [{ name: 'Home', url: `${SITE.url}/` }, { name: 'Guides', url: `${SITE.url}/guides` }];
const body = `<section>${breadcrumbNav(crumbs)}<h1>Buying Guides</h1><div class="guide-grid">${cards || '<p>Guides coming soon.</p>'}</div></section>`;
@@ -288,7 +288,7 @@ app.get('/guides/:slug', async (req, res, next) => {
const body = `<article class="guide">
${breadcrumbNav(crumbs)}
<h1>${esc(g.title)}</h1><p class="dek">${esc(g.dek || '')}</p>
- ${g.hero_image ? `<img class="guide-hero" src="${esc(g.hero_image)}" alt="${esc(g.title)}">` : ''}
+ ${g.hero_image ? `<img class="guide-hero" data-hide-on-error src="${esc(g.hero_image)}" alt="${esc(g.title)}">` : ''}
<div class="guide-body">${md(g.body_md || '')}</div>
${picks.length ? `<h2>Shop this guide</h2><div class="grid">${picks.map(productCard).join('')}</div>` : ''}
</article>`;
← 23f8ed4 fix(csp): allow static.cloudflareinsights.com in public scri
·
back to Interiordesignershowroom
·
auto-data-snapshot: 2026-08-09T17:28:38 (2 data files) — .pl 9530ee0 →