← back to Designer Wallcoverings
chore: lint + refactor newwall remap/scrub scripts, token guard, version-up (session close)
68820077967ca88bdc71015eee7372fdd6038606 · 2026-06-26 10:51:45 -0700 · Steve
Files touched
A audits/designtex-uom-fix/220216-live-2026-06-26.pngM package-lock.jsonM package.jsonM shopify/_cwGRID/sections/collection-grouped.liquidM shopify/scripts/scrub-newwall-metafields.js
Diff
commit 68820077967ca88bdc71015eee7372fdd6038606
Author: Steve <steve@designerwallcoverings.com>
Date: Fri Jun 26 10:51:45 2026 -0700
chore: lint + refactor newwall remap/scrub scripts, token guard, version-up (session close)
---
.../designtex-uom-fix/220216-live-2026-06-26.png | Bin 0 -> 3332038 bytes
package-lock.json | 4 +-
package.json | 2 +-
shopify/_cwGRID/sections/collection-grouped.liquid | 140 +++++++++++++++++----
shopify/scripts/scrub-newwall-metafields.js | 19 +--
5 files changed, 128 insertions(+), 37 deletions(-)
diff --git a/audits/designtex-uom-fix/220216-live-2026-06-26.png b/audits/designtex-uom-fix/220216-live-2026-06-26.png
new file mode 100644
index 00000000..d13cd951
Binary files /dev/null and b/audits/designtex-uom-fix/220216-live-2026-06-26.png differ
diff --git a/package-lock.json b/package-lock.json
index c9b87304..e4d53820 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "designer-wallcoverings",
- "version": "1.0.1",
+ "version": "1.0.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "designer-wallcoverings",
- "version": "1.0.1",
+ "version": "1.0.2",
"dependencies": {
"@anthropic-ai/sdk": "^0.71.2",
"@google-cloud/vision": "^5.3.4",
diff --git a/package.json b/package.json
index bd745918..75681c27 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "designer-wallcoverings",
- "version": "1.0.1",
+ "version": "1.0.2",
"description": "Designer Wallcoverings Shopify Management",
"scripts": {
"build": "tsc --noEmit",
diff --git a/shopify/_cwGRID/sections/collection-grouped.liquid b/shopify/_cwGRID/sections/collection-grouped.liquid
index 21771efe..51754c02 100644
--- a/shopify/_cwGRID/sections/collection-grouped.liquid
+++ b/shopify/_cwGRID/sections/collection-grouped.liquid
@@ -75,27 +75,40 @@
<h1 class="dw-grouped__title">{{ collection.title }}</h1>
{%- endif -%}
- <div class="dw-grouped__toolbar">
- <div class="dw-grouped__count" id="dw-grouped-count"></div>
- <div class="dw-grouped__controls">
- <div class="dw-grouped__cats" id="dw-grouped-cats" role="tablist" aria-label="Filter and sort">
- <button type="button" class="dw-grouped__chipbtn" data-cat="color">Color</button>
- <button type="button" class="dw-grouped__chipbtn" data-cat="style">Style</button>
- <button type="button" class="dw-grouped__chipbtn is-active" data-cat="sort">Sort</button>
+ <div class="dw-grouped__rail" id="dw-grouped-rail">
+ <div class="dw-grouped__toolbar">
+ <div class="dw-grouped__count" id="dw-grouped-count"></div>
+ <div class="dw-grouped__controls">
+ <div class="dw-grouped__cats" id="dw-grouped-cats" role="tablist" aria-label="Filter and sort">
+ <button type="button" class="dw-grouped__chipbtn" data-cat="color">Color</button>
+ <button type="button" class="dw-grouped__chipbtn" data-cat="style">Style</button>
+ <button type="button" class="dw-grouped__chipbtn is-active" data-cat="sort">Sort</button>
+ </div>
+ <label class="dw-grouped__ctl">
+ <span>Density</span>
+ <input id="dw-grouped-density" type="range" min="2" max="7" step="1" value="4">
+ <span class="dw-grouped__density-val" id="dw-grouped-density-val">4</span>
+ </label>
+ <label class="dw-grouped__ctl">
+ <span>Padding</span>
+ <input id="dw-grouped-pad" type="range" min="0" max="28" step="2" value="10">
+ <span class="dw-grouped__density-val" id="dw-grouped-pad-val">10</span>
+ </label>
+ <div class="dw-grouped__ctl dw-grouped__layout" role="group" aria-label="Filter layout">
+ <span>View</span>
+ <button type="button" class="dw-grouped__lbtn is-active" data-layout="top" title="Filters across the top" aria-label="Filters across the top">▤</button>
+ <button type="button" class="dw-grouped__lbtn" data-layout="left" title="Filters on the left" aria-label="Filters on the left">▥</button>
+ </div>
</div>
- <label class="dw-grouped__ctl">
- <span>Density</span>
- <input id="dw-grouped-density" type="range" min="2" max="7" step="1" value="4">
- <span class="dw-grouped__density-val" id="dw-grouped-density-val">4</span>
- </label>
</div>
+ <div class="dw-grouped__subbar" id="dw-grouped-sub" aria-label="Options"></div>
</div>
- <div class="dw-grouped__subbar" id="dw-grouped-sub" aria-label="Options"></div>
- <div id="dw-grouped-grid" class="dw-grouped__grid" role="list"></div>
-
- <div id="dw-grouped-sentinel" class="dw-grouped__sentinel" hidden>
- <span id="dw-grouped-status">Loading more…</span>
+ <div class="dw-grouped__main">
+ <div id="dw-grouped-grid" class="dw-grouped__grid" role="list"></div>
+ <div id="dw-grouped-sentinel" class="dw-grouped__sentinel" hidden>
+ <span id="dw-grouped-status">Loading more…</span>
+ </div>
</div>
</section>
@@ -106,7 +119,7 @@
body.dw-grouped--active .collection-products,
body.dw-grouped--active .boost-pfs-filter-products { display: none !important; }
- #dw-grouped { --cols: 4; font-family: inherit; margin: 0 0 3rem; }
+ #dw-grouped { --cols: 4; --pad: 10px; font-family: inherit; margin: 0 0 3rem; }
.dw-grouped__hero {
position: relative; min-height: 320px; background-size: cover; background-position: center;
@@ -146,7 +159,13 @@
@media (max-width: 1024px) { .dw-grouped__grid { grid-template-columns: repeat(min(3, var(--cols)), minmax(0,1fr)); } }
@media (max-width: 700px) { .dw-grouped__grid { grid-template-columns: repeat(2, minmax(0,1fr)); } }
- .dw-grouped__card { display: block; text-decoration: none; color: inherit; }
+ /* Each tile ("chip") sits in a padded frame; the Padding slider drives --pad all around. */
+ .dw-grouped__card {
+ display: block; text-decoration: none; color: inherit;
+ padding: var(--pad); background: #fff; border: 1px solid #ececec; border-radius: 8px;
+ transition: box-shadow .16s ease, transform .16s ease;
+ }
+ .dw-grouped__card:hover { box-shadow: 0 6px 20px rgba(0,0,0,.07); transform: translateY(-2px); }
.dw-grouped__img-wrap { position: relative; aspect-ratio: 1/1; background: #f4f4f4; border-radius: 4px; overflow: hidden; }
.dw-grouped__img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .4s ease; }
/* truly-dead image (no live fallback) → clean neutral tile, never a broken-icon + alt text */
@@ -169,6 +188,30 @@
.dw-grouped__skel { aspect-ratio: 1/1; border-radius: 4px; background: linear-gradient(90deg,#eee 0%,#f6f6f6 50%,#eee 100%); background-size: 200% 100%; animation: dwgShim 1.2s linear infinite; }
@keyframes dwgShim { 0%{background-position:200% 0} 100%{background-position:-200% 0} }
.dw-grouped__sentinel { text-align: center; padding: 26px; color: #999; font-size: 12px; }
+
+ /* Layout toggle (filters across the top vs. docked on the left) */
+ .dw-grouped__layout { gap: 5px; }
+ .dw-grouped__lbtn {
+ font-size: 14px; line-height: 1; padding: 5px 8px; border: 1px solid #ddd;
+ border-radius: 6px; background: #fff; color: #777; cursor: pointer;
+ }
+ .dw-grouped__lbtn:hover { border-color: #1a1a1a; color: #1a1a1a; }
+ .dw-grouped__lbtn.is-active { background: #1a1a1a; color: #fff; border-color: #1a1a1a; }
+
+ /* === Vertical layout: search info docked as a left rail, grid to its right === */
+ #dw-grouped.is-vrail { display: grid; grid-template-columns: 232px minmax(0, 1fr); column-gap: 30px; align-items: start; }
+ #dw-grouped.is-vrail .dw-grouped__hero,
+ #dw-grouped.is-vrail .dw-grouped__title { grid-column: 1 / -1; }
+ #dw-grouped.is-vrail .dw-grouped__rail { grid-column: 1; position: sticky; top: 16px; align-self: start; }
+ #dw-grouped.is-vrail .dw-grouped__main { grid-column: 2; min-width: 0; }
+ #dw-grouped.is-vrail .dw-grouped__toolbar { flex-direction: column; align-items: flex-start; gap: 16px; border-bottom: 0; padding: 0 0 12px; }
+ #dw-grouped.is-vrail .dw-grouped__controls { flex-direction: column; align-items: flex-start; gap: 16px; }
+ #dw-grouped.is-vrail .dw-grouped__cats { flex-wrap: wrap; }
+ #dw-grouped.is-vrail .dw-grouped__subbar { margin: 0 0 8px; }
+ @media (max-width: 760px) {
+ #dw-grouped.is-vrail { display: block; }
+ #dw-grouped.is-vrail .dw-grouped__rail { position: static; }
+ }
</style>
<script>
@@ -194,6 +237,9 @@
var hueFilter = '', styleFilter = '';
var densEl = document.getElementById('dw-grouped-density');
var densVal = document.getElementById('dw-grouped-density-val');
+ var padEl = document.getElementById('dw-grouped-pad');
+ var padVal = document.getElementById('dw-grouped-pad-val');
+ var facets = { color: [], style: [] }; // populated filter values, from the feed
var PER = 48, page = 0, hasNext = true, loading = false, total = null;
@@ -213,12 +259,45 @@
try { var s = parseInt(localStorage.getItem('dwg.cols'), 10); if (s >= 2 && s <= 7) densEl.value = s; } catch (_) {}
densEl.addEventListener('input', apply); apply();
}
+ // Padding slider — drives --pad (space all around each tile/chip), localStorage-persisted.
+ function wirePad() {
+ function apply() {
+ var n = Math.max(0, Math.min(28, parseInt(padEl.value, 10) || 0));
+ padVal.textContent = n; root.style.setProperty('--pad', n + 'px');
+ try { localStorage.setItem('dwg.pad', n); } catch (_) {}
+ }
+ try { var s = parseInt(localStorage.getItem('dwg.pad'), 10); if (s >= 0 && s <= 28) padEl.value = s; } catch (_) {}
+ padEl.addEventListener('input', apply); apply();
+ }
+ // Layout toggle — filters across the top (default) or docked as a left rail. Persisted.
+ function wireLayout() {
+ var btns = root.querySelectorAll('.dw-grouped__lbtn');
+ function apply(mode) {
+ root.classList.toggle('is-vrail', mode === 'left');
+ btns.forEach(function (b) { b.classList.toggle('is-active', b.getAttribute('data-layout') === mode); });
+ try { localStorage.setItem('dwg.layout', mode); } catch (_) {}
+ }
+ var saved = 'top';
+ try { var s = localStorage.getItem('dwg.layout'); if (s === 'left' || s === 'top') saved = s; } catch (_) {}
+ btns.forEach(function (b) { b.addEventListener('click', function () { apply(b.getAttribute('data-layout')); }); });
+ apply(saved);
+ }
// Two-tier controls: a category chip (Color/Style/Sort) reveals its values on the next line.
- var VALUES = {
- color: [['','All'],['red','Red'],['orange','Orange'],['yellow','Yellow'],['green','Green'],['teal','Teal'],['blue','Blue'],['purple','Purple'],['pink','Pink'],['neutral','Neutral']],
- style: [['','All'],['floral','Floral'],['geometric','Geometric'],['damask','Damask'],['stripe','Stripe'],['toile','Toile'],['trellis','Trellis'],['tropical','Tropical'],['textured','Textured'],['grasscloth','Grasscloth'],['solid','Solid']],
- sort: [['colors_desc','Most Colors'],['newest','Newest'],['pattern_az','Pattern A–Z'],['vendor_az','Vendor A–Z']]
- };
+ // Color/Style values are driven by the feed's FACETS so only values that actually have
+ // products in THIS collection render — no dead tab that returns 0 results. Sort is fixed.
+ var COLOR_LABEL = { red:'Red', orange:'Orange', yellow:'Yellow', green:'Green', teal:'Teal', blue:'Blue', purple:'Purple', pink:'Pink', neutral:'Neutral' };
+ var SORT_VALUES = [['colors_desc','Most Colors'],['newest','Newest'],['pattern_az','Pattern A–Z'],['vendor_az','Vendor A–Z']];
+ function titleize(s) { return String(s).replace(/\b\w/g, function (c) { return c.toUpperCase(); }); }
+ function valuesFor(cat) {
+ if (cat === 'sort') return SORT_VALUES;
+ var keys = (facets[cat] || []).slice().sort(function (a, b) {
+ var la = cat === 'color' ? (COLOR_LABEL[a] || a) : a, lb = cat === 'color' ? (COLOR_LABEL[b] || b) : b;
+ return String(la).localeCompare(String(lb));
+ });
+ var opts = [['', 'All']];
+ keys.forEach(function (k) { opts.push([k, cat === 'color' ? (COLOR_LABEL[k] || titleize(k)) : titleize(k)]); });
+ return opts;
+ }
var activeCat = 'sort';
function currentValFor(cat) { return cat === 'color' ? hueFilter : cat === 'style' ? styleFilter : currentSort; }
function renderSub(cat) {
@@ -227,7 +306,12 @@
b.classList.toggle('is-active', b.getAttribute('data-cat') === cat);
});
var cur = currentValFor(cat);
- subWrap.innerHTML = VALUES[cat].map(function (v) {
+ var vals = valuesFor(cat);
+ if ((cat === 'color' || cat === 'style') && vals.length <= 1) {
+ subWrap.innerHTML = '<span style="font-size:11px;color:#999;padding:6px 2px">No ' + cat + ' filters for this collection</span>';
+ return;
+ }
+ subWrap.innerHTML = vals.map(function (v) {
return '<button type="button" class="dw-grouped__chipbtn dw-grouped__subbtn' + (v[0] === cur ? ' is-active' : '') + '" data-val="' + v[0] + '">' + v[1] + '</button>';
}).join('');
}
@@ -338,6 +422,10 @@
if (next === 1 && !tiles.length) { root.hidden = true; document.body.classList.remove('dw-grouped--active'); return; }
grid.insertAdjacentHTML('beforeend', tiles.map(card).join(''));
healEmpty();
+ if (next === 1 && d.facets) {
+ facets = { color: d.facets.color || [], style: d.facets.style || [] };
+ if (activeCat === 'color' || activeCat === 'style') renderSub(activeCat);
+ }
page = next;
total = d.total_tiles;
hasNext = !!d.has_next;
@@ -354,7 +442,7 @@
});
}
- wireDensity(); wireControls();
+ wireDensity(); wirePad(); wireLayout(); wireControls();
// Own the page immediately (we're the grid now) — toolbar + skeletons show on load,
// not after the feed responds.
document.body.classList.add('dw-grouped--active');
diff --git a/shopify/scripts/scrub-newwall-metafields.js b/shopify/scripts/scrub-newwall-metafields.js
index 3861cc64..3737b347 100644
--- a/shopify/scripts/scrub-newwall-metafields.js
+++ b/shopify/scripts/scrub-newwall-metafields.js
@@ -6,23 +6,26 @@
* Targets the 74 by handle (vendor already fixed, so can't query vendor:Newwall).
* APPLY=1 to write. Brand map: /tmp/newwall_brand_map.tsv ; handles: /tmp/newwall_live.tsv
*/
-const fs = require('path') && require('fs');
+const fs = require('fs');
const path = require('path');
const ROOT = path.resolve(__dirname, '..', '..');
-const env = Object.fromEntries(fs.readFileSync(path.join(ROOT, '.env'), 'utf8').split('\n')
- .filter(l => l.includes('=') && !l.trim().startsWith('#'))
- .map(l => { const i = l.indexOf('='); return [l.slice(0, i).trim(), l.slice(i + 1).trim()]; }));
-const S = (env.SHOPIFY_STORE_DOMAIN || 'designer-laboratory-sandbox.myshopify.com').replace(/^https?:\/\//, '');
-const T = env.SHOPIFY_ADMIN_TOKEN;
+const env = Object.fromEntries(
+ fs.readFileSync(path.join(ROOT, '.env'), 'utf8')
+ .split('\n').filter(l => l.includes('=') && !l.trim().startsWith('#'))
+ .map(l => { const i = l.indexOf('='); return [l.slice(0, i).trim(), l.slice(i + 1).trim()]; })
+);
+const STORE = (env.SHOPIFY_STORE_DOMAIN || 'designer-laboratory-sandbox.myshopify.com').replace(/^https?:\/\//, '');
+const TOKEN = env.SHOPIFY_ADMIN_TOKEN || env.SHOPIFY_ADMIN_ACCESS_TOKEN;
const APPLY = process.env.APPLY === '1';
+if (!TOKEN) { console.error('No SHOPIFY_ADMIN_TOKEN in .env'); process.exit(1); }
const brand = Object.fromEntries(fs.readFileSync('/tmp/newwall_brand_map.tsv', 'utf8').trim().split('\n')
.map(l => l.split('\t')).map(([k, v]) => [k.toUpperCase(), v]));
const handles = fs.readFileSync('/tmp/newwall_live.tsv', 'utf8').trim().split('\n')
.map(l => l.split('\t')).map(([key, status, handle]) => ({ key: key.toUpperCase(), handle }));
-const gql = (q, v) => fetch(`https://${S}/admin/api/2024-01/graphql.json`,
- { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Shopify-Access-Token': T }, body: JSON.stringify({ query: q, variables: v }) }).then(r => r.json());
+const gql = (q, v) => fetch(`https://${STORE}/admin/api/2024-01/graphql.json`,
+ { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Shopify-Access-Token': TOKEN }, body: JSON.stringify({ query: q, variables: v }) }).then(r => r.json());
const sleep = ms => new Promise(r => setTimeout(r, ms));
(async () => {
← 974c7dd0 auto-save: 2026-06-26T10:45:01 (8 files) — pending-approval/
·
back to Designer Wallcoverings
·
auto-save: 2026-06-26T11:15:12 (7 files) — pending-approval/ 9279cbe4 →