[object Object]

← back to Designer Wallcoverings

auto-save: 2026-06-26T16:04:29 (16 files) — pending-approval/boost-filter-consolidation-2026-06-25 shopify/enhancements/ARCHITECTURE.md shopify/enhancements/COLLECTION-LIQUID-INTEGRATION.liquid shopify/enhancements/INFINITE-SCROLL-IMPLEMENTATION.md shopify/enhancements/QUICK-REFERENCE.txt

ca8adbdfac27695d95391fcfc5e038ce62b31e1e · 2026-06-26 16:04:34 -0700 · Steve Abrams

Files touched

Diff

commit ca8adbdfac27695d95391fcfc5e038ce62b31e1e
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jun 26 16:04:34 2026 -0700

    auto-save: 2026-06-26T16:04:29 (16 files) — pending-approval/boost-filter-consolidation-2026-06-25 shopify/enhancements/ARCHITECTURE.md shopify/enhancements/COLLECTION-LIQUID-INTEGRATION.liquid shopify/enhancements/INFINITE-SCROLL-IMPLEMENTATION.md shopify/enhancements/QUICK-REFERENCE.txt
---
 shopify/enhancements/ARCHITECTURE.md               |  19 ++-
 .../COLLECTION-LIQUID-INTEGRATION.liquid           |  15 +-
 .../enhancements/INFINITE-SCROLL-IMPLEMENTATION.md |  24 +++-
 shopify/enhancements/QUICK-REFERENCE.txt           |  54 +++++--
 shopify/enhancements/README.md                     |  56 ++++++--
 shopify/enhancements/VISUAL-EXPLANATION.md         |  15 +-
 shopify/enhancements/WHAT-SHIPPED.md               |  60 ++++++--
 shopify/enhancements/deploy-infinite-scroll.sh     |  18 ++-
 shopify/enhancements/infinite-scroll-observer.js   |  59 +++++++-
 .../enhancements/infinite-scroll-test-harness.html |  60 ++++++--
 shopify/scripts/anna-french-scraper.js             |   1 -
 .../assets/boost-sd-custom.js                      |  77 ++++++++--
 .../snippets/boost-infinite-override.liquid        |  20 ++-
 .../verify-boost-infinite.js                       | 155 +++++++++++++++++----
 14 files changed, 529 insertions(+), 104 deletions(-)

diff --git a/shopify/enhancements/ARCHITECTURE.md b/shopify/enhancements/ARCHITECTURE.md
index 594b7f89..c02ae92e 100644
--- a/shopify/enhancements/ARCHITECTURE.md
+++ b/shopify/enhancements/ARCHITECTURE.md
@@ -1,5 +1,19 @@
 # Infinite Scroll Architecture — Why No Wrapper Divs
 
+> **Scope:** this document describes the **standalone `infinite-scroll-observer.js`**
+> — the mutation-observer / direct-append approach that is the **secondary
+> fallback / reference** in this folder, built for a *native* Shopify Liquid
+> grid (`[data-collection-products]`).
+>
+> It is **not** the live production mechanism. The live collection/search grids
+> are rendered by **Boost AI Search & Discovery** (`.boost-sd__product-item`),
+> and infinite scroll there is forced by the **Boost config override**
+> (`theme-infinite-scroll-142250278963/snippets/boost-infinite-override.liquid`
+> + `…/assets/boost-sd-custom.js`), which rewrites
+> `window.boostSDAppConfig.paginationType → "infinite_scroll"` before Boost's
+> bundle reads it. See `WHAT-SHIPPED.md` for the live architecture. The
+> wrapper-vs-no-wrapper analysis below applies to the native-grid fallback.
+
 ## Problem Statement
 
 **Previous Approach Failed:** Wrapping the grid in a div to manage infinite scroll breaks the Shopify layout because it changes the DOM flow that the parent CSS expects.
@@ -523,4 +537,7 @@ The **mutation observer + direct append** approach avoids wrapper problems by:
 4. **Minimal overhead** — Just watches and appends, no framework
 5. **Testable & debuggable** — Simple state, easy to inspect in console
 
-This architecture is **production-ready** and tested on multiple Shopify themes.
+This architecture is a **reference / fallback** for native-Liquid grids; it is
+tested against the local harness but is **not** the live production mechanism
+(the Boost config override is — see the scope note at the top of this file and
+`WHAT-SHIPPED.md`).
diff --git a/shopify/enhancements/COLLECTION-LIQUID-INTEGRATION.liquid b/shopify/enhancements/COLLECTION-LIQUID-INTEGRATION.liquid
index 226be10a..e85a1c14 100644
--- a/shopify/enhancements/COLLECTION-LIQUID-INTEGRATION.liquid
+++ b/shopify/enhancements/COLLECTION-LIQUID-INTEGRATION.liquid
@@ -1,8 +1,21 @@
 {%- comment -%}
   Infinite Scroll Integration Guide for collection.liquid
 
+  ──────────────────────────────────────────────────────────────────────────
+  STATUS: FALLBACK / REFERENCE PATH — NOT the live storefront mechanism.
+
+  The SHIPPED infinite scroll on the live DW storefront is the Boost SD
+  override (theme-infinite-scroll-142250278963/ — boost-sd-custom.js +
+  snippets/boost-infinite-override.liquid), which forces Boost's own native
+  `infinite_scroll` pagination. Do NOT wire this generic observer into a
+  Boost-powered collection — the two would both manage the grid and fight.
+
+  This guide is for plain-Liquid themes / DW sister-site storefronts that do
+  NOT run Boost. Keep it as a portable reference.
+  ──────────────────────────────────────────────────────────────────────────
+
   This file shows exactly where and how to add infinite scroll
-  to your Shopify collection template.
+  to a (non-Boost) Shopify collection template.
 
   No wrapper divs required — script works with existing grid.
 {%- endcomment -%}
diff --git a/shopify/enhancements/INFINITE-SCROLL-IMPLEMENTATION.md b/shopify/enhancements/INFINITE-SCROLL-IMPLEMENTATION.md
index dc5272ea..9b0d54e4 100644
--- a/shopify/enhancements/INFINITE-SCROLL-IMPLEMENTATION.md
+++ b/shopify/enhancements/INFINITE-SCROLL-IMPLEMENTATION.md
@@ -1,8 +1,28 @@
 # Infinite Scroll for Designer Wallcoverings Collections
 
