[object Object]

← back to Dw Theme Hamburger

TK-11186: data-driven showroom-vendor suppression — render-time card skip (no HTML/SEO leak, no flash) + de-hardcoded backstop reading shop metafield/theme setting

ac8c03084b2a9637873e765563859a8750604d05 · 2026-09-03 12:00:06 -0700 · Steve Abrams

Replaces the hardcoded ['Phillip Jeffries'] JS hack. product-list-item.liquid now wraps the card in {% unless is_showroom_vendor %} so no markup is emitted for a showroom vendor; hide-browse-hidden.liquid reads custom.showroom_vendors metafield -> settings.showroom_vendors -> fallback, with a CSS paint-time hide + minimal JS Boost-AJAX backstop. .pre-showroom backup kept. NOT deployed (theme publish is Steve-gated).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YXf9gMQQWRfNex6EestZhp

Files touched

Diff

commit ac8c03084b2a9637873e765563859a8750604d05
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 3 12:00:06 2026 -0700

    TK-11186: data-driven showroom-vendor suppression — render-time card skip (no HTML/SEO leak, no flash) + de-hardcoded backstop reading shop metafield/theme setting
    
    Replaces the hardcoded ['Phillip Jeffries'] JS hack. product-list-item.liquid now wraps the card in {% unless is_showroom_vendor %} so no markup is emitted for a showroom vendor; hide-browse-hidden.liquid reads custom.showroom_vendors metafield -> settings.showroom_vendors -> fallback, with a CSS paint-time hide + minimal JS Boost-AJAX backstop. .pre-showroom backup kept. NOT deployed (theme publish is Steve-gated).
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01YXf9gMQQWRfNex6EestZhp
---
 snippets/hide-browse-hidden.liquid             |  88 ++++++-----
 snippets/product-list-item.liquid              |  25 ++-
 snippets/product-list-item.liquid.pre-showroom | 205 +++++++++++++++++++++++++
 3 files changed, 277 insertions(+), 41 deletions(-)

diff --git a/snippets/hide-browse-hidden.liquid b/snippets/hide-browse-hidden.liquid
index a827be6..38e66bb 100644
--- a/snippets/hide-browse-hidden.liquid
+++ b/snippets/hide-browse-hidden.liquid
@@ -1,52 +1,60 @@
 {% comment %}
-  Hide browse-hidden products (Phillip Jeffries) from ALL rendered grids.
-  Runs repeatedly to catch Boost pagination, filtering, and AJAX loads.
+  Suppress showroom-only vendors from ALL browse/search grids. TK-11186.
+
+  DATA-DRIVEN — no vendor name is hardcoded in logic. The showroom-vendor list is
+  sourced (in priority order) from:
+    1. shop metafield  custom.showroom_vendors   (comma-separated; store-wide, all themes)
+    2. theme setting   settings.showroom_vendors  (comma-separated)
+    3. a canonical fallback snapshot of ~/Projects/fix-live-board/config/showroom-vendors.json
+       (last resort so suppression never silently fails if 1+2 are unset).
+  Canonical source of truth = showroom-vendors.json. Set the shop metafield to that
+  file's contents (metafield-update skill) and this snippet follows it automatically.
+
+  Two layers:
+    (a) RENDER-TIME: product-list-item.liquid emits NO card HTML for a showroom vendor
+        (the real fix — no SEO-crawlable markup, no flash). This snippet's CSS also
+        hides any card that carries data-vendor at paint time (before JS).
+    (b) BACKSTOP: a JS observer catches Boost AJAX-rendered grids that bypass Liquid,
+        reading the SAME injected list (case-insensitive). Kept minimal.
 {% endcomment %}
