← 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
M snippets/boost-infinite-override.liquid
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 →