+> ## ⚠️ This guide is for the FALLBACK observer, not the live mechanism
+>
+> The **live** infinite scroll on designerwallcoverings.com is the **Boost
+> config override** — `theme-infinite-scroll-142250278963/snippets/boost-infinite-override.liquid`
+> (PRIMARY) + `…/assets/boost-sd-custom.js` (fallback) — because the live
+> collection/search grids are rendered by **Boost AI Search & Discovery**
+> (`.boost-sd__product-item`). To deploy / change the live behaviour, use
+> `push-boost-override-to-dev.sh` / `push-boost-override-to-LIVE.sh` and verify
+> with `verify-boost-infinite.js`. See `WHAT-SHIPPED.md`.
+>
+> This document covers the **standalone `infinite-scroll-observer.js`** — the
+> secondary fallback / reference for a *native* Shopify Liquid grid
+> (`[data-collection-products]`). The deploy steps, `deploy-infinite-scroll.sh`,
+> and `<script src="…observer.js">` tag below apply **only** to that native-grid
+> scenario, NOT to the live Boost grids.
+
 ## Overview
 
-This implementation adds **automatic infinite scroll** to collection pages without using wrapper divs that break the existing Shopify grid layout.
+This document covers the **standalone observer** (the fallback / reference in
+this folder), which adds **automatic infinite scroll** to *native-Liquid*
+collection grids without using wrapper divs that break the existing Shopify grid
+layout. (For the live Boost grids the mechanism is the Boost config override —
+see the banner above.)
 
 **Key Features:**
 - ✅ **No DOM structure changes** — Uses mutation observer + fetch + direct DOM append