+{%- liquid
+  assign showroom_raw = shop.metafields.custom.showroom_vendors
+  if showroom_raw == blank
+    assign showroom_raw = settings.showroom_vendors
+  endif
+  if showroom_raw == blank
+    assign showroom_raw = 'Phillip Jeffries'
+  endif
+  assign showroom_vendors = showroom_raw | split: ','
+-%}
+<style>
+{%- for v in showroom_vendors -%}
+{%- assign vt = v | strip -%}
+{%- if vt != blank -%}
+[data-vendor="{{ vt | escape }}"]{display:none!important;}
+{%- endif -%}
+{%- endfor -%}
+</style>
 <script>
 (function(){
-  var HIDDEN_VENDORS = ['Phillip Jeffries'];
-
-  function hideProducts() {
-    // Method 1: Find vendor text elements
-    document.querySelectorAll('.product-vendor, .product-list-item-vendor, [class*="vendor"]').forEach(function(el) {
-      var text = el.textContent.trim();
-      if (HIDDEN_VENDORS.indexOf(text) > -1) {
-        var card = el.closest('.product-list-item, .boost-sd__product-item, [class*="product-item"], [class*="product-card"], article, .boost-sd__product');
-        if (card && card.style.display !== 'none') { card.style.display = 'none'; }
-      }
+  var HIDDEN = [{%- for v in showroom_vendors -%}{%- assign vt = v | strip -%}{%- if vt != blank -%}{{ vt | json }}{%- unless forloop.last -%},{%- endunless -%}{%- endif -%}{%- endfor -%}].map(function(s){return String(s).trim().toLowerCase();});
+  if (!HIDDEN.length) return;
+  var CARD_SEL = '.product-list-item, .boost-sd__product-item, [class*="product-item"], [class*="product-card"], article, .boost-sd__product';
+  function isHidden(text){ text = (text||'').trim().toLowerCase(); for (var i=0;i<HIDDEN.length;i++){ if (text.indexOf(HIDDEN[i]) > -1) return true; } return false; }
+  function hideProducts(){
+    document.querySelectorAll('.product-vendor, .product-list-item-vendor, [class*="vendor"]').forEach(function(el){
+      if (isHidden(el.textContent)){ var c = el.closest(CARD_SEL); if (c && c.style.display !== 'none') c.style.display = 'none'; }
     });
-
-    // Method 2: Find product titles containing vendor name
-    document.querySelectorAll('.product-list-item-title a, .boost-sd__product-title a, [class*="product-title"] a, h2 a, h3 a').forEach(function(el) {
-      var text = el.textContent.trim();
-      for (var i = 0; i < HIDDEN_VENDORS.length; i++) {
-        if (text.indexOf(HIDDEN_VENDORS[i]) > -1) {
-          var card = el.closest('.product-list-item, .boost-sd__product-item, [class*="product-item"], [class*="product-card"], article, .boost-sd__product');
-          if (card && card.style.display !== 'none') { card.style.display = 'none'; }
-          break;
-        }
-      }
+    document.querySelectorAll('.product-list-item-title a, .boost-sd__product-title a, [class*="product-title"] a, h2 a, h3 a').forEach(function(el){
+      if (isHidden(el.textContent)){ var c = el.closest(CARD_SEL); if (c && c.style.display !== 'none') c.style.display = 'none'; }
     });
-
-    // Method 3: Check Boost's JSON data in script tags
-    document.querySelectorAll('[data-vendor="Phillip Jeffries"]').forEach(function(el) {
-      el.style.display = 'none';
+    document.querySelectorAll('[data-vendor]').forEach(function(el){
+      if (HIDDEN.indexOf(String(el.getAttribute('data-vendor')||'').trim().toLowerCase()) > -1){ el.style.display = 'none'; }
     });
   }
-
-  // Run immediately
   hideProducts();
-
-  // Run after DOM ready
-  if (document.readyState === 'loading') {
-    document.addEventListener('DOMContentLoaded', function() { hideProducts(); });
-  }
-
-  // Run every 2 seconds to catch Boost pagination/AJAX loads
+  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', hideProducts);
   setInterval(hideProducts, 2000);
-
-  // MutationObserver as additional safety
-  var observer = new MutationObserver(function() { setTimeout(hideProducts, 100); });
-  observer.observe(document.body, { childList: true, subtree: true });
+  if (document.body){ new MutationObserver(function(){ setTimeout(hideProducts, 100); }).observe(document.body, { childList:true, subtree:true }); }
 })();
 </script>
diff --git a/snippets/product-list-item.liquid b/snippets/product-list-item.liquid
index 921a4fa..0454647 100644
--- a/snippets/product-list-item.liquid
+++ b/snippets/product-list-item.liquid
@@ -4,6 +4,29 @@
     Defaults to: blank
 {% endcomment %}
 
+{%- comment %} TK-11186 render-time showroom-vendor skip: emit NO card HTML for a showroom-only
+  vendor (addressable-not-discoverable). List sourced from shop metafield / theme setting /
+  canonical showroom-vendors.json fallback — never a hardcoded vendor. {% endcomment -%}
+{%- liquid
+  assign showroom_raw = shop.metafields.custom.showroom_vendors
+  if showroom_raw == blank
+    assign showroom_raw = settings.showroom_vendors
+  endif
+  if showroom_raw == blank
+    assign showroom_raw = 'Phillip Jeffries'
+  endif
+  assign showroom_vendors = showroom_raw | split: ','
+  assign this_vendor = product.vendor | strip | downcase
+  assign is_showroom_vendor = false
+  for sv in showroom_vendors
+    assign svn = sv | strip | downcase
+    if svn != blank and svn == this_vendor
+      assign is_showroom_vendor = true
+    endif
+  endfor
+-%}
+{%- unless is_showroom_vendor -%}
+
 {% liquid
   assign product_attributes = product_attributes | default: ''
   assign product_hover = settings.product_hover |  default: 'quick-shop'
@@ -202,4 +225,4 @@
     {% endif %}
   </div>
 
-</article>
\ No newline at end of file
+</article>{%- endunless -%}
diff --git a/snippets/product-list-item.liquid.pre-showroom b/snippets/product-list-item.liquid.pre-showroom
new file mode 100644
index 0000000..921a4fa
--- /dev/null
+++ b/snippets/product-list-item.liquid.pre-showroom
@@ -0,0 +1,205 @@
+{% comment %}
+  @param product_attributes
+    custom attributes to be applied to the product item
+    Defaults to: blank
+{% endcomment %}
+
+{% liquid
+  assign product_attributes = product_attributes | default: ''
+  assign product_hover = settings.product_hover |  default: 'quick-shop'
+  assign product_stock_level_threshold = settings.product_stock_level_threshold | default: 1
+  assign product_badges = settings.product_badges | default: false
+  assign product_icons = settings.product_badges_icons | default: false
+  assign product_vendor = settings.show_vendor | default: false
+
+  assign item = product
+  if template contains 'search'
+    assign item = item
+  endif
+
+  assign has_quick_shop = false
+  if product_hover == 'quick-shop' and template.name != 'password' and product.variants_count <= 250
+    assign has_quick_shop = true
+  endif
+%}
+
+{% if product_hover == 'stock-level' and item.available %}
+  {% assign total = 0 %}
+  {% assign threshold = product_stock_level_threshold | times: 1 %}
+  {% assign infinity = false %}
+  {% for variant in item.variants %}
+    {% if variant.inventory_management == null %}
+      {% assign infinity = true %}
+    {% elsif variant.inventory_management == '' %}
+      {% assign infinity = true %}
+    {% elsif variant.inventory_management == 'shopify' and variant.inventory_policy == 'continue' %}
+      {% assign infinity = true %}
+    {% elsif infinity == false %}
+      {% capture temp %}{{ total | plus: variant.inventory_quantity }}{% endcapture %}
+      {% assign total = temp | times: 1 %}
+    {% endif %}
+  {% endfor %}
+  {% assign stockText = 'products.product.stock_indicator_message' | t: num: total %}
+{% endif %}
+
+<article
+  class="
+    product-list-item
+    {% if has_quick_shop %} has-quick-shop{% endif %}
+    {% if item.available and infinity == false and total <= threshold %} has-stock-indicator{%endif %}
+  "
+  id="product-list-item-{{ item.id }}"
+  data-product-id="{{ item.id }}"
+  {{ product_attributes }}
+>
+
+  {% assign secondaryImage = false %}
+  {% if item.media.size > 1 and product_hover == 'image-flip' %}
+    {% assign secondaryImage = true %}
+  {% endif %}
+
+  <figure
+    class="
+      product-list-item-thumbnail
+      {% if secondaryImage %}
+        has-secondary-image
+      {% endif %}
+    "
+    data-url="{{ item.url | within: collection }}"
+    {% if secondaryImage %}
+      {%
+        render 'rimg',
+        img: item.media[1].preview_image,
+        alt: item.media[1].preview_image.alt,
+        size: '600x600',
+        background: true,
+        lazy: true
+      %}
+    {% endif %}
+  >
+    <a href="{{ item.url | within: collection }}" aria-label="{{ item.title }}">
+      {% if item.featured_media.preview_image %}
+        {%
+          render 'rimg',
+          img: item.featured_media.preview_image,
+          alt: item.featured_media.preview_image.alt,
+          size: '600x600',
+          lazy: true
+        %}
+      {% else %}
+        {{ 'product-1' | placeholder_svg_tag: 'placeholder-svg' }}
+      {% endif %}
+
+      <div class="product-info">
+        <div class="vertical-center">
+          <div>View Pattern</div>
+          {%- liquid
+            assign sku_raw = item.variants.last.sku | default: ''
+            assign sku_display = sku_raw | replace: '-Sample', '' | replace: '-SAMPLE', '' | replace: '-sample', ''
+            assign sku_display = sku_display | replace: '--', '-' | replace: '--', '-'
+            assign sku_last_char = sku_display | slice: -1, 1
+            if sku_display != blank and sku_last_char == '-'
+              assign sku_display = sku_display | remove_last: '-'
+            endif
+          -%}
+          <div>{{ sku_display }}</div>
+        </div>
+      </div>
+      
+    </a>
+
+    {% if has_quick_shop %}
+      <span
+        class="quick-shop-modal-trigger"
+        data-product-url="{{ item.url | within: collection }}"
+      >
+        {{ 'products.product.quick_shop_trigger_text' | t }}
+      </span>
+    {% elsif product_hover == 'stock-level' %}
+
+      {% if item.available and infinity == false and total <= threshold %}
+        <a class="product-list-item-inventory" href="{{ item.url }}">{{ stockText }}</a>
+      {% endif %}
+
+    {% endif %}
+
+    {% if product_badges %}
+      {% if item.available != true %}
+        <span class="product-list-item-unavailable{% if product_icons %} product-icons{% endif %}" data-title="{{ 'products.product.sold_out' | t }}"></span>
+      {% elsif item.compare_at_price_min > item.price_min %}
+        <span class="product-list-item-on-sale{% if product_icons %} product-icons{% endif %}" data-title="{{ 'products.product.on_sale' | t }}"></span>
+      {% endif %}
+    {% endif %}
+  </figure>
+
+  <div class="product-list-item-details">
+    {% if product_vendor %}
+      <p class="product-list-item-vendor">{{ item.vendor | link_to_vendor }}</p>
+    {% endif %}
+    <h2 class="product-list-item-title"><a href="{{ item.url | within: collection }}">{{ item.title }}</a></h2>
+    <p class="product-list-item-price">
+      {% if item.price_varies %}
+        {% if item.price_varies %}{{ 'products.product.from' | t }}{% endif %}
+        {% if item.compare_at_price_min > item.price_min %}
+          <span class="money">{{ item.price_min | money }}</span>
+          <span class="original money">{{ item.compare_at_price_min | money }}</span>
+        {% else %}
+          <span class="money">{{ item.price_min | money }}</span>
+        {% endif %}
+      {% else %}
+        {% if item.compare_at_price_min > item.price_min %}
+          <span class="money">{{ item.price_min | money }}</span>
+          <span class="original money">{{ item.compare_at_price_min | money }}</span>
+        {% else %}
+          <span class="money">{{ item.price_min | money }}</span>
+        {% endif %}
+      {% endif %}
+    </p>
+    {% assign variant_for_unit_price = item.variants | sort: 'price' | first %}
+    {% if variant_for_unit_price.unit_price %}
+      {% comment %}Inject unit-price begin{% endcomment %}
+      {% comment %}
+        @param variant_for_unit_price
+          Product variant for price
+        @param tax_text
+          String containing 'tax included' text
+      {% endcomment %}
+      
+      {% capture total_quantity %}
+        <span class="product-price__unit-price-total-quantity" data-unit-price-quantity>
+          {{ variant_for_unit_price.unit_price_measurement.quantity_value }}{{ variant_for_unit_price.unit_price_measurement.quantity_unit }}
+        </span>
+      {% endcapture %}
+      
+      
+      {% capture unit_price %}
+        <span class="product-price__unit-price-amount money" data-unit-price-amount>
+          {{ variant_for_unit_price.unit_price | money }}
+        </span>
+      {% endcapture %}
+      {% capture unit_measure %}
+        <span class="product-price__unit-price-measure" data-unit-price-measure>
+          {%- if variant_for_unit_price.unit_price_measurement.reference_value != 1 -%}
+            {{ variant_for_unit_price.unit_price_measurement.reference_value }}
+          {%- endif %}
+          {{ variant_for_unit_price.unit_price_measurement.reference_unit }}
+        </span>
+      {% endcapture %}
+      
+      <div
+        class="
+          product-price__unit-price
+          {% unless variant_for_unit_price.unit_price_measurement %}hidden{% endunless %}
+        "
+        data-unit-price
+      >
+        {{ 'products.product.price_per_unit_html' | t: total_quantity: total_quantity, unit_price: unit_price, unit_measure: unit_measure | strip_newlines }}
+      </div>
+      
+      {% assign variant_for_unit_price = blank %}
+      {% comment %}Inject unit-price end{% endcomment %}
+
+    {% endif %}
+  </div>
+
+</article>
\ No newline at end of file

← b4ce7c5 remove obsolete product enrichment request  ·  back to Dw Theme Hamburger  ·  auto-data-snapshot: 2026-09-03T12:32:21 (1 data files) — sni 388e4bd →