← 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
A shopify/theme-infinite-scroll-142250278963/.gitignoreM shopify/theme-infinite-scroll-142250278963/assets/boost-sd-custom.jsM shopify/theme-infinite-scroll-142250278963/layout/theme.liquid.live52-baseA shopify/theme-infinite-scroll-142250278963/push-boost-override-to-dev.shA shopify/theme-infinite-scroll-142250278963/verify-boost-infinite.js
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 →