@@ -65,7 +85,7 @@ products.forEach(product => {
 
 **Size:** ~6 KB minified
 
-**How to Deploy to Live Theme:**
+**How to Deploy (native-Liquid collection surfaces only — NOT the live Boost grids):**
 
 Add to the bottom of `collection.liquid` (before `</section>`):
 
diff --git a/shopify/enhancements/QUICK-REFERENCE.txt b/shopify/enhancements/QUICK-REFERENCE.txt
index ba6b5257..358092ce 100644
--- a/shopify/enhancements/QUICK-REFERENCE.txt
+++ b/shopify/enhancements/QUICK-REFERENCE.txt
@@ -2,13 +2,35 @@
   INFINITE SCROLL FOR DESIGNER WALLCOVERINGS — QUICK REFERENCE
 ================================================================================
 
+  ⚠️  WHAT IS LIVE (read first)
+  ----------------------------------------------------------------------------
+  The PRODUCTION infinite scroll is the BOOST CONFIG OVERRIDE, not the
+  standalone observer documented in this file:
+
+    LIVE:  theme-infinite-scroll-142250278963/snippets/boost-infinite-override.liquid  (PRIMARY)
+         + theme-infinite-scroll-142250278963/assets/boost-sd-custom.js               (fallback)
+    DEPLOY: push-boost-override-to-dev.sh / push-boost-override-to-LIVE.sh
+    VERIFY: verify-boost-infinite.js  (Playwright)
+    SHIPPED: 2026-06-24, commit fe289af
+
+  Live collection/search grids are rendered by Boost AI Search & Discovery
+  (.boost-sd__product-item). Infinite scroll is forced by rewriting
+  window.boostSDAppConfig.paginationType -> "infinite_scroll" before Boost's
+  bundle reads it. Boost exposes no API for this; the config-patch is the only
+  reliable route.
+
+  THIS FILE describes the standalone infinite-scroll-observer.js — a SECONDARY
+  FALLBACK / REFERENCE for NATIVE Shopify Liquid grids ([data-collection-products]),
+  which the live Boost grids do NOT use. It is NOT deployed to production.
+  ============================================================================
+
 PROJECT LOCATION:
   ~/Projects/Designer-Wallcoverings/shopify/enhancements/
 
-MAIN FILES:
-  ✓ infinite-scroll-observer.js          — Production script (6 KB)
+MAIN FILES (standalone observer — secondary fallback / reference):
+  ✓ infinite-scroll-observer.js          — Fallback/reference script (6 KB)
   ✓ infinite-scroll-test-harness.html    — Local test (no server)
-  ✓ deploy-infinite-scroll.sh            — One-command deployment
+  ✓ deploy-infinite-scroll.sh            — Deploy for native-grid surfaces only
 
 DOCUMENTATION:
   ✓ README.md                            — Quick start (5 min read)
@@ -159,7 +181,8 @@ parent's CSS grid layout. This approach instead:
   5. Layout structure unchanged
   6. Sidebar alignment preserved
 
-That's it. Simple, robust, production-ready.
+That's it. Simple and robust — a reference / fallback for native-Liquid grids
+(the live Boost grids use the Boost config override; see the banner up top).
 
 See ARCHITECTURE.md for technical deep-dive.
 
@@ -228,7 +251,12 @@ All files in: ~/Projects/Designer-Wallcoverings/shopify/enhancements/
 GIT COMMITS
 ================================================================================
 
-Latest commits:
+LIVE mechanism — Boost config override (the actual production ship):
+  fe289af  theme-infinite-scroll: Boost AI pagination override
+           (force infinite_scroll via boostSDAppConfig patch) + hardened
+           boost-sd-custom fallback, Playwright verify, dev/LIVE push scripts
+
+This folder — standalone observer (secondary fallback / reference):
   6e8a95d3 Add final summary document for infinite scroll delivery
   621498bf Add comprehensive infinite scroll documentation
   c218e7fd Add infinite scroll implementation for collections
@@ -239,16 +267,18 @@ All committed to: ~/Projects/Designer-Wallcoverings
 STATUS
 ================================================================================
 
-✅ Script written & tested
+LIVE (production): Boost config override (boost-infinite-override.liquid +
+boost-sd-custom.js) — shipped 2026-06-24 (fe289af), deployed via
+push-boost-override-to-*.sh, verified with verify-boost-infinite.js.
+
+This file's standalone observer (secondary fallback / reference):
+✅ Script written & tested (native-Liquid grid harness)
 ✅ Test harness working (no server needed)
-✅ 5 architecture documents
+✅ Architecture documents
 ✅ Deployment script automated
 ✅ Integration guide provided
 ✅ Committed to git
-✅ Production ready
-
-Next: Deploy to dev theme when you approve → A/B test → Production rollout
-
-Ready for deployment at your go/no-go decision.
+⛔ NOT deployed to the live Boost grids (by design — Boost override is live)
+♻️ Reserved for any future non-Boost (native Liquid) collection surface
 
 ================================================================================
diff --git a/shopify/enhancements/README.md b/shopify/enhancements/README.md
index 864c7c5b..cb1f873f 100644
--- a/shopify/enhancements/README.md
+++ b/shopify/enhancements/README.md
@@ -1,14 +1,42 @@
 # Infinite Scroll for Designer Wallcoverings Collections
 
+> ## ⚠️ STATUS — read this first
+>
+> **What is LIVE:** the **Boost config override** —
+> `theme-infinite-scroll-142250278963/snippets/boost-infinite-override.liquid`
+> (PRIMARY) + `theme-infinite-scroll-142250278963/assets/boost-sd-custom.js`
+> (belt-and-suspenders fallback). Shipped 2026-06-24 (commit `fe289af`).
+> The live collection/search grids are rendered by **Boost AI Search &
+> Discovery** (`.boost-sd__product-item`), so infinite scroll is forced by
+> rewriting Boost's runtime config (`window.boostSDAppConfig.paginationType →
+> "infinite_scroll"`) before Boost's bundle reads it. Boost exposes no API for
+> this, which is why the config-patch is the only reliable route. Deploy with
+> `push-boost-override-to-dev.sh` / `push-boost-override-to-LIVE.sh`; verify
+> with `verify-boost-infinite.js` (Playwright).
+>
+> **What this `enhancements/` folder is:** the standalone
+> `infinite-scroll-observer.js` mutation-observer approach — now a **secondary
+> fallback / reference implementation**. It targets a *native* Shopify Liquid
+> grid (`[data-collection-products]`), which the live Boost-powered collections
+> do **not** use, so it is NOT what runs in production. Keep it for any
+> non-Boost (native Liquid) collection surface. Everything below — the
+> `deploy-infinite-scroll.sh` script and the `<script src="…observer.js">`
+> tag — applies only to that native-grid fallback scenario, not to the live
+> Boost grids.
+
 ## 🎯 What This Does
 
-Adds **automatic infinite scroll** to collection pages that:
+The standalone observer (this folder's fallback/reference) adds **automatic
+infinite scroll** to *native-Liquid* collection grids that:
 - ✅ Loads more products when user scrolls to bottom
 - ✅ Works WITHOUT breaking the existing grid layout
 - ✅ Maintains pagination as a fallback
 - ✅ Animated fade-in for new products
 - ✅ ~6 KB minified, zero dependencies
 
+> For the LIVE Boost grids, the mechanism is the Boost config override, not
+> this script — see the STATUS banner above.
+
 ## 🚫 What It Does NOT Do
 
 - ❌ Use wrapper divs (the source of layout breaks)
@@ -316,26 +344,36 @@ All logs have `[InfiniteScroll]` prefix. Filter in DevTools console.
 ## 📝 Commit Info
 
 ```
-commit c218e7fd
-Add infinite scroll implementation for collections 
-(mutation observer approach, no wrapper divs)
+# LIVE mechanism (Boost config override) — what actually ships infinite scroll:
+fe289af  theme-infinite-scroll: Boost AI pagination override
+         (force infinite_scroll via boostSDAppConfig patch) + hardened
+         boost-sd-custom fallback, Playwright verify
+
+# This folder's standalone observer (secondary fallback / reference):
+c218e7fd Add infinite scroll implementation for collections
+         (mutation observer approach, no wrapper divs)
 ```
 
 ---
 
 ## ✅ Status
 
-- [x] Script written & tested
+**LIVE:** Boost config override (`boost-infinite-override.liquid` +
+`boost-sd-custom.js`) — shipped, deployed via the `push-boost-override-to-*.sh`
+scripts and verified with `verify-boost-infinite.js`.
+
+**This folder (standalone observer — secondary fallback / reference):**
+
+- [x] Script written & tested (against a native-Liquid grid harness)
 - [x] Test harness working
 - [x] Architecture documented
 - [x] Deployment script created
 - [x] Integration guide written
 - [x] Committed to git
-- [ ] Deploy to dev theme
-- [ ] A/B test with users
-- [ ] Launch to production
+- [ ] NOT deployed to the live Boost grids (by design — Boost override is live)
+- [ ] Reserve for any future non-Boost (native Liquid) collection surface
 
-Next step: Run `./deploy-infinite-scroll.sh --dev` when ready.
+Next step (native-grid surfaces only): run `./deploy-infinite-scroll.sh --dev`.
 
 ---
 
diff --git a/shopify/enhancements/VISUAL-EXPLANATION.md b/shopify/enhancements/VISUAL-EXPLANATION.md
index 2f57e652..bf140c1a 100644
--- a/shopify/enhancements/VISUAL-EXPLANATION.md
+++ b/shopify/enhancements/VISUAL-EXPLANATION.md
@@ -1,5 +1,18 @@
 # Visual Explanation: Infinite Scroll Without Wrapper Divs
 
+> **Scope:** these diagrams illustrate the **standalone `infinite-scroll-observer.js`**
+> — the mutation-observer / direct-append approach that is the **secondary
+> fallback / reference** in this folder, drawn for a *native* Shopify Liquid
+> grid (`[data-collection-products]`).
+>
+> They do **not** depict the live production mechanism. On
+> designerwallcoverings.com the collection/search grids are rendered by **Boost
+> AI Search & Discovery** (`.boost-sd__product-item`), and infinite scroll is
+> the **Boost config override** — `boost-infinite-override.liquid` rewrites
+> `window.boostSDAppConfig.paginationType → "infinite_scroll"` before Boost's
+> bundle initializes, with `boost-sd-custom.js` as a belt-and-suspenders
+> fallback. See `WHAT-SHIPPED.md` for the live mechanism.
+
 ## The Problem: Wrapper Breaks Layout
 
 ```
@@ -541,6 +554,6 @@ This approach = Best of both worlds:
 | **Problem** | Collections need infinite scroll | Users hate clicking pagination |
 | **Solution** | Mutation observer + direct append | No wrapper to break layout |
 | **How** | Find grid → Fetch next page → Parse HTML → Append products → Repeat | Clean, simple, robust |
-| **Result** | Works perfectly, no layout breaks, fallback always available | Production ready |
+| **Result** | Works perfectly, no layout breaks, fallback always available | Reference / fallback for native grids (live = Boost override) |
 
 **That's it!** 🎉
diff --git a/shopify/enhancements/WHAT-SHIPPED.md b/shopify/enhancements/WHAT-SHIPPED.md
index 171f1515..41f28e2a 100644
--- a/shopify/enhancements/WHAT-SHIPPED.md
+++ b/shopify/enhancements/WHAT-SHIPPED.md
@@ -1,15 +1,42 @@
 # What Shipped: Infinite Scroll for Designer Wallcoverings
 
+> ## ⚠️ What ACTUALLY shipped to production
+>
+> The live infinite scroll on designerwallcoverings.com is the **Boost config
+> override**, NOT the standalone observer described in the rest of this file.
+>
+> | | LIVE (production) | This folder (`enhancements/`) |
+> |---|---|---|
+> | **Mechanism** | Boost config override | Standalone mutation observer |
+> | **Files** | `theme-infinite-scroll-142250278963/snippets/boost-infinite-override.liquid` (PRIMARY) + `…/assets/boost-sd-custom.js` (fallback) | `infinite-scroll-observer.js` |
+> | **Targets** | Boost grids (`.boost-sd__product-item`) | Native Liquid grid (`[data-collection-products]`) |
+> | **How** | Rewrites `window.boostSDAppConfig.paginationType → "infinite_scroll"` before Boost's bundle reads it | Fetch next page → DOMParser → append to existing grid |
+> | **Deploy** | `push-boost-override-to-dev.sh` / `push-boost-override-to-LIVE.sh` | `deploy-infinite-scroll.sh` |
+> | **Verify** | `verify-boost-infinite.js` (Playwright) | local test harness |
+> | **Status** | **SHIPPED** 2026-06-24, commit `fe289af` | reference / fallback, NOT live |
+>
+> **Why the observer is not live:** the live collection/search grids are
+> rendered by Boost AI Search & Discovery, which uses its own markup
+> (`.boost-sd__product-item`) and exposes no API to switch pagination to
+> infinite scroll. The native-grid observer never finds a
+> `[data-collection-products]` element on those pages, so the only reliable
+> route is patching Boost's runtime config object — that is the Boost override.
+> Keep the observer as a reference / fallback for any non-Boost (native Liquid)
+> collection surface.
+>
+> Everything below documents that secondary observer implementation.
+
 ## Summary
 
-Complete **infinite scroll implementation** for designerwallcoverings.com collections that:
+Complete **standalone infinite scroll implementation** (the secondary /
+fallback reference in this folder) for *native-Liquid* collection grids that:
 
 ✅ **Works WITHOUT wrapper divs** — Uses mutation observer + direct DOM append
 ✅ **Never breaks grid layout** — CSS grid stays intact, sidebar aligned
 ✅ **Maintains pagination fallback** — Manual pagination still clickable
 ✅ **Fully tested** — Working test harness with mock data
-✅ **Production ready** — ~6 KB minified, zero dependencies
-✅ **Well documented** — 5 architecture docs + deployment guide
+✅ **Reference / fallback only** — ~6 KB minified, zero dependencies (NOT the live mechanism — see banner above)
+✅ **Well documented** — architecture docs + deployment guide
 
 ---
 
@@ -258,14 +285,20 @@ No crashes, no broken pages.
 ## Git Commits
 
 ```
-621498bf Add comprehensive infinite scroll documentation 
+# LIVE mechanism — Boost config override (the actual production ship):
+fe289af  theme-infinite-scroll: Boost AI pagination override
+          (force infinite_scroll via boostSDAppConfig patch) + hardened
+          boost-sd-custom fallback, Playwright verify, dev/LIVE push scripts
+
+# This folder — standalone observer (secondary fallback / reference):
+621498bf Add comprehensive infinite scroll documentation
           (architecture, visual explanations, integration guides)
 
-c218e7fd Add infinite scroll implementation for collections 
+c218e7fd Add infinite scroll implementation for collections
           (mutation observer approach, no wrapper divs)
 ```
 
-Both commits in repo `~/Projects/Designer-Wallcoverings`.
+All commits in repo `~/Projects/Designer-Wallcoverings`.
 
 ---
 
@@ -367,13 +400,20 @@ window.DWInfiniteScroll.CONFIG
 
 ## Status
 
-- ✅ Script written & tested
+**LIVE (production):** Boost config override — `boost-infinite-override.liquid`
++ `boost-sd-custom.js`. Shipped 2026-06-24 (`fe289af`), deployed via the
+`push-boost-override-to-*.sh` scripts, verified with `verify-boost-infinite.js`.
+
+**This folder — standalone observer (secondary fallback / reference):**
+
+- ✅ Script written & tested (native-Liquid grid harness)
 - ✅ Test harness working
-- ✅ Architecture documented (5 docs)
+- ✅ Architecture documented
 - ✅ Deployment script created
 - ✅ Integration guide written
 - ✅ Committed to git
-- ⏳ Ready for: Deploy to dev theme → A/B test → Production rollout
+- ⛔ NOT deployed to the live Boost grids (by design — Boost override is live)
+- ♻️ Held in reserve for any future non-Boost (native Liquid) collection surface
 
 ---
 
@@ -390,7 +430,7 @@ window.DWInfiniteScroll.CONFIG
 | **5 documentation files** | Clear explanation for any team member |
 | **Deployment script** | One-command minify + upload to Shopify |
 | **Error handling** | Graceful degradation, pagination fallback |
-| **Production ready** | Tested, documented, committed to git |
+| **Reference / fallback** | Tested, documented, committed to git (live mechanism is the Boost override) |
 
 **Total delivery:** Complete, tested, documented infinite scroll system ready for production deployment.
 
diff --git a/shopify/enhancements/deploy-infinite-scroll.sh b/shopify/enhancements/deploy-infinite-scroll.sh
index d15920b6..3f68b8a0 100644
--- a/shopify/enhancements/deploy-infinite-scroll.sh
+++ b/shopify/enhancements/deploy-infinite-scroll.sh
@@ -1,13 +1,27 @@
 #!/bin/bash
 #
-# Deploy Infinite Scroll to Shopify Theme
+# Deploy the GENERIC (non-Boost) Infinite Scroll asset to a Shopify Theme
+#
+# ----------------------------------------------------------------------------
+# STATUS: FALLBACK / REFERENCE PATH — NOT the live deploy path.
+#
+# The live storefront's infinite scroll is the Boost SD override, which has
+# its OWN deploy scripts under theme-infinite-scroll-142250278963/
+# (push-boost-override-to-dev.sh / push-boost-override-to-LIVE.sh). Use those
+# for the shipped mechanism. This script only pushes the standalone generic
+# observer (infinite-scroll-observer.js) and is intended for non-Boost themes
+# / prototypes. Pushing it onto the live Boost theme would conflict.
+#
+# Steve-gated: do NOT run --prod (or any LIVE push) without explicit sign-off.
+# `--verify-only` is the safe way to dry-run this.
+# ----------------------------------------------------------------------------
 #
 # Usage:
 #   ./deploy-infinite-scroll.sh [--dev|--prod] [--no-minify] [--verify-only]
 #
 # Options:
 #   --dev              Deploy to dev theme (default)
-#   --prod             Deploy to PRODUCTION (careful!)
+#   --prod             Deploy to PRODUCTION (Steve-gated — do not run unasked)
 #   --no-minify        Don't minify, deploy as-is
 #   --verify-only      Check deployment would succeed, don't actually deploy
 #
diff --git a/shopify/enhancements/infinite-scroll-observer.js b/shopify/enhancements/infinite-scroll-observer.js
index 1f4f5e87..2418f29f 100644
--- a/shopify/enhancements/infinite-scroll-observer.js
+++ b/shopify/enhancements/infinite-scroll-observer.js
@@ -1,6 +1,23 @@
 /**
  * Infinite Scroll for Designer Wallcoverings Collections
  *
+ * ┌──────────────────────────────────────────────────────────────────────┐
+ * │  STATUS: FALLBACK / REFERENCE PATH — NOT the live mechanism.           │
+ * │                                                                        │
+ * │  The SHIPPED, live infinite scroll on the storefront is the Boost SD   │
+ * │  override (theme-infinite-scroll-142250278963/ — boost-sd-custom.js +  │
+ * │  snippets/boost-infinite-override.liquid), which forces Boost's own    │
+ * │  native `infinite_scroll` pagination via the Boost SDK config. That is │
+ * │  what runs on collection pages today.                                  │
+ * │                                                                        │
+ * │  THIS file is the generic, framework-agnostic approach for any theme   │
+ * │  or page that does NOT run Boost (plain Liquid collection grids, the   │
+ * │  DW sister-site storefronts, prototypes). Keep it as a portable        │
+ * │  reference / fallback — do not wire it into a Boost-powered collection │
+ * │  (the two would fight over the grid). See the same-named markdown docs │
+ * │  in this folder for the full comparison.                               │
+ * └──────────────────────────────────────────────────────────────────────┘
+ *
  * APPROACH: Mutation Observer + Fetch + Pure DOM Append
  * ✅ NO wrapper divs — respects existing Shopify grid layout
  * ✅ Detects grid mutations (new products appended)
@@ -33,7 +50,8 @@
     observer: null,
     intersectionObserver: null,
     gridElement: null,
-    paginationElement: null
+    paginationElement: null,
+    sentinelElement: null
   };
 
   // ============ LOGGING ============
@@ -102,12 +120,18 @@
         animation: dw-fade-in 0.4s ease forwards;
       }
 
-      /* Loading indicator */
+      /* Loading indicator (span full width inside a CSS grid) */
       .dw-infinite-loading {
         display: flex;
         justify-content: center;
         padding: 32px;
         gap: 6px;
+        grid-column: 1 / -1;
+      }
+
+      /* Sentinel must not consume a visible grid cell */
+      #dw-infinite-scroll-sentinel {
+        grid-column: 1 / -1;
       }
 
       .dw-infinite-loading-dot {
@@ -192,12 +216,23 @@
       return 0;
     }
 
+    // Keep the sentinel pinned to the end of the grid: insert new products
+    // before it, not after. Otherwise the sentinel ends up mid-grid and the
+    // IntersectionObserver stops firing after the first page.
+    const anchor = (state.sentinelElement && state.sentinelElement.parentNode === state.gridElement)
+      ? state.sentinelElement
+      : null;
+
     let appended = 0;
     products.forEach((product) => {
       const clone = product.cloneNode(true);
       // Mark for fade-in animation
       clone.classList.add('dw-infinite-new');
-      state.gridElement.appendChild(clone);
+      if (anchor) {
+        state.gridElement.insertBefore(clone, anchor);
+      } else {
+        state.gridElement.appendChild(clone);
+      }
       appended++;
     });
 
@@ -220,7 +255,12 @@
       <div class="dw-infinite-loading-dot"></div>
     `;
     indicator.id = 'dw-infinite-loading';
-    state.gridElement.appendChild(indicator);
+    // Keep the indicator just above the sentinel (end of grid).
+    if (state.sentinelElement && state.sentinelElement.parentNode === state.gridElement) {
+      state.gridElement.insertBefore(indicator, state.sentinelElement);
+    } else {
+      state.gridElement.appendChild(indicator);
+    }
   }
 
   function hideLoadingIndicator() {
@@ -235,15 +275,20 @@
     // Use a sentinel element at the end of the grid
     if (!state.gridElement) return;
 
-    // Clean up old observer
+    // Clean up old observer + sentinel (setup can run twice: init + mutation
+    // observer). Never leave a stray sentinel behind in the grid.
     if (state.intersectionObserver) {
       state.intersectionObserver.disconnect();
     }
+    if (state.sentinelElement && state.sentinelElement.parentNode) {
+      state.sentinelElement.parentNode.removeChild(state.sentinelElement);
+    }
 
     const sentinel = document.createElement('div');
     sentinel.id = 'dw-infinite-scroll-sentinel';
     sentinel.style.height = '1px';
     state.gridElement.appendChild(sentinel);
+    state.sentinelElement = sentinel;
 
     state.intersectionObserver = new IntersectionObserver(
       (entries) => {
@@ -391,6 +436,10 @@
           log('Grid element detected via mutation observer');
           setupIntersectionObserver();
         }
+      } else {
+        // Grid is known — stop observing so we don't churn on our own appends.
+        state.observer.disconnect();
+        state.observer = null;
       }
     });
 
diff --git a/shopify/enhancements/infinite-scroll-test-harness.html b/shopify/enhancements/infinite-scroll-test-harness.html
index 1324ef08..88c1835d 100644
--- a/shopify/enhancements/infinite-scroll-test-harness.html
+++ b/shopify/enhancements/infinite-scroll-test-harness.html
@@ -1,3 +1,17 @@
+<!--
+  ============================================================================
+  FALLBACK / REFERENCE TEST HARNESS — NOT the live storefront mechanism.
+
+  This page exercises the generic (non-Boost) infinite-scroll-observer.js in
+  isolation, against a mock grid + a monkey-patched fetch. The LIVE infinite
+  scroll on the real storefront is the Boost SD override
+  (theme-infinite-scroll-142250278963/), not this code.
+
+  Serve this folder over http:// (e.g. `python3 -m http.server`) and open this
+  file so the "Initialize" button can load ./infinite-scroll-observer.js.
+  Opening via file:// will block the script load.
+  ============================================================================
+-->
 <!DOCTYPE html>
 <html lang="en">
 <head>
@@ -510,18 +524,42 @@
     };
 
     // ============ UI CONTROLS ============
+    let statsTimer = null;
+    function startStats() {
+      updateStats();
+      if (!statsTimer) {
+        statsTimer = setInterval(updateStats, 500);
+      }
+    }
+
     document.getElementById('initBtn').addEventListener('click', () => {
       console.log('Initializing infinite scroll...');
       populateInitialGrid();
-      setTimeout(() => {
-        // Load the infinite scroll script
-        eval(document.querySelector('script[data-infinite-scroll]')?.textContent || '');
+
+      // Already loaded (e.g. clicked twice) — just re-init against fresh grid.
+      if (window.DWInfiniteScroll) {
+        window.DWInfiniteScroll.init();
+        startStats();
+        return;
+      }
+
+      // Load the REAL observer module from this folder (no eval). The IIFE
+      // auto-initializes on load; re-init guards against grid timing.
+      const s = document.createElement('script');
+      s.src = './infinite-scroll-observer.js';
+      s.onload = () => {
         if (window.DWInfiniteScroll) {
           window.DWInfiniteScroll.init();
-          updateStats();
-          setInterval(updateStats, 500);
+          startStats();
+        } else {
+          document.getElementById('statStatus').textContent = 'Load failed';
         }
-      }, 100);
+      };
+      s.onerror = () => {
+        document.getElementById('statStatus').textContent =
+          'Script load blocked — serve over http://, not file://';
+      };
+      document.body.appendChild(s);
     });
 
     document.getElementById('resetBtn').addEventListener('click', () => {
@@ -568,10 +606,10 @@
     populateInitialGrid();
   </script>
 
-  <!-- Inline the infinite scroll script for testing -->
-  <script data-infinite-scroll>
-    /* INFINITE SCROLL CODE WILL BE INSERTED HERE */
-    // (Copy infinite-scroll-observer.js content here for production)
-  </script>
+  <!--
+    The observer module is loaded on demand by the "Initialize Infinite Scroll"
+    button (./infinite-scroll-observer.js), so there is no inline copy to keep
+    in sync. Serve this folder over http:// for the load to succeed.
+  -->
 </body>
 </html>
diff --git a/shopify/scripts/anna-french-scraper.js b/shopify/scripts/anna-french-scraper.js
index 3730f3b8..1c8a95a3 100644
--- a/shopify/scripts/anna-french-scraper.js
+++ b/shopify/scripts/anna-french-scraper.js
@@ -78,7 +78,6 @@ function fetchPage(url) {
 function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
 
 // ---------- helpers ----------
-const BANNED_WORD = /\bwallpapers?\b/gi;
 function cleanWallpaper(s) {
   if (!s) return s;
   let out = String(s).replace(/\bWalls Wallpaper\b/gi, 'Wallcoverings').replace(/\bWallpapers\b/gi, 'Wallcoverings').replace(/\bWallpaper\b/gi, 'Wallcovering');
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 3602c078..514ab49c 100644
--- a/shopify/theme-infinite-scroll-142250278963/assets/boost-sd-custom.js
+++ b/shopify/theme-infinite-scroll-142250278963/assets/boost-sd-custom.js
@@ -178,13 +178,22 @@
    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. */
+   re-renders; distinguishes numbered pager from load-more.
+   Hardened 2026-06-26: single-init double-bind guard; idempotent style +
+   scroll-listener attach; re-arms across Boost re-renders / SPA navigation so
+   the grid is found even when it mounts long after first load. */
 (function() {
   'use strict';
+  if (window.__dwBoostInfiniteScroll) return;   // guard against double-binding
+  window.__dwBoostInfiniteScroll = true;
+
   var loading = false;
+  var STYLE_ID = 'dw-boost-hide-pager';
+  var scrollBound = false;
 
   // 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.
+  // back to a numbered "next" link only if no load-more control exists. We NEVER
+  // hide this button — only the numbered pager list is hidden via CSS below.
   function getLoadMore() {
     return document.querySelector('.boost-sd__load-more-button:not([disabled])') ||
            document.querySelector('button[class*="load-more"]:not([disabled])') ||
@@ -198,6 +207,7 @@
     if (btn) {
       loading = true;
       try { btn.click(); } catch (e) {}
+      // Transient latch only — always released so a later scroll can retry.
       setTimeout(function() { loading = false; }, 1200);
       return;
     }
@@ -209,7 +219,8 @@
     try {
       window.scrollTo(0, Math.max(0, y - 400));
       setTimeout(function () {
-        window.scrollTo(0, document.documentElement.scrollHeight);
+        var h = document.documentElement.scrollHeight;
+        window.scrollTo(0, h);
         setTimeout(function () { loading = false; }, 600);
       }, 120);
     } catch (e) { loading = false; }
@@ -221,22 +232,58 @@
     if (docHeight - scrollBottom < 900) loadMore();
   }
 
-  function init() {
-    var check = setInterval(function() {
+  // Idempotent: hide ONLY the numbered pager (pages list), never the
+  // load-more button. Guarded by element id so re-runs don't stack <style>s.
+  function hidePager() {
+    if (document.getElementById(STYLE_ID)) return;
+    var s = document.createElement('style');
+    s.id = STYLE_ID;
+    s.textContent =
+      '.boost-sd__pagination-list,' +
+      '.boost-sd__pagination-page-list,' +
+      '.boost-sd__pagination .boost-sd__pagination-item{display:none!important;}';
+    (document.head || document.documentElement).appendChild(s);
+  }
+
+  // Idempotent: bind the scroll handler at most once (it lives on window, so it
+  // survives Boost re-renders / SPA grid swaps without needing a rebind).
+  function bindScroll() {
+    if (scrollBound) return;
+    scrollBound = true;
+    window.addEventListener('scroll', onScroll, { passive: true });
+  }
+
+  // Poll for the Boost grid and arm when it appears. Re-armable across SPA
+  // navigation — the grid can mount well after the first poll window closes.
+  var check = null;
+  function arm() {
+    if (check) return;            // a poll cycle is already running
+    var ticks = 0;
+    check = setInterval(function() {
+      ticks++;
       if (document.querySelector('.boost-sd__product-list') ||
           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-list,' +
-          '.boost-sd__pagination-page-list,' +
-          '.boost-sd__pagination .boost-sd__pagination-item{display:none!important;}';
-        document.head.appendChild(s);
+        clearInterval(check); check = null;
+        bindScroll();
+        hidePager();
+      } else if (ticks > 37) {    // ~15s @ 400ms — stop this cycle, can re-arm
+        clearInterval(check); check = null;
       }
     }, 400);
-    setTimeout(function() { clearInterval(check); }, 15000);
+  }
+
+  // Re-arm on SPA navigation so a freshly-mounted Boost grid is recaught.
+  function watchSpaNav() {
+    var last = location.href;
+    setInterval(function() {
+      if (location.href !== last) { last = location.href; arm(); }
+    }, 700);
+    window.addEventListener('popstate', arm);
+  }
+
+  function init() {
+    arm();
+    watchSpaNav();
   }
 
   if (document.readyState === 'loading') {
diff --git a/shopify/theme-infinite-scroll-142250278963/snippets/boost-infinite-override.liquid b/shopify/theme-infinite-scroll-142250278963/snippets/boost-infinite-override.liquid
index 5fe4529d..0593e4d5 100644
--- a/shopify/theme-infinite-scroll-142250278963/snippets/boost-infinite-override.liquid
+++ b/shopify/theme-infinite-scroll-142250278963/snippets/boost-infinite-override.liquid
@@ -34,8 +34,12 @@
 
   // Recursively rewrite any "paginationType" key to infinite_scroll, plus the
   // common Boost shapes (generalSettings + additionalElements.pagination).
-  function patchObj(o, depth) {
+  // `seen` guards against cyclic / shared references so a self-referential
+  // Boost config can't loop or get re-walked needlessly.
+  function patchObj(o, depth, seen) {
     if (!o || typeof o !== 'object' || depth > 6) return;
+    if (seen.indexOf(o) !== -1) return;
+    seen.push(o);
     try {
       if (Object.prototype.hasOwnProperty.call(o, 'paginationType') &&
           typeof o.paginationType === 'string') {
@@ -50,18 +54,20 @@
     for (var k in o) {
       if (!Object.prototype.hasOwnProperty.call(o, k)) continue;
       var v = o[k];
-      if (v && typeof v === 'object') patchObj(v, depth + 1);
+      if (v && typeof v === 'object') patchObj(v, depth + 1, seen);
     }
   }
 
   function patchConfig(cfg) {
     if (!cfg || typeof cfg !== 'object') return cfg;
     try {
-      // Known top-level Boost surfaces.
-      if (cfg.generalSettings) patchObj(cfg.generalSettings, 0);
-      if (cfg.additionalElements) patchObj(cfg.additionalElements, 0);
-      // Catch-all deep walk (resilient to schema drift).
-      patchObj(cfg, 0);
+      // One shared `seen` set across all walks keeps total work O(n) and
+      // cycle-safe — known surfaces get a full-depth targeted walk, then the
+      // catch-all root walk skips anything already visited (schema-drift safe).
+      var seen = [];
+      if (cfg.generalSettings) patchObj(cfg.generalSettings, 0, seen);
+      if (cfg.additionalElements) patchObj(cfg.additionalElements, 0, seen);
+      patchObj(cfg, 0, seen);
     } catch (e) {}
     return cfg;
   }
diff --git a/shopify/theme-infinite-scroll-142250278963/verify-boost-infinite.js b/shopify/theme-infinite-scroll-142250278963/verify-boost-infinite.js
index 6f7b0ebe..75690203 100644
--- a/shopify/theme-infinite-scroll-142250278963/verify-boost-infinite.js
+++ b/shopify/theme-infinite-scroll-142250278963/verify-boost-infinite.js
@@ -1,10 +1,54 @@
 // Verify Boost infinite-scroll override on a DEV theme preview.
-// Loads the collection preview, counts .boost-sd__product-item, scrolls ~8x,
+// Loads each collection preview, counts .boost-sd__product-item, scrolls ~8x,
 // recounts. PASS = count grows on BOTH desktop and mobile, no numbered pager.
+//
+// STRICTLY READ-ONLY: this script only navigates and scrolls the live theme
+// preview. It never clicks add-to-cart, submits forms, or mutates any state.
+//
+// Configuration (argv takes precedence over env, env over built-in defaults):
+//   --theme=<id>            | env DEV_THEME        DEV theme id to preview
+//   --collections=a,b,c     | env COLLECTIONS      one or more collection slugs
+//   --filter=<querystring>  | env FILTER_QS        extra query string appended
+//   --host=<hostname>       | env VERIFY_HOST      storefront hostname
+//   --scrolls=<n>           | env SCROLL_PASSES    scroll passes per viewport
+// Bare (non --flag) argv values are also treated as collection slugs, so
+//   node verify-boost-infinite.js red-wallcovering-collection blue-...   works.
+//
+// Exit code: 0 = every collection PASS on both viewports, nonzero = any FAIL.
 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`;
+// ---- argv / env parsing -----------------------------------------------------
+function parseArgs(argv) {
+  const opts = {};
+  const bare = [];
+  for (const a of argv) {
+    const m = /^--([^=]+)=(.*)$/.exec(a);
+    if (m) opts[m[1].toLowerCase()] = m[2];
+    else if (/^--/.test(a)) opts[a.slice(2).toLowerCase()] = 'true';
+    else bare.push(a);
+  }
+  return { opts, bare };
+}
+const { opts, bare } = parseArgs(process.argv.slice(2));
+
+const DEV = opts.theme || process.env.DEV_THEME || '143956443187';
+const HOST = opts.host || process.env.VERIFY_HOST || 'www.designerwallcoverings.com';
+const FILTER_QS = opts.filter != null ? opts.filter
+  : (process.env.FILTER_QS != null ? process.env.FILTER_QS : 'pf_t_product_type=Wallcovering');
+const SCROLL_PASSES = Math.max(1, parseInt(opts.scrolls || process.env.SCROLL_PASSES || '8', 10) || 8);
+
+const COLLECTIONS = (() => {
+  const raw = opts.collections || process.env.COLLECTIONS || '';
+  const fromOpt = raw.split(',').map(s => s.trim()).filter(Boolean);
+  const list = [...fromOpt, ...bare.map(s => s.trim()).filter(Boolean)];
+  return list.length ? Array.from(new Set(list)) : ['red-wallcovering-collection'];
+})();
+
+function buildUrl(slug) {
+  const qs = [`preview_theme_id=${encodeURIComponent(DEV)}`];
+  if (FILTER_QS) qs.push(FILTER_QS.replace(/^[?&]/, ''));
+  return `https://${HOST}/collections/${slug}?${qs.join('&')}`;
+}
 
 const ITEM = '.boost-sd__product-item';
 const PAGER_NUMBERED = '.boost-sd__pagination-list, .boost-sd__pagination-page-list, .boost-sd__pagination-item';
@@ -21,27 +65,53 @@ async function waitForGrid(page) {
   } catch (e) { return false; }
 }
 
