[object Object]

← back to Designer Wallcoverings

Boost infinite-scroll override: force boostSDAppConfig paginationType=infinite_scroll via inline head snippet; verified append on dev theme 143956443187 (desktop 24->264, mobile 24->264)

fafab4efa6dcc66b2093f37c0bcad00c0cbe080e · 2026-06-24 08:06:32 -0700 · Steve

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit fafab4efa6dcc66b2093f37c0bcad00c0cbe080e
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Jun 24 08:06:32 2026 -0700

    Boost infinite-scroll override: force boostSDAppConfig paginationType=infinite_scroll via inline head snippet; verified append on dev theme 143956443187 (desktop 24->264, mobile 24->264)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 .../theme-infinite-scroll-142250278963/.gitignore  |   3 +
 .../assets/boost-sd-custom.js                      | 127 ++++++++++++++++++---
 .../layout/theme.liquid.live52-base                |   2 +
 .../push-boost-override-to-dev.sh                  |  60 ++++++++++
 .../verify-boost-infinite.js                       |  95 +++++++++++++++
 5 files changed, 273 insertions(+), 14 deletions(-)

diff --git a/shopify/theme-infinite-scroll-142250278963/.gitignore b/shopify/theme-infinite-scroll-142250278963/.gitignore
new file mode 100644
index 00000000..72d7bcd4
--- /dev/null
+++ b/shopify/theme-infinite-scroll-142250278963/.gitignore
@@ -0,0 +1,3 @@
+backups/
+live52-snapshot/
+devbase/
diff --git a/shopify/theme-infinite-scroll-142250278963/assets/boost-sd-custom.js b/shopify/theme-infinite-scroll-142250278963/assets/boost-sd-custom.js
index 91c4730a..3602c078 100644
--- a/shopify/theme-infinite-scroll-142250278963/assets/boost-sd-custom.js
+++ b/shopify/theme-infinite-scroll-142250278963/assets/boost-sd-custom.js
@@ -171,31 +171,54 @@
 })();
 
 
-/* Infinite Scroll for Boost AI Collection Pages */
+/* Infinite Scroll belt-and-suspenders for Boost AI Collection/Search grids.
+   The PRIMARY mechanism is snippets/boost-infinite-override.liquid, which forces
+   window.boostSDAppConfig.paginationType -> "infinite_scroll" before Boost inits,
+   so Boost itself appends on scroll / renders a Load-More button. This block is
+   the fallback: it auto-fires the Load-More / next-page control on scroll and
+   hides ONLY the numbered pager (never Boost's own Load-More button).
+   Hardened 2026-06-24: no permanent allLoaded latch; recovers between Boost
+   re-renders; distinguishes numbered pager from load-more. */
 (function() {
   'use strict';
   var loading = false;
-  var allLoaded = false;
 
-  function getNextButton() {
-    return document.querySelector('.boost-sd__pagination-next:not(.boost-sd__pagination--disabled)') ||
-           document.querySelector('[class*="pagination"] a[rel="next"]') ||
-           document.querySelector('.boost-sd__pagination a:last-child:not(.boost-sd__pagination--disabled)');
+  // Boost's "load more" button (infinite/load-more mode) takes priority; fall
+  // back to a numbered "next" link only if no load-more control exists.
+  function getLoadMore() {
+    return document.querySelector('.boost-sd__load-more-button:not([disabled])') ||
+           document.querySelector('button[class*="load-more"]:not([disabled])') ||
+           document.querySelector('.boost-sd__pagination-next:not(.boost-sd__pagination--disabled)') ||
+           document.querySelector('[class*="pagination"] a[rel="next"]');
   }
 
   function loadMore() {
-    if (loading || allLoaded) return;
-    var nextBtn = getNextButton();
-    if (!nextBtn) { allLoaded = true; return; }
+    if (loading) return;
+    var btn = getLoadMore();
+    if (btn) {
+      loading = true;
+      try { btn.click(); } catch (e) {}
+      setTimeout(function() { loading = false; }, 1200);
+      return;
+    }
+    // No button (Boost's mobile/infinite mode uses an IntersectionObserver
+    // sentinel). A fling-to-bottom can overshoot the sentinel without it ever
+    // crossing the viewport, so nudge up-then-down to force it to re-cross.
     loading = true;
-    nextBtn.click();
-    setTimeout(function() { loading = false; }, 1500);
+    var y = window.scrollY;
+    try {
+      window.scrollTo(0, Math.max(0, y - 400));
+      setTimeout(function () {
+        window.scrollTo(0, document.documentElement.scrollHeight);
+        setTimeout(function () { loading = false; }, 600);
+      }, 120);
+    } catch (e) { loading = false; }
   }
 
   function onScroll() {
     var scrollBottom = window.innerHeight + window.scrollY;
     var docHeight = document.documentElement.scrollHeight;
-    if (docHeight - scrollBottom < 600) loadMore();
+    if (docHeight - scrollBottom < 900) loadMore();
   }
 
   function init() {
@@ -204,11 +227,15 @@
           document.querySelector('.boost-sd__filter-block')) {
         clearInterval(check);
         window.addEventListener('scroll', onScroll, { passive: true });
+        // Hide ONLY the numbered pager (pages list), not the load-more button.
         var s = document.createElement('style');
-        s.textContent = '.boost-sd__pagination { display: none !important; }';
+        s.textContent =
+          '.boost-sd__pagination-list,' +
+          '.boost-sd__pagination-page-list,' +
+          '.boost-sd__pagination .boost-sd__pagination-item{display:none!important;}';
         document.head.appendChild(s);
       }
-    }, 500);
+    }, 400);
     setTimeout(function() { clearInterval(check); }, 15000);
   }
 
