[object Object]

← 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

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 →