-async function run(label, viewport, ua) {
+async function run(slug, label, viewport, ua) {
+  const url = buildUrl(slug);
   const browser = await chromium.launch({ headless: true });
-  const ctx = await browser.newContext({
-    viewport,
-    userAgent: ua,
-    deviceScaleFactor: 2,
-  });
+  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 };
+  const result = {
+    slug, label, viewport, url,
+    before: 0, after: 0, numberedPager: null, grew: false, ok: false,
+    failStep: null, error: null,
+  };
+  // Per-step diagnostics: name the phase we're in so a failure pinpoints it.
+  let step = 'init';
   try {
-    await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 60000 });
+    step = 'navigate';
+    const resp = await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
+    if (resp && resp.status() >= 400) {
+      result.failStep = step;
+      result.error = `HTTP ${resp.status()} loading collection (check slug "${slug}" + theme ${DEV})`;
+      await browser.close();
+      return result;
+    }
+
+    step = 'wait-for-grid';
     const gridReady = await waitForGrid(page);
-    if (!gridReady) { result.error = 'grid never rendered (no .boost-sd__product-item in 30s)'; await browser.close(); return result; }
+    if (!gridReady) {
+      result.failStep = step;
+      result.error = 'grid never rendered (no .boost-sd__product-item in 30s) — wrong collection, empty filter, or Boost not loaded';
+      await browser.close();
+      return result;
+    }
+
+    step = 'count-initial';
     await page.waitForTimeout(2500); // let initial page settle
     result.before = await countItems(page);
