[object Object]

← back to Designer Wallcoverings

auto-save: 2026-06-26T11:15:12 (7 files) — pending-approval/boost-filter-consolidation-2026-06-25 vendor-scrapers/china-seas-refresh audits/designtex-uom-fix/220216-live-AFTER-boltsize-2026-06-26.png audits/designtex-uom-fix/220216-spec-boltsize-LIVE.png shopify/_cwGRID/deployed/

9279cbe42d697f58d63ee1ae642b32fa6eae856a · 2026-06-26 11:15:21 -0700 · Steve Abrams

Files touched

Diff

commit 9279cbe42d697f58d63ee1ae642b32fa6eae856a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jun 26 11:15:21 2026 -0700

    auto-save: 2026-06-26T11:15:12 (7 files) — pending-approval/boost-filter-consolidation-2026-06-25 vendor-scrapers/china-seas-refresh audits/designtex-uom-fix/220216-live-AFTER-boltsize-2026-06-26.png audits/designtex-uom-fix/220216-spec-boltsize-LIVE.png shopify/_cwGRID/deployed/
---
 .../220216-live-AFTER-boltsize-2026-06-26.png      | Bin 0 -> 3336723 bytes
 .../220216-spec-boltsize-LIVE.png                  | Bin 0 -> 1916077 bytes
 .../design-coordinate.product-block.liquid         | 374 +++++++++++++++++++++
 shopify/push-section-to-dev-theme.sh               | 112 ++++++
 shopify/surgical-dc-v7-on-dev-theme.sh             |  98 ++++++
 5 files changed, 584 insertions(+)

