[object Object]

← back to Dw Theme Boost Fix

TK-10835: sync structural CLS fix into rendered snippet (snippets/boost-infinite-override.liquid) for single-file deploy

13f27ed1c09fb9308ea19b377128a0e3a2594d4c · 2026-08-25 10:59:54 -0700 · Steve Abrams

Files touched

Diff

commit 13f27ed1c09fb9308ea19b377128a0e3a2594d4c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Aug 25 10:59:54 2026 -0700

    TK-10835: sync structural CLS fix into rendered snippet (snippets/boost-infinite-override.liquid) for single-file deploy
---
 snippets/boost-infinite-override.liquid | 183 +++++++++++++++++++++++++++++++-
 1 file changed, 182 insertions(+), 1 deletion(-)

diff --git a/snippets/boost-infinite-override.liquid b/snippets/boost-infinite-override.liquid
index e98e121..190fffe 100644
--- a/snippets/boost-infinite-override.liquid
+++ b/snippets/boost-infinite-override.liquid
@@ -46,7 +46,7 @@
   ============================================================================
 {% endcomment %}
 <script>
-/* DW-GUARD-VERSION 20260730-1305 */
+/* DW-GUARD-VERSION 20260825-1130 */
 (function () {
   'use strict';
   if (window.__dwBoostInfinite) return;
@@ -116,6 +116,46 @@
   })();
 
 
+
+  /* DW same-origin request governor + 429/430 auto-backoff (TK-10074, 2026-07-30).
+     Reproduced: Shopify local_rate_limited trips ~30-45 origin req/s per IP, ~32s
+     lockout. The theme is request-heavy (~1700 req/session; infinite scroll cascades
+     ~14 pages in <8s), so a bursty visitor (fast nav / prefetch / multi-tab / dense
+     grid) can self-inflict 429 and see the raw local_rate_limit page. Installed AFTER
+     the dedup wrapper so it is the OUTERMOST window.fetch: caps concurrent same-origin
+     requests to 6 (can't reach the trip point) AND retries 429/430 invisibly honoring
+     Retry-After, so the grid just pauses instead of erroring. Idempotent. */
+  (function(){
+    'use strict';
+    if (window.__dwReqGovernor) return; window.__dwReqGovernor = true;
+    var ORIGIN = location.origin, MAX_CONCURRENT = 6, active = 0, queue = [];
+    var origFetch = window.fetch;              // wraps whatever is current (incl. dedup)
+    if (typeof origFetch !== 'function') return;
+    function pump(){ while (active < MAX_CONCURRENT && queue.length) queue.shift()(); }
+    function isOrigin(u){ try { u = String(u); return u.indexOf(ORIGIN) === 0 || u.charAt(0) === '/'; } catch(e){ return false; } }
+    window.fetch = function(input, init){
+      var url = (typeof input === 'string') ? input : (input && input.url) || '';
+      if (!isOrigin(url)) return origFetch.call(this, input, init);
+      var self = this;
+      return new Promise(function(resolve, reject){
+        queue.push(function run(){
+          active++;
+          (function attempt(tries){
+            origFetch.call(self, input, init).then(function(resp){
+              if ((resp.status === 429 || resp.status === 430) && tries < 4){
+                var ra = parseFloat(resp.headers.get('retry-after'));
+                var wait = (isFinite(ra) && ra > 0 ? ra*1000 : Math.min(1000*Math.pow(2,tries), 8000)) + Math.random()*400;
+                setTimeout(function(){ attempt(tries + 1); }, wait); return;
+              }
+              active--; pump(); resolve(resp);
+            }).catch(function(e){ active--; pump(); reject(e); });
+          })(0);
+        });
+        pump();
+      });
+    };
+  })();
+
   // USER PAGINATION CHOICE (DW, 2026-07-13 — Steve's verdict on customer
   // pushback): infinite scroll stays the DEFAULT, but the shopper can switch
   // to classic numbered pages of 100 via the floating toggle rendered below.
@@ -124,6 +164,21 @@
   var mode = 'infinite';
   try { if (localStorage.getItem('dwPaginationMode') === 'paged') mode = 'paged'; } catch (e) {}
 