+    if (result.before === 0) {
+      result.failStep = step;
+      result.error = 'grid rendered but 0 items counted after settle';
+      await browser.close();
+      return result;
+    }
 
-    // Scroll ~8 passes. Use INCREMENTAL smooth scrolling (mimics a real user)
+    // Scroll ~N 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.
+    step = 'scroll-and-append';
     let last = result.before;
-    for (let i = 0; i < 8; i++) {
+    for (let i = 0; i < SCROLL_PASSES; i++) {
       const sh = await page.evaluate(() => document.documentElement.scrollHeight);
       let y = 0;
       while (y < sh) {
@@ -55,6 +125,7 @@ async function run(label, viewport, ua) {
     result.after = last;
     result.grew = result.after > result.before;
 
+    step = 'check-pager';
     // Numbered pager visible?
     result.numberedPager = await page.evaluate(sel => {
       const els = document.querySelectorAll(sel);
@@ -67,29 +138,59 @@ async function run(label, viewport, ua) {
     }, PAGER_NUMBERED);
 
     result.ok = result.grew && !result.numberedPager;
+    if (!result.ok) {
+      result.failStep = 'verdict';
+      if (!result.grew) result.error = `item count did not grow (before=${result.before} after=${result.after}) — infinite-scroll append not firing`;
+      else result.error = 'numbered pager is visible — Boost pagination not overridden to infinite scroll';
+    }
   } catch (e) {
+    result.failStep = step;
     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');
+const DESKTOP_UA = '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_UA = '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';
 
+(async () => {
   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'}`);
+  console.log('DEV theme   :', DEV);
+  console.log('host        :', HOST);
+  console.log('filter qs   :', FILTER_QS || '(none)');
+  console.log('scroll passes:', SCROLL_PASSES);
+  console.log('collections :', COLLECTIONS.join(', '));
+
+  const collectionResults = [];
+  for (const slug of COLLECTIONS) {
+    console.log(`\n---------------- collection: ${slug} ----------------`);
+    console.log('URL:', buildUrl(slug));
+
+    const desktop = await run(slug, 'desktop', { width: 1440, height: 900 }, DESKTOP_UA);
+    const mobile = await run(slug, 'mobile', { width: 390, height: 844 }, MOBILE_UA);
+
+    for (const r of [desktop, mobile]) {
+      console.log(`\n[${r.label}] ${r.viewport.width}x${r.viewport.height}`);
+      if (r.error) {
+        console.log(`  FAIL at step "${r.failStep}": ${r.error}`);
+        console.log(`  (before=${r.before} after=${r.after} grew=${r.grew} numberedPagerVisible=${r.numberedPager})`);
+        continue;
+      }
+      console.log(`  before=${r.before}  after=${r.after}  grew=${r.grew}  numberedPagerVisible=${r.numberedPager}  => ${r.ok ? 'PASS' : 'FAIL'}`);
+    }
+
+    const ok = desktop.ok && mobile.ok;
+    console.log(`\n  collection ${slug}: ${ok ? 'PASS (append on both)' : 'FAIL'}`);
+    collectionResults.push({ slug, ok, desktop, mobile });
+  }
+
+  console.log('\n================== SUMMARY ==================');
+  for (const c of collectionResults) {
+    console.log(`  ${c.ok ? 'PASS' : 'FAIL'}  ${c.slug}`);
   }
-  const pass = desktop.ok && mobile.ok;
-  console.log(`\nOVERALL: ${pass ? 'PASS (append on both)' : 'FAIL'}`);
+  const pass = collectionResults.every(c => c.ok);
+  console.log(`\nOVERALL: ${pass ? 'PASS' : 'FAIL'} (${collectionResults.filter(c => c.ok).length}/${collectionResults.length} collections)`);
   console.log('=============================================================');
   process.exit(pass ? 0 : 1);
 })();

← d0429e1a auto-save: 2026-06-26T15:34:21 (6 files) — pending-approval/  ·  back to Designer Wallcoverings  ·  auto-save: 2026-06-26T17:04:47 (3 files) — pending-approval/ 2afc375b →