diff --git a/audits/designtex-uom-fix/220216-live-AFTER-boltsize-2026-06-26.png b/audits/designtex-uom-fix/220216-live-AFTER-boltsize-2026-06-26.png
new file mode 100644
index 00000000..e9e6f9f7
Binary files /dev/null and b/audits/designtex-uom-fix/220216-live-AFTER-boltsize-2026-06-26.png differ
diff --git a/audits/designtex-uom-fix/220216-spec-boltsize-LIVE.png b/audits/designtex-uom-fix/220216-spec-boltsize-LIVE.png
new file mode 100644
index 00000000..b37e47bb
Binary files /dev/null and b/audits/designtex-uom-fix/220216-spec-boltsize-LIVE.png differ
diff --git a/shopify/_cwGRID/deployed/design-coordinate.product-block.liquid b/shopify/_cwGRID/deployed/design-coordinate.product-block.liquid
new file mode 100644
index 00000000..6a85e5f0
--- /dev/null
+++ b/shopify/_cwGRID/deployed/design-coordinate.product-block.liquid
@@ -0,0 +1,374 @@
+{%- comment -%}
+  SOURCE OF TRUTH: this is the LIVE Design Coordinate block as deployed inside
+  sections/product.liquid on dev theme 144000057395 (Steves Version 5.6.0 [Dev]).
+  Pulled 2026-06-26 after the v7 surgical edits (Auto Generated rename + 6+6).
+  It DIVERGES from dw-pairs-well/deploy/shopify/design-coordinate.liquid (which lacks
+  the place() below-image helper). Edit THIS via surgical-dc-v7-on-dev-theme.sh.
+{%- endcomment -%}
+{%- comment -%}
+      Design Coordinate v6 — Steve's spec:
+        • Catalog grid: 6 stripe/plaid/check coordinates that use the source palette
+        • Density slider 3-12 cols (default 6), localStorage-persisted
+        • Auto Generated: 6 SVG-poster placeholders seeded from palette
+        • Vendor picker: SW / DE / BM / F&B (default on) + Behr / PPG / RAL (opt-in)
+        • Each card shows palette dots + selected vendors' paint chips
+      Service: https://pairs.designerwallcoverings.com  (commit history at ~/Projects/dw-pairs-well)
+      CSS namespace kept as .pairs-well__* — only the button TEXT is "Design Coordinate".
+    {%- endcomment -%}
+    <section
+      id="pairs-well-with"
+      class="dc-section"
+      data-pairs-endpoint="https://pairs.designerwallcoverings.com/api/pairs"
+      data-ai-endpoint="https://pairs.designerwallcoverings.com/api/ai-coordinates"
+      data-product-handle="{{ product.handle }}"
+      data-product-sku="{{ product.metafields.custom.manufacturer_sku | default: product.selected_or_first_available_variant.sku | escape }}"
+      style="margin: 0 0 1.25em; clear: both;"
+      hidden
+    >
+      <button type="button" class="pairs-well__cta" aria-expanded="false">
+        <span class="pairs-well__cta-text">Design Coordinate</span>
+        <span class="pairs-well__cta-icon" aria-hidden="true">+</span>
+      </button>
+
+      <div class="pairs-well__source-palette" id="pairs-well-source-palette" hidden></div>
+
+      <div class="pairs-well__vendor-picker" id="pairs-well-vendor-picker" hidden>
+        <h4>Paint brands</h4>
+        <div class="chips">
+          <label><input type="checkbox" value="sw"   checked> Sherwin-Williams</label>
+          <label><input type="checkbox" value="de"   checked> Dunn-Edwards</label>
+          <label><input type="checkbox" value="bm"   checked> Benjamin Moore</label>
+          <label><input type="checkbox" value="fb"   checked> Farrow &amp; Ball</label>
+          <label><input type="checkbox" value="behr"        > Behr</label>
+          <label><input type="checkbox" value="ppg"         > PPG</label>
+          <label><input type="checkbox" value="ral"         > RAL</label>
+        </div>
+      </div>
+
+      <div class="pairs-well__catalog" id="pairs-well-catalog" hidden>
+        <div class="pairs-well__panel-head">
+          <h3>Stripes &amp; Plaids from our catalog <span class="count" id="pairs-well-catalog-count"></span></h3>
+          <div class="pairs-well__controls">
+            <label for="pairs-well-cols-catalog">Columns</label>
+            <input id="pairs-well-cols-catalog" type="range" min="3" max="12" value="6" step="1">
+            <span class="pairs-well__cols-val" id="pairs-well-cols-catalog-val">6</span>
+          </div>
+        </div>
+        <div id="pairs-well-grid" class="pairs-well__grid"></div>
+      </div>
+
+      <div class="pairs-well__ai" id="pairs-well-ai" hidden>
+        <div class="pairs-well__panel-head">
+          <h3>Auto Generated <span class="ai-badge">Preview · 6 options</span></h3>
+          <div class="pairs-well__controls">
+            <label for="pairs-well-cols-ai">Columns</label>
+            <input id="pairs-well-cols-ai" type="range" min="3" max="12" value="6" step="1">
+            <span class="pairs-well__cols-val" id="pairs-well-cols-ai-val">6</span>
+          </div>
+        </div>
+        <div id="pairs-well-ai-grid" class="pairs-well__grid pairs-well__grid--ai"></div>
+      </div>
+    </section>
+
+    <script>
+      /* Move the compact Design Coordinate pill up to sit right under the product image. */
+      (function () {
+        var s = document.getElementById('pairs-well-with'); if (!s) return;
+        function place() {
+          var g = document.querySelector('.product-gallery--viewport--figure')
+               || document.querySelector('.product-gallery--media-wrapper');
+          if (g && g.parentNode && !s.dataset.placed) { g.parentNode.insertBefore(s, g.nextSibling); s.style.margin = '14px 0 0'; s.dataset.placed = '1'; }
+        }
+        if (document.readyState !== 'loading') place(); else document.addEventListener('DOMContentLoaded', place);
+      })();
+    </script>
+
+    <style>
+      #pairs-well-with { font-family: inherit; --cols-catalog: 6; --cols-ai: 6; }
+      .pairs-well__cta {
+        display: inline-flex; align-items: center; justify-content: center; gap: 8px;
+        width: auto; padding: 8px 16px; margin: 0;
+        background: #111; color: #fff;
+        border: 0; border-radius: 999px;
+        font-size: 12px; font-weight: 500; letter-spacing: 0.05em;
+        text-transform: uppercase; cursor: pointer;
+        transition: background 160ms ease, transform 120ms ease;
+      }
+      .pairs-well__cta:hover  { background: #2a2a2a; }
+      .pairs-well__cta:active { transform: scale(0.995); }
+      .pairs-well__cta[aria-expanded="true"] .pairs-well__cta-icon { transform: rotate(45deg); }
+      .pairs-well__cta-icon { display: inline-block; font-size: 22px; line-height: 1; transition: transform 200ms ease; }
+
+      .pairs-well__source-palette { margin: 18px 0 0; }
+      .pairs-well__source-palette h4 { margin: 0 0 8px; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: #777; }
+      .pairs-well__source-palette table { border-collapse: collapse; width: 100%; font-size: 12px; }
+      .pairs-well__source-palette th, .pairs-well__source-palette td { text-align: left; padding: 6px 8px; border-bottom: 1px solid #f0ebe2; }
+      .pairs-well__source-palette th { font-weight: 500; color: #777; font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; }
+      .pairs-well__source-palette td.swatch { width: 28px; }
+
+      .pw-dot    { display: inline-block; width: 18px; height: 18px; border-radius: 50%; border: 1px solid rgba(0,0,0,0.12); vertical-align: middle; }
+      .pw-square { display: inline-block; width: 13px; height: 13px; border-radius: 3px; border: 1px solid rgba(0,0,0,0.12); vertical-align: middle; margin-right: 4px; }
+      .pw-code   { color: #888; font-variant-numeric: tabular-nums; font-size: 11px; }
+
+      .pairs-well__vendor-picker { margin: 18px 0 0; }
+      .pairs-well__vendor-picker h4 { margin: 0 0 8px; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: #777; }
+      .pairs-well__vendor-picker .chips { display: flex; flex-wrap: wrap; gap: 6px; }
+      .pairs-well__vendor-picker label {
+        font-size: 12px; padding: 5px 10px; border: 1px solid #e7e7e7; border-radius: 999px;
+        cursor: pointer; background: #fff; user-select: none;
+      }
+      .pairs-well__vendor-picker label:has(input:checked) { background: #1a1a1a; color: #fff; border-color: #1a1a1a; }
+      .pairs-well__vendor-picker input { display: none; }
+
+      .pairs-well__panel-head {
+        display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;
+        gap: 12px; margin: 24px 0 12px;
+      }
+      .pairs-well__panel-head h3 { margin: 0; font: 500 16px/1.2 ui-serif, Georgia, serif; }
+      .pairs-well__panel-head .count { color: #888; font-size: 12px; margin-left: 6px; font-family: -apple-system, sans-serif; font-weight: 400; }
+      .pairs-well__panel-head .ai-badge {
+        display: inline-block; padding: 3px 8px; background: #2a2a2a; color: #fff;
+        border-radius: 999px; font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
+        margin-left: 8px; font-family: -apple-system, sans-serif; font-weight: 600;
+      }
+      .pairs-well__controls {
+        display: flex; align-items: center; gap: 10px;
+        font-size: 11px; color: #777; text-transform: uppercase; letter-spacing: 0.06em;
+      }
+      .pairs-well__controls input[type=range] { width: 160px; accent-color: #1a1a1a; }
+      .pairs-well__cols-val { font-variant-numeric: tabular-nums; color: #1a1a1a; min-width: 1.5em; text-align: right; font-weight: 600; }
+
+      .pairs-well__grid {
+        display: grid; gap: 14px; margin-top: 6px;
+        grid-template-columns: repeat(var(--cols-catalog), minmax(0, 1fr));
+      }
+      .pairs-well__grid--ai { grid-template-columns: repeat(var(--cols-ai), minmax(0, 1fr)); }
+      @media (max-width: 1100px) { .pairs-well__grid { grid-template-columns: repeat(min(6, var(--cols-catalog)), minmax(0, 1fr)); } .pairs-well__grid--ai { grid-template-columns: repeat(min(6, var(--cols-ai)), minmax(0, 1fr)); } }
+      @media (max-width: 900px)  { .pairs-well__grid { grid-template-columns: repeat(min(4, var(--cols-catalog)), minmax(0, 1fr)); } .pairs-well__grid--ai { grid-template-columns: repeat(min(4, var(--cols-ai)), minmax(0, 1fr)); } }
+      @media (max-width: 600px)  { .pairs-well__grid { grid-template-columns: repeat(min(2, var(--cols-catalog)), minmax(0, 1fr)); } .pairs-well__grid--ai { grid-template-columns: repeat(min(2, var(--cols-ai)), minmax(0, 1fr)); } }
+
+      .pairs-well__card {
+        display: block; text-decoration: none; color: inherit;
+        background: #fff; border: 1px solid #e7e7e7; border-radius: 6px;
+        overflow: hidden; transition: box-shadow 160ms ease, transform 160ms ease;
+      }
+      .pairs-well__card:hover { box-shadow: 0 6px 20px rgba(0,0,0,0.08); transform: translateY(-2px); }
+      .pairs-well__card--ai { border-style: dashed; border-color: #d9c7a8; }
+      .pairs-well__img-wrap { aspect-ratio: 1 / 1; background: #f4f4f4; overflow: hidden; }
+      .pairs-well__img { width: 100%; height: 100%; object-fit: cover; display: block; }
+      .pairs-well__meta { padding: 10px 12px 12px; }
+      .pairs-well__title { margin: 0 0 4px; font-size: 13px; font-weight: 600; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
+      .pairs-well__vendor { margin: 0 0 6px; font-size: 10px; color: #777; letter-spacing: 0.04em; text-transform: uppercase; }
+      .pairs-well__why { display: inline-block; padding: 2px 7px; background: #f4ede4; color: #5a4628; border-radius: 999px; font-size: 10px; line-height: 1.4; margin-bottom: 8px; }
+      .pairs-well__card--ai .pairs-well__why { background: #efe6f5; color: #5b3478; }
+      .pairs-well__palette { display: flex; gap: 3px; margin: 0 0 8px; flex-wrap: wrap; }
+      .pairs-well__palette .pw-dot { width: 12px; height: 12px; }
+      .pairs-well__paints { border-top: 1px dashed #ebe4d8; margin-top: 4px; padding-top: 6px; font-size: 10px; line-height: 1.5; color: #444; }
+      .pairs-well__paints .label { color: #888; font-weight: 600; margin-right: 4px; letter-spacing: 0.04em; }
+      .pairs-well__paints .row { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+
+      .pairs-well__skeleton { background: linear-gradient(90deg, #eee 0%, #f6f6f6 50%, #eee 100%); background-size: 200% 100%; animation: pwShim 1.2s linear infinite; aspect-ratio: 1 / 1; border-radius: 6px; }
+      @keyframes pwShim { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
+      .pairs-well__empty { padding: 18px; color: #777; font-size: 14px; grid-column: 1 / -1; }
+    </style>
+
+    <script>
+      (function () {
+        var root = document.getElementById('pairs-well-with');
+        if (!root) return;
+
+        var endpointPairs = root.getAttribute('data-pairs-endpoint');
+        var endpointAi    = root.getAttribute('data-ai-endpoint');
+        var handle        = root.getAttribute('data-product-handle');
+        var sku           = root.getAttribute('data-product-sku');
+        if (!(sku && sku.length) && !(handle && handle.length)) return;
+        root.hidden = false;
+
+        var cta            = root.querySelector('.pairs-well__cta');
+        var catalogPanel   = document.getElementById('pairs-well-catalog');
+        var catalogGrid    = document.getElementById('pairs-well-grid');
+        var catalogCols    = document.getElementById('pairs-well-cols-catalog');
+        var catalogColsVal = document.getElementById('pairs-well-cols-catalog-val');
+        var catalogCount   = document.getElementById('pairs-well-catalog-count');
+        var aiPanel        = document.getElementById('pairs-well-ai');
+        var aiGrid         = document.getElementById('pairs-well-ai-grid');
+        var aiCols         = document.getElementById('pairs-well-cols-ai');
+        var aiColsVal      = document.getElementById('pairs-well-cols-ai-val');
+        var sourcePalette  = document.getElementById('pairs-well-source-palette');
+        var vendorPicker   = document.getElementById('pairs-well-vendor-picker');
+        var vendorChecks   = vendorPicker.querySelectorAll('input[type=checkbox]');
+        var loaded = false;
+        var cachedPairs = null, cachedAi = null, cachedSource = null;
+
+        var VENDOR_LABEL = { sw:'Sherwin-Williams', de:'Dunn-Edwards', bm:'Benjamin Moore', fb:'Farrow & Ball', behr:'Behr', ppg:'PPG', ral:'RAL' };
+        var VENDOR_SHORT = { sw:'SW', de:'DE', bm:'BM', fb:'F&B', behr:'Behr', ppg:'PPG', ral:'RAL' };
+        var DEFAULT_VENDORS = ['sw','de','bm','fb'];
+
+        function selectedVendors() {
+          var out = [];
+          vendorChecks.forEach(function (cb) { if (cb.checked) out.push(cb.value); });
+          return out.length ? out : DEFAULT_VENDORS.slice();
+        }
+        try {
+          var stored = JSON.parse(localStorage.getItem('pw.vendors') || 'null');
+          if (Array.isArray(stored) && stored.length) {
+            vendorChecks.forEach(function (cb) { cb.checked = stored.indexOf(cb.value) !== -1; });
+          }
+        } catch (_) {}
+        function persistVendors() {
+          try { localStorage.setItem('pw.vendors', JSON.stringify(selectedVendors())); } catch (_) {}
+        }
+        vendorChecks.forEach(function (cb) {
+          cb.addEventListener('change', function () {
+            persistVendors();
+            if (cachedSource) renderSourcePalette(cachedSource.palette);
+            if (cachedPairs && !catalogPanel.hidden) renderGrid(catalogGrid, cachedPairs, false);
+          });
+        });
+
+        function wireSlider(input, valEl, gridEl, varName, storageKey) {
+          function apply() {
+            var n = Math.max(3, Math.min(12, parseInt(input.value, 10) || 8));
+            valEl.textContent = n;
+            gridEl.parentNode.parentNode.style.setProperty('--' + varName, n);
+            root.style.setProperty('--' + varName, n);
+            try { localStorage.setItem(storageKey, n); } catch (_) {}
+          }
+          try {
+            var s = parseInt(localStorage.getItem(storageKey), 10);
+            if (!isNaN(s) && s >= 3 && s <= 12) input.value = s;
+          } catch (_) {}
+          input.addEventListener('input', apply);
+          apply();
+        }
+        wireSlider(catalogCols, catalogColsVal, catalogGrid, 'cols-catalog', 'pw.cols.catalog');
+        wireSlider(aiCols,      aiColsVal,      aiGrid,      'cols-ai',      'pw.cols.ai');
+
+        function esc(s) {
+          return String(s == null ? '' : s)
+            .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
+            .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
+        }
+        function renderSkeletons(grid, n) {
+          var html = '';
+          for (var i = 0; i < n; i++) html += '<div class="pairs-well__skeleton"></div>';
+          grid.innerHTML = html;
+        }
+
+        function renderSourcePalette(palette) {
+          if (!palette || !palette.length) { sourcePalette.hidden = true; vendorPicker.hidden = true; return; }
+          var vendors = selectedVendors();
+          var head = '<th></th>' + vendors.map(function (v) { return '<th>' + esc(VENDOR_LABEL[v]) + '</th>'; }).join('');
+          var body = palette.map(function (c) {
+            var hex = typeof c === 'string' ? c : c.hex;
+            var cells = '<td class="swatch"><span class="pw-dot" style="background:' + esc(hex) + '"></span></td>';
+            vendors.forEach(function (v) {
+              var p = c && c.paints && c.paints[v];
+              cells += '<td>' + (p
+                ? '<span class="pw-square" style="background:' + esc(p.hex) + '"></span>' + esc(p.name) + ' <span class="pw-code">' + esc(p.code) + '</span>'
+                : '—') + '</td>';
+            });
+            return '<tr>' + cells + '</tr>';
+          }).join('');
+          sourcePalette.innerHTML = '<h4>Color story</h4><table><thead><tr>' + head + '</tr></thead><tbody>' + body + '</tbody></table>';
+          sourcePalette.hidden = false;
+          vendorPicker.hidden = false;
+        }
+
+        function renderCard(p, isAi) {
+          var img = p.image_url ? esc(p.image_url) : '';
+          var why = (p.why && p.why[0]) ? esc(p.why[0]) : (p.motif ? esc(p.motif) : '');
+          var palette = (p.palette || []).map(function (c) {
+            var hex = typeof c === 'string' ? c : c.hex;
+            var sw = c && c.paints && c.paints.sw, de = c && c.paints && c.paints.de;
+            var tip = (sw ? 'SW ' + sw.name + ' ' + sw.code : '') + (de ? ' · DE ' + de.name + ' ' + de.code : '');
+            return '<span class="pw-dot" style="background:' + esc(hex) + '" title="' + esc(tip) + '"></span>';
+          }).join('');
+          var dom = (p.palette && p.palette[0]) || null;
+          var paintsBlock = '';
+          if (dom && dom.paints) {
+            var vendors = selectedVendors();
+            var rows = vendors.map(function (v) {
+              var paint = dom.paints[v];
+              if (!paint) return '';
+              return '<span class="row"><span class="label">' + esc(VENDOR_SHORT[v]) + '</span>'
+                + '<span class="pw-square" style="background:' + esc(paint.hex) + '"></span>'
+                + esc(paint.name) + ' <span class="pw-code">' + esc(paint.code) + '</span></span>';
+            }).filter(Boolean).join('');
+            if (rows) paintsBlock = '<div class="pairs-well__paints">' + rows + '</div>';
+          }
+          var href = p.url ? esc(p.url) : (p.handle ? '/products/' + esc(p.handle) : '#');
+          return '<a class="pairs-well__card' + (isAi ? ' pairs-well__card--ai' : '') + '" href="' + href + '" title="' + esc((p.why || []).join(' · ')) + '">'
+            +   '<div class="pairs-well__img-wrap">' + (img ? '<img class="pairs-well__img" src="' + img + '" alt="' + esc(p.title) + '" loading="lazy">' : '') + '</div>'
+            +   '<div class="pairs-well__meta">'
+            +     '<p class="pairs-well__title">' + esc(p.title || '') + '</p>'
+            +     '<p class="pairs-well__vendor">' + esc(p.vendor || (isAi ? 'Auto Generated' : '')) + '</p>'
+            +     (why ? '<span class="pairs-well__why">' + why + '</span>' : '')
+            +     (palette ? '<div class="pairs-well__palette">' + palette + '</div>' : '')
+            +     paintsBlock
+            +   '</div>'
+            + '</a>';
+        }
+        function renderGrid(grid, items, isAi) {
+          if (!items || !items.length) { grid.innerHTML = '<div class="pairs-well__empty">No coordinates found for this pattern.</div>'; return; }
+          grid.innerHTML = items.map(function (p) { return renderCard(p, isAi); }).join('');
+        }
+
+        function buildUrl(base, limit) {
+          var qs = [];
+          if (sku)    qs.push('dw_sku=' + encodeURIComponent(sku));
+          if (handle) qs.push('handle=' + encodeURIComponent(handle));
+          if (limit)  qs.push('limit=' + limit);
+          return base + '?' + qs.join('&');
+        }
+
+        function load() {
+          if (loaded) return Promise.resolve();
+          loaded = true;
+          renderSkeletons(catalogGrid, 6);
+          renderSkeletons(aiGrid, 6);
+          catalogPanel.hidden = false;
+          aiPanel.hidden = false;
+
+          var pairsPromise = fetch(buildUrl(endpointPairs, 6), { credentials: 'omit' })
+            .then(function (r) { return r.ok ? r.json() : Promise.reject(new Error('http ' + r.status)); })
+            .then(function (d) {
+              if (!d || d.ok === false) throw new Error((d && d.error) || 'bad payload');
+              cachedSource = d.source || null;
+              cachedPairs  = d.pairs  || [];
+              if (cachedSource) renderSourcePalette(cachedSource.palette || []);
+              renderGrid(catalogGrid, cachedPairs, false);
+              catalogCount.textContent = '(' + cachedPairs.length + ')';
+              if (!cachedPairs.length && (!cachedSource || !cachedSource.palette || !cachedSource.palette.length)) {
+                root.hidden = true;
+              }
+            })
+            .catch(function () { catalogGrid.innerHTML = '<div class="pairs-well__empty">Could not load coordinates right now.</div>'; });
+
+          var aiPromise = fetch(buildUrl(endpointAi, 6), { credentials: 'omit' })
+            .then(function (r) { return r.ok ? r.json() : Promise.reject(new Error('http ' + r.status)); })
+            .then(function (d) {
+              if (!d || d.ok === false) throw new Error('bad payload');
+              cachedAi = d.items || [];
+              renderGrid(aiGrid, cachedAi, true);
+            })
+            .catch(function () { aiGrid.innerHTML = '<div class="pairs-well__empty">Auto Generated coordinates not ready.</div>'; });
+
+          return Promise.all([pairsPromise, aiPromise]);
+        }
+
+        cta.addEventListener('click', function () {
+          var open = cta.getAttribute('aria-expanded') === 'true';
+          if (open) {
+            cta.setAttribute('aria-expanded', 'false');
+            sourcePalette.hidden = true;
+            vendorPicker.hidden = true;
+            catalogPanel.hidden = true;
+            aiPanel.hidden = true;
+            return;
+          }
+          cta.setAttribute('aria-expanded', 'true');
+          load();
+        });
+      })();
+    </script>
\ No newline at end of file
diff --git a/shopify/push-section-to-dev-theme.sh b/shopify/push-section-to-dev-theme.sh
new file mode 100755
index 00000000..0edb1244
--- /dev/null
+++ b/shopify/push-section-to-dev-theme.sh
@@ -0,0 +1,112 @@
+#!/usr/bin/env bash
+# Push ONE theme section asset to the Shopify DEV theme "Updated copy of NewWall
+# Template [Dev]" — drift-safe. Never touches the live/main theme.
+#
+# Backs up the dev theme's CURRENT copy of the asset, PUTs the local file,
+# then GETs it back and verifies expected markers are present.
+#
+# Defaults to the collection-grouped section. Override via env:
+#   SECTION_FILE=_cwGRID/sections/collection-grouped.liquid   # local source
+#   ASSET_KEY=sections/collection-grouped.liquid              # remote asset key
+#   VERIFY_MARKER=dw-grouped__lbtn                            # must be in pushed asset
+#
+# Needs a theme-scoped token (auto-read from secrets-manager/.env, or pass it):
+#   THEME_TOKEN=shpat_xxx bash push-section-to-dev-theme.sh
+# Read-only validation (lists theme + backs up, NO write):
+#   DRYRUN=1 bash push-section-to-dev-theme.sh
+set -euo pipefail
+cd "$(dirname "$0")"
+
+STORE="designer-laboratory-sandbox.myshopify.com"
+API="2024-10"
+SECRETS="$HOME/Projects/secrets-manager/.env"
+TARGET_NAME="Updated copy of NewWall Template [Dev]"
+SECTION_FILE="${SECTION_FILE:-_cwGRID/sections/collection-grouped.liquid}"
+ASSET_KEY="${ASSET_KEY:-sections/collection-grouped.liquid}"
+VERIFY_MARKER="${VERIFY_MARKER:-dw-grouped__lbtn}"
+DRYRUN="${DRYRUN:-0}"
+
+[ -f "$SECTION_FILE" ] || { echo "Local section not found: $SECTION_FILE"; exit 1; }
+
+TARGET_THEME_ID="${TARGET_THEME_ID:-}"
+
+TOK="${THEME_TOKEN:-$(grep -hE '^SHOPIFY_THEME_TOKEN=' "$SECRETS" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"'"'"' ')}"
+[ -z "$TOK" ] && { echo "No theme token. Run:  THEME_TOKEN=shpat_xxx bash push-section-to-dev-theme.sh"; exit 1; }
+echo "token …${TOK: -4}  | asset: $ASSET_KEY  | theme: ${TARGET_THEME_ID:-auto}  | dryrun: $DRYRUN"
+
+mkdir -p theme-backups
+python3 - "$STORE" "$API" "$TOK" "$TARGET_NAME" "theme-backups" "$SECTION_FILE" "$ASSET_KEY" "$VERIFY_MARKER" "$DRYRUN" "$TARGET_THEME_ID" <<'PY'
+import sys, json, urllib.request, urllib.parse, urllib.error, datetime
+store, api, tok, target_name, bkdir, section_file, asset_key, marker, dryrun, forced_id = sys.argv[1:11]
+H = {"X-Shopify-Access-Token": tok, "Content-Type": "application/json"}
+def get(url): return json.load(urllib.request.urlopen(urllib.request.Request(url, headers=H)))
+def put(url, body):
+    req = urllib.request.Request(url, data=json.dumps(body).encode(), headers=H, method="PUT")
+    return json.load(urllib.request.urlopen(req))
+
+try:
+    themes = get(f"https://{store}/admin/api/{api}/themes.json")["themes"]
+except urllib.error.HTTPError as e:
+    print("THEMES LIST FAILED HTTP", e.code, e.read().decode()[:300]); sys.exit(1)
+def asset_exists(tid):
+    q = urllib.parse.urlencode({"asset[key]": asset_key})
+    try:
+        get(f"https://{store}/admin/api/{api}/themes/{tid}/assets.json?{q}"); return True
+    except urllib.error.HTTPError as e:
+        if e.code == 404: return False
+        raise
+
+print("themes:")
+for t in themes: print("  ", t["id"], t["role"], "|", t["name"])
+
+if forced_id:
+    theme = next((t for t in themes if str(t["id"]) == str(forced_id)), None)
+    if not theme: print(f"TARGET_THEME_ID {forced_id} not in theme list."); sys.exit(1)
+    if theme["role"] == "main": print("Refusing to push to the MAIN (live) theme."); sys.exit(1)
+else:
+    # Self-correcting: update IN PLACE wherever the section already lives (non-main).
+    havers = [t for t in themes if t["role"] != "main" and asset_exists(t["id"])]
+    if len(havers) == 1:
+        theme = havers[0]
+    elif len(havers) > 1:
+        print("Section exists on MULTIPLE dev themes — pass TARGET_THEME_ID=<id>:")
+        for t in havers: print("   ", t["id"], "|", t["name"])
+        sys.exit(1)
+    else:
+        # not anywhere yet → fall back to named dev theme, then any development-role theme
+        cand = [t for t in themes if t["name"] == target_name] or [t for t in themes if t["role"]=="development"]
+        cand = [t for t in cand if t["role"] != "main"]
+        if not cand: print("No target theme found; pass TARGET_THEME_ID=<id>."); sys.exit(1)
+        theme = cand[0]
+tid = theme["id"]
+print(f'=== target: {tid} "{theme["name"]}" (role={theme["role"]}) — live (main) untouched ===')
+
+# Back up current remote copy (if the asset already exists on the dev theme)
+q = urllib.parse.urlencode({"asset[key]": asset_key})
+stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
+try:
+    cur = get(f"https://{store}/admin/api/{api}/themes/{tid}/assets.json?{q}")["asset"]["value"]
+    safe = asset_key.replace("/", "_")
+    bk = f"{bkdir}/{safe}.dev-{tid}.{stamp}.bak"
+    open(bk, "w").write(cur); print(f"backup: {bk} ({len(cur)} bytes)")
+except urllib.error.HTTPError as e:
+    if e.code == 404: print("note: asset does not exist on dev theme yet (will be created)")
+    else: print("backup GET failed HTTP", e.code, e.read().decode()[:200]); sys.exit(1)
+
+new = open(section_file).read()
+if marker and marker not in new:
+    print(f'⚠️  verify marker "{marker}" NOT in local file — aborting (wrong file?)'); sys.exit(1)
+print(f"local source: {section_file} ({len(new)} bytes, marker ok)")
+
+if dryrun == "1":
+    print("── DRYRUN: skipping PUT. Re-run without DRYRUN=1 to push. ──"); sys.exit(0)
+
+put(f"https://{store}/admin/api/{api}/themes/{tid}/assets.json",
+    {"asset": {"key": asset_key, "value": new}})
+print("PUT ok — verifying…")
+back = get(f"https://{store}/admin/api/{api}/themes/{tid}/assets.json?{q}")["asset"]["value"]
+ok = (marker in back) and (len(back) == len(new))
+print(f'verify: marker={"present" if marker in back else "MISSING"}  size remote={len(back)} local={len(new)}')
+print("── PUSH OK ──" if ok else "── PUSH WROTE but verify mismatch — check the theme editor ──")
+print(f"preview: https://{store.replace('.myshopify.com','')}.myshopify.com/admin/themes/{tid}/editor")
+PY
diff --git a/shopify/surgical-dc-v7-on-dev-theme.sh b/shopify/surgical-dc-v7-on-dev-theme.sh
new file mode 100755
index 00000000..c425abc6
--- /dev/null
+++ b/shopify/surgical-dc-v7-on-dev-theme.sh
@@ -0,0 +1,98 @@
+#!/usr/bin/env bash
+# Surgically apply the Design Coordinate v7 tweaks (Auto Generated rename + 6+6)
+# to the embedded block inside a dev theme's sections/product.liquid.
+# Each edit asserts an EXACT match count; if any is off, the whole run aborts
+# with NO write. Backs up product.liquid, PUTs, verifies. Never main/live.
+#
+#   DRYRUN=1 bash surgical-dc-v7-on-dev-theme.sh    # show edits, no write
+#   bash surgical-dc-v7-on-dev-theme.sh             # apply
+#   TARGET_THEME_ID=<id> ...                         # default 144000057395
+set -euo pipefail
+cd "$(dirname "$0")"
+STORE="designer-laboratory-sandbox.myshopify.com"; API="2024-10"
+SECRETS="$HOME/Projects/secrets-manager/.env"
+HOST="sections/product.liquid"
+TID="${TARGET_THEME_ID:-144000057395}"
+DRYRUN="${DRYRUN:-0}"
+TOK="${THEME_TOKEN:-$(grep -hE '^SHOPIFY_THEME_TOKEN=' "$SECRETS" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"'"'"' ')}"
+[ -z "$TOK" ] && { echo "No theme token."; exit 1; }
+echo "token …${TOK: -4} | theme $TID | dryrun $DRYRUN"
+mkdir -p theme-backups
+python3 - "$STORE" "$API" "$TOK" "$TID" "$HOST" "theme-backups" "$DRYRUN" <<'PY'
+import sys, json, urllib.request, urllib.parse, urllib.error, datetime, time
+store, api, tok, tid, host, bkdir, dryrun = sys.argv[1:8]
+H={"X-Shopify-Access-Token":tok,"Content-Type":"application/json"}
+def get(u): return json.load(urllib.request.urlopen(urllib.request.Request(u,headers=H)))
+def put(u,b): return json.load(urllib.request.urlopen(urllib.request.Request(u,data=json.dumps(b).encode(),headers=H,method="PUT")))
+def asset(k):
+    q=urllib.parse.urlencode({"asset[key]":k})
+    return get(f"https://{store}/admin/api/{api}/themes/{tid}/assets.json?{q}")["asset"]["value"]
+
+# guard: never main
+themes=get(f"https://{store}/admin/api/{api}/themes.json")["themes"]
+th=next((t for t in themes if str(t["id"])==str(tid)),None)
+if not th: print("theme not found"); sys.exit(1)
+if th["role"]=="main": print("refusing main/live"); sys.exit(1)
+print(f'target: {tid} "{th["name"]}" ({th["role"]})')
+
+p=asset(host); orig=p
+# (old, new, expected_count)
+EDITS=[
+ ('<h3>AI Generated Coordinates <span class="ai-badge">Preview · 24 options</span></h3>',
+  '<h3>Auto Generated <span class="ai-badge">Preview · 6 options</span></h3>', 1),
+ ('fetch(buildUrl(endpointPairs, 24)', 'fetch(buildUrl(endpointPairs, 6)', 1),
+ ('fetch(buildUrl(endpointAi, 24)',    'fetch(buildUrl(endpointAi, 6)', 1),
+ ('renderSkeletons(catalogGrid, 12);', 'renderSkeletons(catalogGrid, 6);', 1),
+ ('renderSkeletons(aiGrid, 12);',      'renderSkeletons(aiGrid, 6);', 1),
+ ("(p.motif ? 'AI ' + esc(p.motif) : '')", "(p.motif ? esc(p.motif) : '')", 1),
+ ("(isAi ? 'AI-Generated' : '')",      "(isAi ? 'Auto Generated' : '')", 1),
+ ('">AI generator not ready.</div>',   '">Auto Generated coordinates not ready.</div>', 1),
+ ('#pairs-well-with { font-family: inherit; --cols-catalog: 8; --cols-ai: 8; }',
+  '#pairs-well-with { font-family: inherit; --cols-catalog: 6; --cols-ai: 6; }', 1),
+ ('<input id="pairs-well-cols-catalog" type="range" min="3" max="12" value="8" step="1">',
+  '<input id="pairs-well-cols-catalog" type="range" min="3" max="12" value="6" step="1">', 1),
+ ('id="pairs-well-cols-catalog-val">8</span>', 'id="pairs-well-cols-catalog-val">6</span>', 1),
+ ('<input id="pairs-well-cols-ai" type="range" min="3" max="12" value="8" step="1">',
+  '<input id="pairs-well-cols-ai" type="range" min="3" max="12" value="6" step="1">', 1),
+ ('id="pairs-well-cols-ai-val">8</span>', 'id="pairs-well-cols-ai-val">6</span>', 1),
+ ('.pairs-well__panel-head h3 { margin: 0; font: 500 18px/1.2 ui-serif, Georgia, serif; }',
+  '.pairs-well__panel-head h3 { margin: 0; font: 500 16px/1.2 ui-serif, Georgia, serif; }', 1),
+ ('• Catalog grid: 24 stripe/plaid/check coordinates that use the source palette',
+  '• Catalog grid: 6 stripe/plaid/check coordinates that use the source palette', 1),
+ ('• Density slider 3-12 cols (default 8), localStorage-persisted',
+  '• Density slider 3-12 cols (default 6), localStorage-persisted', 1),
+ ('• AI Generated Coordinates: 24 SVG-poster placeholders seeded from palette',
+  '• Auto Generated: 6 SVG-poster placeholders seeded from palette', 1),
+]
+errs=[]
+for old,new,exp in EDITS:
+    c=p.count(old)
+    if c!=exp: errs.append(f"[{exp} expected, {c} found] {old[:60]}…"); continue
+    p=p.replace(old,new)
+if errs:
+    print("ABORT — edit anchors did not match exactly (no write):")
+    for e in errs: print("  -",e)
+    sys.exit(1)
+
+# post guards
+g=[]
+if 'AI Generated Coordinates' in p: g.append("old AI heading still present")
+if 'Auto Generated' not in p: g.append("Auto Generated missing")
+if p.count('id="pairs-well-with"')!=1: g.append("pairs-well-with count != 1")
+if abs(len(p)-len(orig))>400: g.append(f"size delta too big ({len(orig)}->{len(p)})")
+if g:
+    print("ABORT — post guards failed (no write):"); [print("  -",x) for x in g]; sys.exit(1)
+print(f"all {len(EDITS)} edits applied · {len(orig)} -> {len(p)} bytes")
+
+stamp=datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
+bk=f"{bkdir}/{host.replace('/','_')}.dev-{tid}.{stamp}.bak"
+open(bk,"w").write(orig); print("backup:",bk)
+
+if dryrun=="1": print("── DRYRUN: no write ──"); sys.exit(0)
+put(f"https://{store}/admin/api/{api}/themes/{tid}/assets.json", {"asset":{"key":host,"value":p}})
+time.sleep(6)
+back=asset(host)
+ok = ('Auto Generated' in back) and ('AI Generated Coordinates' not in back) and ('buildUrl(endpointAi, 6)' in back)
+print("verify:", "OK" if ok else "MISMATCH (restore from backup if wrong)")
+print(f"preview: https://{store}/admin/themes/{tid}/editor")
+PY

← 68820077 chore: lint + refactor newwall remap/scrub scripts, token gu  ·  back to Designer Wallcoverings  ·  auto-save: 2026-06-26T11:45:25 (8 files) — pending-approval/ 669dc5bb →