← back to Designer Wallcoverings
harden: escape img src in dw-boost-overrides cardHtml (was raw)
9d1c6b1f7ce370ac7b1e554d143e6199b9d2a38c · 2026-09-17 16:48:36 -0700 · Steve
Code-review finding (2026-09-17): src was raw-interpolated while alt/href on
the same line were escaped. Wrap in escHtml(img) for consistency. First-party
feed so low risk, but closes the attribute-breakout inconsistency on a live theme.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1fbVDeQRfYhz5QnxYoPU
Files touched
M shopify/theme-LIVE-591/snippets/dw-boost-overrides.liquidM shopify/theme-files/snippets/dw-boost-overrides.liquid
Diff
commit 9d1c6b1f7ce370ac7b1e554d143e6199b9d2a38c
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Sep 17 16:48:36 2026 -0700
harden: escape img src in dw-boost-overrides cardHtml (was raw)
Code-review finding (2026-09-17): src was raw-interpolated while alt/href on
the same line were escaped. Wrap in escHtml(img) for consistency. First-party
feed so low risk, but closes the attribute-breakout inconsistency on a live theme.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JJ1fbVDeQRfYhz5QnxYoPU
---
.../snippets/dw-boost-overrides.liquid | 30 +++++++++++++++++-----
.../theme-files/snippets/dw-boost-overrides.liquid | 30 +++++++++++++++++-----
2 files changed, 48 insertions(+), 12 deletions(-)
diff --git a/shopify/theme-LIVE-591/snippets/dw-boost-overrides.liquid b/shopify/theme-LIVE-591/snippets/dw-boost-overrides.liquid
index 4888cc93..1392cee4 100644
--- a/shopify/theme-LIVE-591/snippets/dw-boost-overrides.liquid
+++ b/shopify/theme-LIVE-591/snippets/dw-boost-overrides.liquid
@@ -472,8 +472,18 @@ body.dw-filters-collapsed .boost-sd__product-list[class*="grid--"]{grid-template
/* Apply grid and watch for Boost overrides */
applyGrid(defaultCols);
- /* Re-apply when crossing the mobile breakpoint (rotate / resize) */
- window.addEventListener('resize', function(){ applyGrid(defaultCols); });
+ /* Re-apply when crossing the mobile breakpoint (rotate / resize). Read the CURRENT
+ column choice from localStorage (not the init-time defaultCols) so a mid-session
+ slider change survives a resize; debounced so a drag-resize doesn't thrash.
+ (TK review 2026-09-17) */
+ var _dwResizeT;
+ window.addEventListener('resize', function(){
+ clearTimeout(_dwResizeT);
+ _dwResizeT = setTimeout(function(){
+ var live = parseInt(localStorage.getItem('dw-grid-cols'), 10);
+ applyGrid((live >= 3 && live <= 8) ? live : defaultCols);
+ }, 150);
+ });
/* Observe Boost grid for style changes and re-apply our columns + kill padding */
var observer = new MutationObserver(function(){
@@ -572,6 +582,7 @@ body.dw-filters-collapsed .boost-sd__product-list[class*="grid--"]{grid-template
return p.image || '';
}
+ function escHtml(s){ return String(s==null?'':s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,'''); }
function cardHtml(p){
var img = imgOf(p);
if (img) img += (img.indexOf('?') > -1 ? '&' : '?') + 'width=600';
@@ -579,12 +590,12 @@ body.dw-filters-collapsed .boost-sd__product-list[class*="grid--"]{grid-template
/* NEUTRAL classes (dw-if-*) so Boost/theme card scripts never re-process and
strip our <img>. Styled by our own .dw-if-card CSS in the mobile block. */
return '<div class="dw-if-card">' +
- '<a href="/products/'+ p.handle +'">' +
+ '<a href="/products/'+ encodeURIComponent(String(p.handle||'')) +'">' +
'<div class="dw-if-img">' +
- (img ? '<img loading="lazy" src="'+ img +'" alt="'+ nm.replace(/"/g,'"') +'">' : '') +
+ (img ? '<img loading="lazy" src="'+ escHtml(img) +'" alt="'+ escHtml(nm) +'">' : '') +
'</div>' +
- '<div class="dw-if-name">'+ nm +'</div>' +
- '<div class="dw-if-ven">'+ (p.vendor || '') +'</div>' +
+ '<div class="dw-if-name">'+ escHtml(nm) +'</div>' +
+ '<div class="dw-if-ven">'+ escHtml(p.vendor || '') +'</div>' +
'</a></div>';
}
@@ -808,6 +819,13 @@ body.dw-filters-collapsed .boost-sd__product-list[class*="grid--"]{grid-template
if(ready < Math.min(cards.length, 6)) return false;
var decorated=cards.map(function(c,i){return {c:c,i:i,k:cardKey(c,mode)};});
decorated.sort(function(a,b){ return a.k===b.k ? a.i-b.i : (a.k<b.k?-1:1); });
+ /* Bail if the cards are ALREADY in target order. appendChild moves generate
+ addedNodes MutationRecords, which re-trigger the document.body observer below;
+ without this bail the observer -> apply -> reorder -> appendChild cycle re-sorts
+ ~5x/sec forever while a color mode is active. (TK review 2026-09-17) */
+ var same=true;
+ for(var s=0;s<decorated.length;s++){ if(decorated[s].c!==cards[s]){ same=false; break; } }
+ if(same) return true;
var frag=document.createDocumentFragment();
decorated.forEach(function(d){frag.appendChild(d.c);});
grid.appendChild(frag);
diff --git a/shopify/theme-files/snippets/dw-boost-overrides.liquid b/shopify/theme-files/snippets/dw-boost-overrides.liquid
index a83ebbd7..a764860e 100644
--- a/shopify/theme-files/snippets/dw-boost-overrides.liquid
+++ b/shopify/theme-files/snippets/dw-boost-overrides.liquid
@@ -472,8 +472,18 @@ body.dw-filters-collapsed .boost-sd__product-list[class*="grid--"]{grid-template
/* Apply grid and watch for Boost overrides */
applyGrid(defaultCols);
- /* Re-apply when crossing the mobile breakpoint (rotate / resize) */
- window.addEventListener('resize', function(){ applyGrid(defaultCols); });
+ /* Re-apply when crossing the mobile breakpoint (rotate / resize). Read the CURRENT
+ column choice from localStorage (not the init-time defaultCols) so a mid-session
+ slider change survives a resize; debounced so a drag-resize doesn't thrash.
+ (TK review 2026-09-17) */
+ var _dwResizeT;
+ window.addEventListener('resize', function(){
+ clearTimeout(_dwResizeT);
+ _dwResizeT = setTimeout(function(){
+ var live = parseInt(localStorage.getItem('dw-grid-cols'), 10);
+ applyGrid((live >= 3 && live <= 8) ? live : defaultCols);
+ }, 150);
+ });
/* Observe Boost grid for style changes and re-apply our columns + kill padding */
var observer = new MutationObserver(function(){
@@ -572,6 +582,7 @@ body.dw-filters-collapsed .boost-sd__product-list[class*="grid--"]{grid-template
return p.image || '';
}
+ function escHtml(s){ return String(s==null?'':s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,'''); }
function cardHtml(p){
var img = imgOf(p);
if (img) img += (img.indexOf('?') > -1 ? '&' : '?') + 'width=600';
@@ -579,12 +590,12 @@ body.dw-filters-collapsed .boost-sd__product-list[class*="grid--"]{grid-template
/* NEUTRAL classes (dw-if-*) so Boost/theme card scripts never re-process and
strip our <img>. Styled by our own .dw-if-card CSS in the mobile block. */
return '<div class="dw-if-card">' +
- '<a href="/products/'+ p.handle +'">' +
+ '<a href="/products/'+ encodeURIComponent(String(p.handle||'')) +'">' +
'<div class="dw-if-img">' +
- (img ? '<img loading="lazy" src="'+ img +'" alt="'+ nm.replace(/"/g,'"') +'">' : '') +
+ (img ? '<img loading="lazy" src="'+ escHtml(img) +'" alt="'+ escHtml(nm) +'">' : '') +
'</div>' +
- '<div class="dw-if-name">'+ nm +'</div>' +
- '<div class="dw-if-ven">'+ (p.vendor || '') +'</div>' +
+ '<div class="dw-if-name">'+ escHtml(nm) +'</div>' +
+ '<div class="dw-if-ven">'+ escHtml(p.vendor || '') +'</div>' +
'</a></div>';
}
@@ -808,6 +819,13 @@ body.dw-filters-collapsed .boost-sd__product-list[class*="grid--"]{grid-template
if(ready < Math.min(cards.length, 6)) return false;
var decorated=cards.map(function(c,i){return {c:c,i:i,k:cardKey(c,mode)};});
decorated.sort(function(a,b){ return a.k===b.k ? a.i-b.i : (a.k<b.k?-1:1); });
+ /* Bail if the cards are ALREADY in target order. appendChild moves generate
+ addedNodes MutationRecords, which re-trigger the document.body observer below;
+ without this bail the observer -> apply -> reorder -> appendChild cycle re-sorts
+ ~5x/sec forever while a color mode is active. (TK review 2026-09-17) */
+ var same=true;
+ for(var s=0;s<decorated.length;s++){ if(decorated[s].c!==cards[s]){ same=false; break; } }
+ if(same) return true;
var frag=document.createDocumentFragment();
decorated.forEach(function(d){frag.appendChild(d.c);});
grid.appendChild(frag);
← 62f26ac4 security: purge leaked ANTHROPIC_API_KEY from tracked market
·
back to Designer Wallcoverings
·
auto-data-snapshot: 2026-09-17T16:53:13 (3 data files) — sho 40ecde25 →