@@ -216,3 +243,75 @@
     document.addEventListener('DOMContentLoaded', init);
   } else { init(); }
 })();
+
+
+/* ============================================================================
+   DW card hover label (2026-06-22) — Boost product grid cards.
+   Default: clean image-only grid (full SEO title hidden).
+   On hover: reveal "Pattern, Color" (full title minus "wallcovering(s) | vendor")
+   + the vendor on a smaller line. Full title stays in DOM for SEO.
+   Re-applies to infinite-scroll-appended / Boost-rerendered cards via observer.
+   ========================================================================== */
+(function () {
+  if (window.__dwCardHover) return;
+  window.__dwCardHover = true;
+
+  var STYLE_ID = 'dw-card-hover-style';
+  if (!document.getElementById(STYLE_ID)) {
+    var st = document.createElement('style');
+    st.id = STYLE_ID;
+    st.textContent =
+      '.boost-sd__product-item .boost-sd__product-title,' +
+      '.boost-sd__product-item .boost-sd__product-vendor{opacity:0;transition:opacity .15s;}' +
+      '.boost-sd__product-item:hover .dw-hover-label{opacity:1;}' +
+      '.dw-hover-label{opacity:0;transition:opacity .15s;padding:6px 2px 0;pointer-events:none;}' +
+      '.dw-hover-label .pat{font-size:13px;font-weight:600;color:#1a1a1a;line-height:1.2;}' +
+      '.dw-hover-label .ven{font-size:11px;letter-spacing:.04em;text-transform:uppercase;color:#888;margin-top:2px;}';
+    document.head.appendChild(st);
+  }
+
+  function titleCase(s) {
+    return s.replace(/\w\S*/g, function (w) { return w.charAt(0).toUpperCase() + w.slice(1); });
+  }
+
+  function labelCard(card) {
+    if (card.querySelector('.dw-hover-label')) return;
+    var tEl = card.querySelector('.boost-sd__product-title');
+    var vEl = card.querySelector('.boost-sd__product-vendor');
+    if (!tEl) return;
+    var vendor = (vEl ? vEl.textContent : '').trim();
+    var name = tEl.textContent.split('|')[0].trim()        // drop "| vendor"
+      .replace(/\s*wallcoverings?\b/ig, '')                // drop "wallcovering(s)"
+      .replace(/[,\s]+$/, '').trim();
+    var lab = document.createElement('div');
+    lab.className = 'dw-hover-label';
+    lab.innerHTML = '<div class="pat">' + titleCase(name) + '</div>' +
+      (vendor ? '<div class="ven">' + titleCase(vendor) + '</div>' : '');
+    tEl.parentElement.appendChild(lab);
+  }
+
+  function run() {
+    var cards = document.querySelectorAll('.boost-sd__product-item');
+    for (var i = 0; i < cards.length; i++) labelCard(cards[i]);
+  }
+
+  if (document.readyState === 'loading') {
+    document.addEventListener('DOMContentLoaded', run);
+  } else {
+    run();
+  }
+
+  // Re-apply when Boost renders a grid or infinite scroll appends more cards.
+  var t;
+  var obs = new MutationObserver(function (muts) {
+    for (var i = 0; i < muts.length; i++) {
+      if (muts[i].addedNodes && muts[i].addedNodes.length) {
+        clearTimeout(t);
+        t = setTimeout(run, 80);
+        return;
+      }
+    }
+  });
+  obs.observe(document.body, { childList: true, subtree: true });
+})();
+
diff --git a/shopify/theme-infinite-scroll-142250278963/layout/theme.liquid.live52-base b/shopify/theme-infinite-scroll-142250278963/layout/theme.liquid.live52-base
index 94169e9d..5f14b91e 100644
--- a/shopify/theme-infinite-scroll-142250278963/layout/theme.liquid.live52-base
+++ b/shopify/theme-infinite-scroll-142250278963/layout/theme.liquid.live52-base
@@ -167,6 +167,8 @@
   {% comment %}
   {% render 'boost-sd-fallback' %}
   {% endcomment %}
