[object Object]

← back to Dw Theme Boost Fix

fix: cascade-guard for deep-page infinite-scroll runaway (Hollywood vendor page) — pin scroll + user-gesture gate; synced from live theme 144396058675 (session close)

62e4c65e843d9ee7e6c9c32f2d97771b4b569c0f · 2026-07-30 13:11:36 -0700 · Steve

Files touched

Diff

commit 62e4c65e843d9ee7e6c9c32f2d97771b4b569c0f
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Jul 30 13:11:36 2026 -0700

    fix: cascade-guard for deep-page infinite-scroll runaway (Hollywood vendor page) — pin scroll + user-gesture gate; synced from live theme 144396058675 (session close)
---
 snippets/boost-infinite-override.liquid | 121 ++++++++++++++++++++++++++++++++
 1 file changed, 121 insertions(+)

diff --git a/snippets/boost-infinite-override.liquid b/snippets/boost-infinite-override.liquid
index 0a81c2b..e98e121 100644
--- a/snippets/boost-infinite-override.liquid
+++ b/snippets/boost-infinite-override.liquid
@@ -46,11 +46,76 @@
   ============================================================================
 {% endcomment %}
 <script>
+/* DW-GUARD-VERSION 20260730-1305 */
 (function () {
   'use strict';
   if (window.__dwBoostInfinite) return;
   window.__dwBoostInfinite = true;
 
+
+  /* DW Boost duplicate-request suppressor (v2 — buffered, body-safe).
+     Landing on a deep-linked ?page=N (N>1) in infinite-scroll mode makes Boost
+     fire its bc-sf-filter/search request TWICE (identical params + identical t=).
+     We can't edit Boost's bundle, so we coalesce: the 1st call hits the network,
+     its body is buffered ONCE, and any identical 2nd call within a short window is
+     served a fresh Response built from that buffer — so both Boost init paths get
+     VALID JSON (no empty-clone crash), from ONE round-trip, resolving together =
+     one clean render. page= is part of the key, so real scroll-appends are never
+     suppressed. Idempotent. */
+  (function () {
+    'use strict';
+    if (window.__dwBoostDedup) return;
+    window.__dwBoostDedup = true;
+
+    // 5s: the real-browser double-fire is Boost re-firing the SAME page (often a
+    // page=1 reset on a deep-linked landing) ~3.3s apart, well past a 1.5s guard.
+    // Distinct pages keep distinct keys, so a wide window never touches real
+    // scroll-appends — only an identical same-page/same-filter repeat is collapsed.
+    var WINDOW_MS = 5000;
+    var recent = Object.create(null);           // key -> { ts, buf: Promise<{body,init}|null> }
+
+    function isSearch(u) { return /bc-sf-filter\/(search|filter)/i.test(String(u)); }
+    function keyOf(u) {
+      try { return String(u).replace(/([?&])t=\d+/, '$1t=').replace(/([?&])event=[^&]*/, '$1event='); }
+      catch (e) { return String(u); }
+    }
+    function fresh(k) { var h = recent[k]; return h && (Date.now() - h.ts) < WINDOW_MS ? h : null; }
+
+    var origFetch = window.fetch;
+    if (typeof origFetch !== 'function') return;
+
+    window.fetch = function (input, init) {
+      var url = (typeof input === 'string') ? input : (input && input.url) || '';
+      if (!isSearch(url)) return origFetch.call(this, input, init);
+
+      var k = keyOf(url), h = fresh(k);
+      if (h && h.buf) {
+        // Duplicate within window: serve a fresh, fully-readable Response from the
+        // buffered bytes. If buffering failed, fall back to a real network fetch.
+        var self = this;
+        return h.buf.then(function (o) {
+          if (!o) return origFetch.call(self, input, init);
+          return new Response(o.body ? o.body.slice(0) : null, o.init);
+        });
+      }
+
+      // First (real) call: fetch, then clone+buffer the body the instant it
+      // resolves (before any consumer reads it), so duplicates get valid data.
+      var real = origFetch.call(this, input, init);
+      recent[k] = {
+        ts: Date.now(),
+        buf: real.then(function (resp) {
+          return resp.clone().arrayBuffer().then(function (ab) {
+            return { body: ab, init: { status: resp.status, statusText: resp.statusText || '', headers: resp.headers } };
+          });
+        }).catch(function () { return null; })
+      };
+      recent[k].buf.then(function () { if (recent[k]) recent[k].ts = Date.now(); });
+      return real;                               // first caller gets the untouched original
+    };
+  })();
+
+
   // 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.
@@ -61,6 +126,62 @@
 
   var TARGET = mode === 'paged' ? 'default' : 'infinite_scroll';
 
+  // DEEP-LINK NORMALIZE (DW, 2026-07-28 — root fix for the deep-page jump/flip):
+  // Infinite scroll has no real "page N" anchor. Landing on ?page=N (N>1) makes
+  // Boost reconcile deep — resetting to page 1, double-firing its search, AND
+  // cascading many unscrolled auto-loads (the "really bad", worse-the-deeper
+  // jump). In infinite mode we strip the page param BEFORE Boost reads the URL,
+  // so it starts clean at page 1 and only appends on real scroll. Paged mode
+  // (numbered pages of 100) keeps page=N — it's meaningful there.
+  if (mode !== 'paged') {
+    try {
+      var _u = new URL(location.href);
+      if (parseInt(_u.searchParams.get('page'), 10) > 1) {
+        _u.searchParams.delete('page');
+        history.replaceState(null, '', _u.pathname + _u.search + _u.hash);
+      }
+    } catch (e) {}
+  }
+
+  // CASCADE GUARD (DW, 2026-07-30 — kills the intermittent deep-page runaway).
+  // Even with page= stripped, a deep-link landing intermittently (a real-browser
+  // timing race, ~1/3, invisible to headless) jumps the viewport down ~3000px on
+  // its own; that puts Boost's scroll sentinel on-screen and fires a BURST of
+  // auto-appends (24 -> 72+ cards) before the shopper has touched anything — the
+  // "nightmare". The URL strip alone doesn't cover it because the trigger is a
+  // native scroll jump, not a stale page param. So in infinite mode we (1) take
+  // scroll restoration off automatic, and (2) pin the viewport to the top until a
+  // GENUINE user gesture, so no second page can auto-load on landing. The instant
+  // the shopper scrolls/wheels/touches, the guard releases and normal infinite
+  // scroll resumes. Paged mode is untouched.
+  if (mode !== 'paged') {
+    try { if ('scrollRestoration' in history) history.scrollRestoration = 'manual'; } catch (e) {}
+    window.__dwUserEngaged = false;
+    var release = function () {
+      if (window.__dwUserEngaged) return;
+      window.__dwUserEngaged = true;
+      ['wheel', 'touchstart', 'keydown', 'pointerdown', 'mousedown'].forEach(function (evt) {
+        try { window.removeEventListener(evt, release, true); } catch (e) {}
+      });
+    };
+    ['wheel', 'touchstart', 'keydown', 'pointerdown', 'mousedown'].forEach(function (evt) {
+      try { window.addEventListener(evt, release, { capture: true, passive: true }); } catch (e) {}
+    });
+    // Hold the top for a short landing window; stop the instant the user engages
+    // or the window elapses, so we never fight a shopper who scrolls immediately.
+    var pinStart = Date.now();
+    var pin = function () {
+      if (window.__dwUserEngaged) return;                 // user took over -> stop
+      if (Date.now() - pinStart > 4000) return;           // safety cap -> stop
+      try { if (window.pageYOffset > 0) window.scrollTo(0, 0); } catch (e) {}
+      (window.requestAnimationFrame || window.setTimeout)(pin, 16);
+    };
+    try {
+      if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', pin);
+      else pin();
+    } catch (e) {}
+  }
+
   // Verified production Boost SD endpoints for this shop (2026-07-13).
   var PROD_API = {
     filterUrl:     'https://services.mybcapps.com/bc-sf-filter/filter',

← 35d1d05 color-palette: hide color-index cards whose image fails to l  ·  back to Dw Theme Boost Fix  ·  Fix new-arrivals scroll-jump: deep ?page=N->paged + anti-col 04227c1 →