+  // DEEP-LANDING -> STAY INFINITE (DW, 2026-08-25 — Steve's verdict: persist
+  // infinite scroll on deep-linked ?page=N landings instead of falling back to
+  // numbered pages). Previously (2026-08-04) a deep ?page=N (N>1) landing flipped
+  // mode='paged' to dodge the append-shove CLS; Steve now wants the shopper to
+  // KEEP infinite scroll even when they arrive on a deep link. We no longer flip
+  // the mode — a deep ?page=N landing is instead handled by the two infinite-mode
+  // primitives already below: DEEP-LINK NORMALIZE strips the page param via
+  // history.replaceState so Boost starts clean at page 1, and CASCADE GUARD pins
+  // the viewport to the top until a real user gesture so no auto-append cascade
+  // fires on landing. Net: deep landings render as a clean page-1 infinite grid
+  // with no batch-append shove. The numbered-page toggle (localStorage
+  // 'dwPaginationMode') stays fully available for shoppers who opt into pages.
+  // (Deliberately a no-op branch retained for clarity: mode stays 'infinite'
+  // for a deep ?page=N landing unless the shopper explicitly chose 'paged'.)
+
   var TARGET = mode === 'paged' ? 'default' : 'infinite_scroll';
 
   // DEEP-LINK NORMALIZE (DW, 2026-07-28 — root fix for the deep-page jump/flip):
@@ -319,3 +374,129 @@
   {% endif %}
 })();
 </script>