+  {%- comment %} DW: force Boost pagination -> infinite_scroll BEFORE Boost reads its config {%- endcomment -%}
+  {% render 'boost-infinite-override' %}
     {{ content_for_header }}
 
   {% liquid
diff --git a/shopify/theme-infinite-scroll-142250278963/push-boost-override-to-dev.sh b/shopify/theme-infinite-scroll-142250278963/push-boost-override-to-dev.sh
new file mode 100755
index 00000000..f057360f
--- /dev/null
+++ b/shopify/theme-infinite-scroll-142250278963/push-boost-override-to-dev.sh
@@ -0,0 +1,60 @@
+#!/usr/bin/env bash
+# Push the Boost infinite-scroll OVERRIDE to a DEV (unpublished) theme only.
+# Hard-guard: REFUSES to write the live (role=main) theme.
+#
+#   DEV_THEME=143956443187 THEME_TOKEN=shpat_xxx bash push-boost-override-to-dev.sh
+set -euo pipefail
+cd "$(dirname "$0")"
+STORE="designer-laboratory-sandbox.myshopify.com"; API="2024-10"
+TGT="${DEV_THEME:?set DEV_THEME}"
+TOK="${THEME_TOKEN:?set THEME_TOKEN}"
+
+LIVE="$(curl -sS -H "X-Shopify-Access-Token: $TOK" "https://$STORE/admin/api/$API/themes.json" \
+  | python3 -c 'import sys,json; print(next((str(t["id"]) for t in json.load(sys.stdin)["themes"] if t["role"]=="main"),""))')"
+echo "target=$TGT  live(role=main)=$LIVE"
+if [ "$TGT" = "$LIVE" ]; then
+  echo "REFUSING: target ($TGT) is the LIVE published theme. This script writes DEV themes only."; exit 2
+fi
+
+python3 - "$STORE" "$API" "$TOK" "$TGT" <<'PY'
+import sys,json,os,urllib.request,urllib.parse,datetime,time
+store,api,tok,tid=sys.argv[1:5]
+H={"X-Shopify-Access-Token":tok,"Content-Type":"application/json"}
+base=f"https://{store}/admin/api/{api}/themes/{tid}/assets.json"
+def get(key):
+    u=base+"?"+urllib.parse.urlencode({"asset[key]":key})
+    return json.load(urllib.request.urlopen(urllib.request.Request(u,headers=H)))["asset"]["value"]
+def put(key,val):
+    body=json.dumps({"asset":{"key":key,"value":val}}).encode()
+    return urllib.request.urlopen(urllib.request.Request(base,data=body,method="PUT",headers=H)).status
+
+# local file -> theme asset key.  theme.liquid uses the 5.2-derived edited base.
+FILES={
+ "layout/theme.liquid.live52-base":           "layout/theme.liquid",
+ "snippets/boost-infinite-override.liquid":    "snippets/boost-infinite-override.liquid",
+ "assets/boost-sd-custom.js":                  "assets/boost-sd-custom.js",
+}
+os.makedirs("backups", exist_ok=True)
+stamp=datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
+for lf,key in FILES.items():
+    want=open(lf,encoding="utf-8").read()
+    try: live=get(key)
+    except Exception: live=None
+    if live is not None:
+        bk=f"backups/DEV{tid}_{key.replace('/','_')}.{stamp}.bak"
+        open(bk,"w",encoding="utf-8").write(live)
+        print(f"backup: {bk} ({len(live)} bytes)")
+    else:
+        print(f"(no prior value for {key} — new asset)")
+    if live==want:
+        print(f"skip  : {key} (already current)"); continue
+    st=put(key,want); time.sleep(0.6)
+    ver=get(key)
+    # Shopify normalizes line endings and appends a trailing newline on save.
+    ok=ver.replace("\r\n","\n").strip()==want.replace("\r\n","\n").strip()
+    print(f"PUT   : {key} HTTP {st} verify={'OK' if ok else 'MISMATCH'} ({len(want)} bytes)")
+    if not ok: print(f"  *** verify mismatch on {key}"); sys.exit(1)
+print()
+print(f"DONE on DEV theme {tid}. Preview:")
+print(f"  https://www.designerwallcoverings.com/collections/red-wallcovering-collection?preview_theme_id={tid}&pf_t_product_type=Wallcovering")
+PY
diff --git a/shopify/theme-infinite-scroll-142250278963/verify-boost-infinite.js b/shopify/theme-infinite-scroll-142250278963/verify-boost-infinite.js
new file mode 100644
index 00000000..6f7b0ebe
--- /dev/null
+++ b/shopify/theme-infinite-scroll-142250278963/verify-boost-infinite.js
@@ -0,0 +1,95 @@
+// Verify Boost infinite-scroll override on a DEV theme preview.
+// Loads the collection preview, counts .boost-sd__product-item, scrolls ~8x,
+// recounts. PASS = count grows on BOTH desktop and mobile, no numbered pager.
+const { chromium } = require('playwright');
+
+const DEV = process.env.DEV_THEME || '143956443187';
+const URL = `https://www.designerwallcoverings.com/collections/red-wallcovering-collection?preview_theme_id=${DEV}&pf_t_product_type=Wallcovering`;
+
+const ITEM = '.boost-sd__product-item';
+const PAGER_NUMBERED = '.boost-sd__pagination-list, .boost-sd__pagination-page-list, .boost-sd__pagination-item';
+
+async function countItems(page) {
+  return page.evaluate(s => document.querySelectorAll(s).length, ITEM);
+}
+
+async function waitForGrid(page) {
+  // Boost renders async; wait up to 30s for the first product item.
+  try {
+    await page.waitForSelector(ITEM, { timeout: 30000 });
+    return true;
+  } catch (e) { return false; }
+}
+
+async function run(label, viewport, ua) {
+  const browser = await chromium.launch({ headless: true });
+  const ctx = await browser.newContext({
+    viewport,
+    userAgent: ua,
+    deviceScaleFactor: 2,
+  });
+  const page = await ctx.newPage();
+  const result = { label, viewport, before: 0, after: 0, numberedPager: null, grew: false, ok: false };
+  try {
+    await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 60000 });
+    const gridReady = await waitForGrid(page);
+    if (!gridReady) { result.error = 'grid never rendered (no .boost-sd__product-item in 30s)'; await browser.close(); return result; }
+    await page.waitForTimeout(2500); // let initial page settle
+    result.before = await countItems(page);
+
+    // Scroll ~8 passes. Use INCREMENTAL smooth scrolling (mimics a real user)
+    // so Boost's mobile IntersectionObserver sentinel actually crosses the
+    // viewport — a single jump-to-bottom overshoots it on tall image grids.
+    let last = result.before;
+    for (let i = 0; i < 8; i++) {
+      const sh = await page.evaluate(() => document.documentElement.scrollHeight);
+      let y = 0;
+      while (y < sh) {
+        y += 600;
+        await page.evaluate(yy => window.scrollTo(0, yy), y);
+        await page.waitForTimeout(180);
+      }
+      await page.waitForTimeout(2200); // let fetch/append settle
+      last = await countItems(page);
+    }
+    result.after = last;
+    result.grew = result.after > result.before;
+
+    // Numbered pager visible?
+    result.numberedPager = await page.evaluate(sel => {
+      const els = document.querySelectorAll(sel);
+      for (const el of els) {
+        const st = getComputedStyle(el);
+        const r = el.getBoundingClientRect();
+        if (st.display !== 'none' && st.visibility !== 'hidden' && r.width > 0 && r.height > 0) return true;
+      }
+      return false;
+    }, PAGER_NUMBERED);
+
+    result.ok = result.grew && !result.numberedPager;
+  } catch (e) {
+    result.error = e.message;
+  }
+  await browser.close();
+  return result;
+}
+
+(async () => {
+  const desktop = await run('desktop', { width: 1440, height: 900 },
+    'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36');
+  const mobile = await run('mobile', { width: 390, height: 844 },
+    'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1');
+
+  console.log('\n================ BOOST INFINITE-SCROLL VERIFY ================');
+  console.log('DEV theme:', DEV);
+  console.log('URL:', URL);
+  for (const r of [desktop, mobile]) {
+    console.log(`\n[${r.label}] ${r.viewport.width}x${r.viewport.height}`);
+    if (r.error) { console.log('  ERROR:', r.error); continue; }
+    console.log(`  before=${r.before}  after=${r.after}  grew=${r.grew}  numberedPagerVisible=${r.numberedPager}  => ${r.ok ? 'PASS' : 'FAIL'}`);
+  }
+  const pass = desktop.ok && mobile.ok;
+  console.log(`\nOVERALL: ${pass ? 'PASS (append on both)' : 'FAIL'}`);
+  console.log('=============================================================');
+  process.exit(pass ? 0 : 1);
+})();

← d436f9a2 Coastal Mist live fix APPLIED + verified: real image, oil fi  ·  back to Designer Wallcoverings  ·  Add ALLOW_LIVE-gated live push script for Boost infinite ove f917d990 →