+{% if request.page_type == 'collection' or request.page_type == 'search' %}
+{% comment %}
+  ============================================================================
+  STRUCTURAL CLS FIX (DW, 2026-08-25 — TK-10835 path 2, Steve-approved).
+  ----------------------------------------------------------------------------
+  MEASURED (real Chrome + layout-shift sources API): the Boost product grid
+  lives in `section.product-app--container` (a Shopify section). At
+  DOMContentLoaded it is an ~800px placeholder; when Boost injects the 3-col
+  grid it grows (and grows again on EVERY infinite-scroll append). Because the
+  page furniture below it — the sibling section `__main`, plus the body-level
+  `#dwcw-btn` and `__footer` sections — flows BELOW the grid, each grid growth
+  shoved all of it down. That append-driven shove was the whole jank: CLS
+  decomposed as initial-render ~0.16, scroll-APPEND ~0.21-0.34+ (worse the
+  deeper you scrolled). Total control CLS 0.37 (24 appends) → 0.50 (40).
+
+  Two structural primitives kill it WITHOUT stalling Boost's own bottom
+  detection (Boost fires on `.boost-sd__pagination-infinite-scroll-container-
+  -target`, the sentinel that sits BELOW the grid):
+
+    (A) REORDER — make `.boost-sd__product-filter-fallback` a flex column and
+        push the grid's Shopify section to `order:99` so `__main` renders
+        ABOVE the grid. Grid growth then has no `__main` below it to shove.
+        `:has()` is the fast path; a JS fallback tags the section with
+        `.dw-grid-section` for browsers without `:has()`.
+
+    (B) COMPENSATING SPACER — a 1px-wide, aria-hidden spacer appended as the
+        LAST child of `.main-content`, whose height = (reservedBudget − live
+        content height). As the grid grows, the spacer SHRINKS by the same
+        amount, so `.main-content`'s TOTAL height stays constant → every
+        body-level section after it (`#dwcw-btn`, `__footer`) never re-shoves.
+        Budget is monotonic (bumps only if a huge grid exceeds it → at most one
+        tiny shove, then stable). The Boost sentinel is ABOVE the spacer, so
+        incremental scrolling still reaches it — infinite loading is intact
+        (verified: card count matches control at 336 cards / 40 wheels).
+
+  RESULT (real Chrome, reproducible ×3): normal landing CLS 0.37→0.03,
+  40-wheel deep stress 0.50→0.027, deep ?page=4 0.155→0.03. Append portion
+  0.21→0.000. All well under the 0.1 target on BOTH the normal landing and the
+  deep ?page=N case — so infinite scroll now persists on deep links (via the
+  DEEP-LINK NORMALIZE + CASCADE GUARD above) with NO append jank.
+
+  Card-image aspect-ratio reservation (below) is retained for within-batch
+  lazy-image stability; it was never the append-shove culprit (the shifters
+  were __main/__footer/dwcw-btn, not the cards) but it's still correct + free.
+  ============================================================================
+{% endcomment %}
+<style id="dw-cls-structural">
+/* Reserve each Boost card's image box so lazy images can't reflow siblings on
+   append (kept from 2026-08-04; matches the theme's forced 1/1 look). */
+.boost-sd__product-item-grid-view-layout-image,
+.boost-sd__product-link-image,
+.boost-sd__product-link-image > div{ aspect-ratio:1/1 !important; overflow:hidden; display:block; }
+.boost-sd__product-image-wrapper,
+.boost-sd__product-image{ aspect-ratio:1/1 !important; overflow:hidden; }
+.boost-sd__product-image-img{ width:100% !important; height:100% !important; object-fit:cover !important; }
+/* (A) Reorder: grid section renders AFTER __main so grid growth never shoves __main.
+   :has() fast path + .dw-grid-section JS fallback for non-:has() browsers. */
+.boost-sd__product-filter-fallback{ display:flex; flex-direction:column; }
+.boost-sd__product-filter-fallback > .shopify-section:has(section.product-app--container),
+.boost-sd__product-filter-fallback > .shopify-section:has(.boost-sd-container){ order:99; }
+.boost-sd__product-filter-fallback > .shopify-section.dw-grid-section{ order:99; }
+</style>
+<script id="dw-cls-structural-js">
+(function () {
+  'use strict';
+  if (window.__dwClsStructural) return;
+  window.__dwClsStructural = true;
+
+  // (A-fallback) If :has() is unsupported, tag the grid's Shopify section with
+  // .dw-grid-section so the CSS order rule above still fires.
+  function reorderFallback() {
+    try { if (window.CSS && CSS.supports && CSS.supports('selector(:has(*))')) return; } catch (e) {}
+    var fb = document.querySelector('.boost-sd__product-filter-fallback');
+    if (!fb) return;
+    Array.prototype.forEach.call(fb.children, function (ch) {
+      if (ch.querySelector && (ch.querySelector('section.product-app--container') || ch.querySelector('.boost-sd-container'))) {
+        ch.classList.add('dw-grid-section');
+      }
+    });
+  }
+
+  // (B) Compensating shrinking spacer — keeps .main-content total height constant
+  // as the grid grows, so body-level furniture below it (#dwcw-btn, __footer)
+  // never re-shoves on append. Sentinel stays above the spacer → loading intact.
+  var mc, spacer, reserved = 0;
+  function ensure() {
+    mc = mc || document.querySelector('.main-content');
+    if (!mc) return false;
+    if (!document.querySelector('.boost-sd__product-list')) return false;
+    if (!spacer) {
+      spacer = document.createElement('div');
+      spacer.id = 'dw-cls-spacer';
+      spacer.style.cssText = 'width:1px;order:100;flex:0 0 auto;';
+      spacer.setAttribute('aria-hidden', 'true');
+      mc.appendChild(spacer);
+    }
+    return true;
+  }
+  function contentHeight() {
+    var sp = spacer ? (parseFloat(spacer.style.height) || 0) : 0;
+    return Math.ceil(mc.scrollHeight - sp);   // .main-content height EXCLUDING our spacer
+  }
+  var stopped = false;
+  function update() {
+    if (stopped || !ensure()) return;
+    // Numbered-pages mode doesn't append → no spacer needed; release if the shopper opted in.
+    try { if (localStorage.getItem('dwPaginationMode') === 'paged') { spacer.style.height = '0px'; stopped = true; return; } } catch (e) {}
+    var c = contentHeight();
+    if (reserved === 0) reserved = c + 3000;              // initial budget above current content
+    if (c > reserved - 200) reserved = c + 3000;          // budget nearly hit → bump (rare, one tiny shove)
+    spacer.style.height = Math.max(0, reserved - c) + 'px';
+  }
+  function start() {
+    reorderFallback();
+    if (!ensure()) { setTimeout(start, 40); return; }
+    update();
+    try { new ResizeObserver(update).observe(document.querySelector('.boost-sd__product-list')); } catch (e) {}
+    try { new MutationObserver(update).observe(mc, { childList: true, subtree: true }); } catch (e) {}
+    var iv = setInterval(function () { if (stopped) { clearInterval(iv); return; } update(); }, 100);
+    setTimeout(function () { clearInterval(iv); }, 60000);
+  }
+  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
+  else start();
+})();
+</script>
+{% endif %}

← c0ff941 TK-10835: structural CLS fix for Boost infinite scroll (reor  ·  back to Dw Theme Boost Fix  ·  auto-data-snapshot: 2026-08-25T12:44:18 (1 data files) — dat 8116bee →