← back to Dw Theme Patches
TK-12166: Brand McKenzie-only Lead Time spec row patch (backup, deploy/restore with drift check, Playwright verify) — NOT deployed
9cfe6d9e89b05498d93a58cacee64f37cddff7a1 · 2026-09-24 13:59:10 -0700 · Steve Abrams
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WQbD96zYqheZwvE63PvYM2
Files touched
A .gitignoreA TK-12166/_lib.shA TK-12166/backup.sha256A TK-12166/deploy.shA TK-12166/patch.diffA TK-12166/product-description-meta.liquidA TK-12166/product-description-meta.liquid.backupA TK-12166/restore.shA TK-12166/shots/pre-deploy/brand-mckenzie-groove-inferno-bmgi006-03.pngA TK-12166/shots/pre-deploy/dwbm-260001.pngA TK-12166/shots/pre-deploy/dwbm-260059.pngA TK-12166/shots/pre-deploy/dwtt-70112-designer-wallcoverings-los-an.pngA TK-12166/verify.mjs
Diff
commit 9cfe6d9e89b05498d93a58cacee64f37cddff7a1
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Sep 24 13:59:10 2026 -0700
TK-12166: Brand McKenzie-only Lead Time spec row patch (backup, deploy/restore with drift check, Playwright verify) — NOT deployed
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WQbD96zYqheZwvE63PvYM2
---
.gitignore | 8 ++
TK-12166/_lib.sh | 22 ++++
TK-12166/backup.sha256 | 1 +
TK-12166/deploy.sh | 14 +++
TK-12166/patch.diff | 11 ++
TK-12166/product-description-meta.liquid | 114 +++++++++++++++++++++
TK-12166/product-description-meta.liquid.backup | 112 ++++++++++++++++++++
TK-12166/restore.sh | 8 ++
.../brand-mckenzie-groove-inferno-bmgi006-03.png | Bin 0 -> 10616 bytes
TK-12166/shots/pre-deploy/dwbm-260001.png | Bin 0 -> 20004 bytes
TK-12166/shots/pre-deploy/dwbm-260059.png | Bin 0 -> 19935 bytes
.../dwtt-70112-designer-wallcoverings-los-an.png | Bin 0 -> 43650 bytes
TK-12166/verify.mjs | 44 ++++++++
13 files changed, 334 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1924158
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
diff --git a/TK-12166/_lib.sh b/TK-12166/_lib.sh
new file mode 100644
index 0000000..cdd2cc8
--- /dev/null
+++ b/TK-12166/_lib.sh
@@ -0,0 +1,22 @@
+# Shared helpers for TK-12166 deploy/restore. Sourced, not executed.
+set -euo pipefail
+HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+SHOP="designer-laboratory-sandbox.myshopify.com"; API="2024-10"; THEME_ID="145556635699"
+KEY="snippets/product-description-meta.liquid"
+TOK="$(grep -E '^SHOPIFY_ADMIN_TOKEN=' "$HOME/Projects/secrets-manager/.env" | cut -d= -f2- | tr -d '"')"
+[ -n "$TOK" ] || { echo "ABORT: SHOPIFY_ADMIN_TOKEN not found" >&2; exit 1; }
+BASE="https://$SHOP/admin/api/$API/themes/$THEME_ID/assets.json"
+sha_of() { shasum -a 256 "$1" | cut -d' ' -f1; }
+# Fetch live asset value to $1 byte-exact (same method used to take the backup).
+fetch_live() {
+ curl -fsS -G -H "X-Shopify-Access-Token: $TOK" "$BASE" --data-urlencode "asset[key]=$KEY" \
+ | python3 -c "import sys,json;open(sys.argv[1],'w').write(json.load(sys.stdin)['asset']['value'])" "$1"
+}
+# PUT file $1 as the asset value; asserts HTTP 200.
+put_asset() {
+ local body code; body="$(mktemp)"
+ python3 -c "import sys,json;print(json.dumps({'asset':{'key':sys.argv[2],'value':open(sys.argv[1]).read()}}))" "$1" "$KEY" > "$body"
+ code="$(curl -sS -o "$body.out" -w '%{http_code}' -X PUT -H "X-Shopify-Access-Token: $TOK" -H 'Content-Type: application/json' --data-binary @"$body" "$BASE")"
+ if [ "$code" != "200" ]; then echo "ABORT: PUT returned HTTP $code"; cat "$body.out"; rm -f "$body" "$body.out"; exit 1; fi
+ rm -f "$body" "$body.out"
+}
diff --git a/TK-12166/backup.sha256 b/TK-12166/backup.sha256
new file mode 100644
index 0000000..283673e
--- /dev/null
+++ b/TK-12166/backup.sha256
@@ -0,0 +1 @@
+ccf32e546b3691682621ab26736f93d84014487210a19babb30a31cf59d24a91
diff --git a/TK-12166/deploy.sh b/TK-12166/deploy.sh
new file mode 100755
index 0000000..d33431d
--- /dev/null
+++ b/TK-12166/deploy.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env bash
+# TK-12166 — push the Brand McKenzie Lead Time row to the LIVE theme (one asset only).
+# Refuses if the live file drifted from the backup taken 2026-09-24. Undo: ./restore.sh
+source "$(cd "$(dirname "$0")" && pwd)/_lib.sh"
+EXPECT="$(cat "$HERE/backup.sha256")"; NEW="$HERE/product-description-meta.liquid"
+LIVE="$(mktemp)"; fetch_live "$LIVE"; CUR="$(sha_of "$LIVE")"
+echo "live sha256 : $CUR"; echo "backup sha256 : $EXPECT"
+if [ "$CUR" != "$EXPECT" ]; then
+ echo "ABORT: live $KEY has DRIFTED since backup — re-take backup + re-apply patch. Nothing pushed."; diff -u "$HERE/product-description-meta.liquid.backup" "$LIVE" | head -40; exit 2; fi
+[ "${DRY_RUN:-0}" = "1" ] && { echo "DRY_RUN=1 — drift check passed, not pushing."; exit 0; }
+put_asset "$NEW"
+fetch_live "$LIVE"; AFTER="$(sha_of "$LIVE")"; WANT="$(sha_of "$NEW")"
+echo "new live sha256: $AFTER"; echo "patched sha256 : $WANT"
+[ "$AFTER" = "$WANT" ] && echo "OK: deployed. Now run: node $HERE/verify.mjs (undo: $HERE/restore.sh)" || { echo "WARN: live sha != patched file — inspect"; exit 3; }
diff --git a/TK-12166/patch.diff b/TK-12166/patch.diff
new file mode 100644
index 0000000..d69ed1f
--- /dev/null
+++ b/TK-12166/patch.diff
@@ -0,0 +1,11 @@
+--- product-description-meta.liquid.backup 2026-09-24 13:57:08
++++ product-description-meta.liquid 2026-09-24 13:57:16
+@@ -73,6 +73,8 @@
+ {% if pkg != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Packaged</span><span class="dw-spec-value">{{ pkg }}</span></div>{% endif %}
+ {% if uom != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Unit</span><span class="dw-spec-value">{{ uom }}</span></div>{% endif %}
+ {% if coo != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Origin</span><span class="dw-spec-value">{{ coo }}</span></div>{% endif %}
++ {% comment %} TK-12166 — Lead Time row, Brand McKenzie ONLY (Steve approved option A, vendor-scoped). {% endcomment %}
++ {% if product.vendor == 'Brand McKenzie' %}{% assign lt = product.metafields.global.lead_time.value | default: product.metafields.custom.delivery_time.value %}{% if lt != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Lead Time</span><span class="dw-spec-value">{{ lt }}</span></div>{% endif %}{% endif %}
+ {% if dur != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Wyzenbeek</span><span class="dw-spec-value">{{ dur }}</span></div>{% endif %}
+ {% if mart != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Martindale</span><span class="dw-spec-value">{{ mart }}</span></div>{% endif %}
+ {% if abr != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Abrasion</span><span class="dw-spec-value">{{ abr }}</span></div>{% endif %}
diff --git a/TK-12166/product-description-meta.liquid b/TK-12166/product-description-meta.liquid
new file mode 100644
index 0000000..88d4c36
--- /dev/null
+++ b/TK-12166/product-description-meta.liquid
@@ -0,0 +1,114 @@
+<style>
+.dw-specs { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 16px 0; }
+.dw-specs-title { font-size: 13px; font-weight: 600; color: #888; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 8px; padding-bottom: 6px; border-bottom: 1px solid #eee; }
+.dw-spec-row { display: flex; padding: 6px 0; border-bottom: 1px solid #f5f5f5; font-size: 15px; line-height: 1.4; }
+.dw-spec-row:last-child { border-bottom: none; }
+.dw-spec-label { font-weight: 600; color: #333; width: 40%; min-width: 120px; flex-shrink: 0; }
+.dw-spec-value { color: #555; flex: 1; }
+</style>
+
+{% comment %} DW Specs — canonical namespace: custom.* — falls back to specs.* then global.* for legacy products {% endcomment %}
+
+{% assign has_specs = false %}
+{% assign w = product.metafields.custom.width.value | default: product.metafields.specs.width.value | default: product.metafields.global.Width.value | default: product.metafields.global.width.value %}
+{% assign mat = product.metafields.custom.material.value | default: product.metafields.specs.composition.value | default: product.metafields.global.Content.value | default: product.metafields.global.Contents.value | default: product.metafields.global.Construction.value %}
+{% comment %} envelope-guard: unwrap a leaked raw metafield JSON wrapper e.g. {"type":"single_line_text_field","value":"Paper"} if one ever slips through, and prefer the canonical global.Material when the value is still an envelope {% endcomment %}
+{% if mat contains '"single_line_text_field"' and mat contains '"value"' %}{% assign mat = mat | split: '"value": "' | last | split: '"}' | first | default: product.metafields.global.Material.value %}{% endif %}
+{% assign col = product.metafields.custom.collection_name.value | default: product.metafields.specs.collection.value | default: product.metafields.global.Collection.value %}
+{% assign rep = product.metafields.custom.pattern_repeat.value | default: product.metafields.global.repeat.value | default: product.metafields.global['Vert-Rpt'].value %}
+{% assign fin = product.metafields.custom.finish.value | default: product.metafields.specs.finish.value | default: product.metafields.global.Finish.value | default: product.metafields.global.FINISH.value %}
+{% assign care = product.metafields.custom.care.value | default: product.metafields.specs.care.value | default: product.metafields.global.Cleaning.value | default: product.metafields.global['Clean-Code'].value | default: product.metafields.global['Cleaning-Code'].value %}
+{% assign fire = product.metafields.custom.fire_rating.value | default: product.metafields.specs.fire_rating.value | default: product.metafields.global.fire_rating.value | default: product.metafields.global.FLAMMABILITY.value %}
+{% assign match = product.metafields.custom.match_type.value | default: product.metafields.specs.match_type.value | default: product.metafields.global.MATCH.value | default: product.metafields.global.Match.value %}
+{% assign app = product.metafields.custom.application.value | default: product.metafields.specs.application.value | default: product.metafields.global.application.value %}
+{% assign len = product.metafields.custom.length.value | default: product.metafields.global.length.value | default: product.metafields.global.Length.value %}
+{% assign pkg = product.metafields.custom.packaging.value | default: product.metafields.global.packaged.value | default: product.metafields.global.Packaged.value %}
+{% assign uom = product.metafields.custom.unit_of_measure.value | default: product.metafields.global.unit_of_measure.value %}
+{% assign coo = product.metafields.custom.origin.value | default: product.metafields.global.Country.value | default: product.metafields.global['Country-of-Origin'].value %}
+{% assign dur = product.metafields.custom.wyzenbeek.value | default: product.metafields.global['Wyzenbeek-#'].value %}
+{% assign mart = product.metafields.custom.martindale.value | default: product.metafields.global['Martindale-#'].value %}
+{% assign abr = product.metafields.custom.abrasion.value | default: product.metafields.specs.abrasion.value %}
+{% assign bk = product.metafields.custom.backing.value | default: product.metafields.specs.backing.value | default: product.metafields.global.Substrate.value | default: product.metafields.global.substrate.value %}
+{% assign brand = product.metafields.custom.brand.value | default: product.metafields.global.Brand.value %}
+{% assign wt = product.metafields.custom.product_weight.value | default: product.metafields.global.Weight.value %}
+
+{% if w != blank or mat != blank or col != blank %}{% assign has_specs = true %}{% endif %}
+
+{% comment %} TK-11304 — Type II contract wallcovering: bold badge atop the PDP spec area for
+ any product tagged "Type II" (also accepts "Type 2"). Rendered independent of has_specs so it
+ still shows on Type II products with sparse specs. Contract goods are sold BY THE YARD — the
+ unit_of_measure metafield already drives the "Unit" row below (no single-roll hardcode). {% endcomment %}
+{% comment %} Wrap each tag in commas so a substring test is token-boundaried — otherwise
+ 'type ii' would falsely match inside 'type iii' (Type III). Also accepts a space-suffixed
+ tag like 'Type II Commercial'. {% endcomment %}
+{% assign dw_tagstr = product.tags | join: ',' | downcase | prepend: ',' | append: ',' %}
+{% if dw_tagstr contains ',type ii,' or dw_tagstr contains ',type ii ' or dw_tagstr contains ',type 2,' or dw_tagstr contains ',type 2 ' %}
+<div class="dw-type2-label" style="font-weight:700;font-size:15px;color:#1a1714;letter-spacing:.01em;margin:14px 0 4px;">Type II Commercial Wallcovering</div>
+{% endif %}
+
+{% if has_specs %}
+<div class="dw-specs">
+ <div class="dw-specs-title">Specifications</div>
+ {% if w != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Width</span><span class="dw-spec-value">{{ w }}</span></div>{% endif %}
+ {% comment %} Double-roll lines (Thibaut/Malibu Wallpaper/Malibu Walls/York): explicit S/R + D/R
+ length metafields (D/R = 2 x S/R) render directly so the theme never has to guess by halving
+ global.length. Falls back to the plain Length row for everything else. (Steve 2026-07-08 "2 and 2") {% endcomment %}
+ {% assign srlen = product.metafields.global.single_roll_length.value %}
+ {% assign drlen = product.metafields.global.double_roll_length.value %}
+ {% if srlen != blank and drlen != blank %}
+ <div class="dw-spec-row"><span class="dw-spec-label">Length S/R</span><span class="dw-spec-value">{{ srlen }}</span></div>
+ <div class="dw-spec-row"><span class="dw-spec-label">Length D/R</span><span class="dw-spec-value">{{ drlen }}</span></div>
+ {% elsif len != blank %}<div class="dw-spec-row"><span class="dw-spec-label">{% if product.vendor == 'Designtex' %}Bolt size{% else %}Length{% endif %}</span><span class="dw-spec-value">{{ len }}</span></div>{% endif %}
+ {% if rep != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Pattern Repeat</span><span class="dw-spec-value">{{ rep }}</span></div>{% endif %}
+ {% if match != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Match</span><span class="dw-spec-value">{{ match }}</span></div>{% endif %}
+ {% if mat != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Material</span><span class="dw-spec-value">{{ mat }}</span></div>{% endif %}
+ {% if bk != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Backing</span><span class="dw-spec-value">{{ bk }}</span></div>{% endif %}
+ {% if wt != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Weight</span><span class="dw-spec-value">{{ wt }}</span></div>{% endif %}
+ {% if col != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Collection</span><span class="dw-spec-value">{{ col }}</span></div>{% endif %}
+ {% if brand != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Designer</span><span class="dw-spec-value">{{ brand }}</span></div>{% endif %}
+ {% if fin != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Finish</span><span class="dw-spec-value">{{ fin }}</span></div>{% endif %}
+ {% if fire != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Fire Rating</span><span class="dw-spec-value">{{ fire }}</span></div>{% endif %}
+ {% if care != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Care</span><span class="dw-spec-value">{{ care }}</span></div>{% endif %}
+ {% if app != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Application</span><span class="dw-spec-value">{{ app }}</span></div>{% endif %}
+ {% if pkg != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Packaged</span><span class="dw-spec-value">{{ pkg }}</span></div>{% endif %}
+ {% if uom != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Unit</span><span class="dw-spec-value">{{ uom }}</span></div>{% endif %}
+ {% if coo != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Origin</span><span class="dw-spec-value">{{ coo }}</span></div>{% endif %}
+ {% comment %} TK-12166 — Lead Time row, Brand McKenzie ONLY (Steve approved option A, vendor-scoped). {% endcomment %}
+ {% if product.vendor == 'Brand McKenzie' %}{% assign lt = product.metafields.global.lead_time.value | default: product.metafields.custom.delivery_time.value %}{% if lt != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Lead Time</span><span class="dw-spec-value">{{ lt }}</span></div>{% endif %}{% endif %}
+ {% if dur != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Wyzenbeek</span><span class="dw-spec-value">{{ dur }}</span></div>{% endif %}
+ {% if mart != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Martindale</span><span class="dw-spec-value">{{ mart }}</span></div>{% endif %}
+ {% if abr != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Abrasion</span><span class="dw-spec-value">{{ abr }}</span></div>{% endif %}
+ {% assign ai_note = product.metafields.specs.ai_rooms_note.value %}
+ {% if ai_note != blank %}
+ <div class="dw-spec-row" style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #eee;">
+ <span class="dw-spec-label" style="color: #b45309; font-size: 13px;">AI Rooms</span>
+ <span class="dw-spec-value" style="color: #92400e; font-size: 13px; font-style: italic;">{{ ai_note }}</span>
+ </div>
+ {% endif %}
+</div>
+{% endif %}
+
+{% comment %}
+Contact popup form
+{% endcomment %}
+<div id="custom-popup-overlay">
+ <div class="custom-popup-main">
+ <div class="custom-close">×</div>
+ <div class="content-main">
+ <div class="popup-content">
+ <h2 style="text-align:center;">Send us a message</h2>
+ {% form 'contact' %}
+ {% if form.errors %}<div class="error-message" style="display:none;"><span>{{ 'general.contact.error' | t }}</span></div>{% endif %}
+ {% if form.posted_successfully? %}<div class="success-message" style="display:none;">{{ 'general.contact.success' | t }}</div>{% endif %}
+ <div class="field-wrap sku"><input type="text" value="{{ product.variants.last.sku }}" name="contact[SKU]" readonly="true"></div>
+ <div class="field-wrap name"><input type="text" placeholder="Name" value="" name="contact2[name]" class="{% if form.errors contains 'author' %}error{% endif %}" required></div>
+ <div class="field-wrap email"><input type="email" name="contact[email]" autocorrect="off" autocapitalize="off" value="{% if form.email %}{{ form.email }}{% elsif customer %}{{ customer.email }}{% endif %}" class="{% if form.errors contains 'email' %}input--error{% endif %}" placeholder="Email" required></div>
+ <div class="field-wrap phone"><input type="text" placeholder="Phone number" value="" name="contact[phone number]" required></div>
+ <div class="field-wrap message"><textarea name="contact[message]" placeholder="Message" rows="5" required></textarea></div>
+ <input type="hidden" class="comment-check" value="" />
+ <input style="background-color:#000;color:#fff;" type="submit" value="{{ 'general.contact.submit' | t }}">
+ {% endform %}
+ </div>
+ </div>
+ </div>
+</div>
\ No newline at end of file
diff --git a/TK-12166/product-description-meta.liquid.backup b/TK-12166/product-description-meta.liquid.backup
new file mode 100644
index 0000000..cb14a7c
--- /dev/null
+++ b/TK-12166/product-description-meta.liquid.backup
@@ -0,0 +1,112 @@
+<style>
+.dw-specs { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 16px 0; }
+.dw-specs-title { font-size: 13px; font-weight: 600; color: #888; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 8px; padding-bottom: 6px; border-bottom: 1px solid #eee; }
+.dw-spec-row { display: flex; padding: 6px 0; border-bottom: 1px solid #f5f5f5; font-size: 15px; line-height: 1.4; }
+.dw-spec-row:last-child { border-bottom: none; }
+.dw-spec-label { font-weight: 600; color: #333; width: 40%; min-width: 120px; flex-shrink: 0; }
+.dw-spec-value { color: #555; flex: 1; }
+</style>
+
+{% comment %} DW Specs — canonical namespace: custom.* — falls back to specs.* then global.* for legacy products {% endcomment %}
+
+{% assign has_specs = false %}
+{% assign w = product.metafields.custom.width.value | default: product.metafields.specs.width.value | default: product.metafields.global.Width.value | default: product.metafields.global.width.value %}
+{% assign mat = product.metafields.custom.material.value | default: product.metafields.specs.composition.value | default: product.metafields.global.Content.value | default: product.metafields.global.Contents.value | default: product.metafields.global.Construction.value %}
+{% comment %} envelope-guard: unwrap a leaked raw metafield JSON wrapper e.g. {"type":"single_line_text_field","value":"Paper"} if one ever slips through, and prefer the canonical global.Material when the value is still an envelope {% endcomment %}
+{% if mat contains '"single_line_text_field"' and mat contains '"value"' %}{% assign mat = mat | split: '"value": "' | last | split: '"}' | first | default: product.metafields.global.Material.value %}{% endif %}
+{% assign col = product.metafields.custom.collection_name.value | default: product.metafields.specs.collection.value | default: product.metafields.global.Collection.value %}
+{% assign rep = product.metafields.custom.pattern_repeat.value | default: product.metafields.global.repeat.value | default: product.metafields.global['Vert-Rpt'].value %}
+{% assign fin = product.metafields.custom.finish.value | default: product.metafields.specs.finish.value | default: product.metafields.global.Finish.value | default: product.metafields.global.FINISH.value %}
+{% assign care = product.metafields.custom.care.value | default: product.metafields.specs.care.value | default: product.metafields.global.Cleaning.value | default: product.metafields.global['Clean-Code'].value | default: product.metafields.global['Cleaning-Code'].value %}
+{% assign fire = product.metafields.custom.fire_rating.value | default: product.metafields.specs.fire_rating.value | default: product.metafields.global.fire_rating.value | default: product.metafields.global.FLAMMABILITY.value %}
+{% assign match = product.metafields.custom.match_type.value | default: product.metafields.specs.match_type.value | default: product.metafields.global.MATCH.value | default: product.metafields.global.Match.value %}
+{% assign app = product.metafields.custom.application.value | default: product.metafields.specs.application.value | default: product.metafields.global.application.value %}
+{% assign len = product.metafields.custom.length.value | default: product.metafields.global.length.value | default: product.metafields.global.Length.value %}
+{% assign pkg = product.metafields.custom.packaging.value | default: product.metafields.global.packaged.value | default: product.metafields.global.Packaged.value %}
+{% assign uom = product.metafields.custom.unit_of_measure.value | default: product.metafields.global.unit_of_measure.value %}
+{% assign coo = product.metafields.custom.origin.value | default: product.metafields.global.Country.value | default: product.metafields.global['Country-of-Origin'].value %}
+{% assign dur = product.metafields.custom.wyzenbeek.value | default: product.metafields.global['Wyzenbeek-#'].value %}
+{% assign mart = product.metafields.custom.martindale.value | default: product.metafields.global['Martindale-#'].value %}
+{% assign abr = product.metafields.custom.abrasion.value | default: product.metafields.specs.abrasion.value %}
+{% assign bk = product.metafields.custom.backing.value | default: product.metafields.specs.backing.value | default: product.metafields.global.Substrate.value | default: product.metafields.global.substrate.value %}
+{% assign brand = product.metafields.custom.brand.value | default: product.metafields.global.Brand.value %}
+{% assign wt = product.metafields.custom.product_weight.value | default: product.metafields.global.Weight.value %}
+
+{% if w != blank or mat != blank or col != blank %}{% assign has_specs = true %}{% endif %}
+
+{% comment %} TK-11304 — Type II contract wallcovering: bold badge atop the PDP spec area for
+ any product tagged "Type II" (also accepts "Type 2"). Rendered independent of has_specs so it
+ still shows on Type II products with sparse specs. Contract goods are sold BY THE YARD — the
+ unit_of_measure metafield already drives the "Unit" row below (no single-roll hardcode). {% endcomment %}
+{% comment %} Wrap each tag in commas so a substring test is token-boundaried — otherwise
+ 'type ii' would falsely match inside 'type iii' (Type III). Also accepts a space-suffixed
+ tag like 'Type II Commercial'. {% endcomment %}
+{% assign dw_tagstr = product.tags | join: ',' | downcase | prepend: ',' | append: ',' %}
+{% if dw_tagstr contains ',type ii,' or dw_tagstr contains ',type ii ' or dw_tagstr contains ',type 2,' or dw_tagstr contains ',type 2 ' %}
+<div class="dw-type2-label" style="font-weight:700;font-size:15px;color:#1a1714;letter-spacing:.01em;margin:14px 0 4px;">Type II Commercial Wallcovering</div>
+{% endif %}
+
+{% if has_specs %}
+<div class="dw-specs">
+ <div class="dw-specs-title">Specifications</div>
+ {% if w != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Width</span><span class="dw-spec-value">{{ w }}</span></div>{% endif %}
+ {% comment %} Double-roll lines (Thibaut/Malibu Wallpaper/Malibu Walls/York): explicit S/R + D/R
+ length metafields (D/R = 2 x S/R) render directly so the theme never has to guess by halving
+ global.length. Falls back to the plain Length row for everything else. (Steve 2026-07-08 "2 and 2") {% endcomment %}
+ {% assign srlen = product.metafields.global.single_roll_length.value %}
+ {% assign drlen = product.metafields.global.double_roll_length.value %}
+ {% if srlen != blank and drlen != blank %}
+ <div class="dw-spec-row"><span class="dw-spec-label">Length S/R</span><span class="dw-spec-value">{{ srlen }}</span></div>
+ <div class="dw-spec-row"><span class="dw-spec-label">Length D/R</span><span class="dw-spec-value">{{ drlen }}</span></div>
+ {% elsif len != blank %}<div class="dw-spec-row"><span class="dw-spec-label">{% if product.vendor == 'Designtex' %}Bolt size{% else %}Length{% endif %}</span><span class="dw-spec-value">{{ len }}</span></div>{% endif %}
+ {% if rep != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Pattern Repeat</span><span class="dw-spec-value">{{ rep }}</span></div>{% endif %}
+ {% if match != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Match</span><span class="dw-spec-value">{{ match }}</span></div>{% endif %}
+ {% if mat != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Material</span><span class="dw-spec-value">{{ mat }}</span></div>{% endif %}
+ {% if bk != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Backing</span><span class="dw-spec-value">{{ bk }}</span></div>{% endif %}
+ {% if wt != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Weight</span><span class="dw-spec-value">{{ wt }}</span></div>{% endif %}
+ {% if col != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Collection</span><span class="dw-spec-value">{{ col }}</span></div>{% endif %}
+ {% if brand != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Designer</span><span class="dw-spec-value">{{ brand }}</span></div>{% endif %}
+ {% if fin != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Finish</span><span class="dw-spec-value">{{ fin }}</span></div>{% endif %}
+ {% if fire != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Fire Rating</span><span class="dw-spec-value">{{ fire }}</span></div>{% endif %}
+ {% if care != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Care</span><span class="dw-spec-value">{{ care }}</span></div>{% endif %}
+ {% if app != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Application</span><span class="dw-spec-value">{{ app }}</span></div>{% endif %}
+ {% if pkg != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Packaged</span><span class="dw-spec-value">{{ pkg }}</span></div>{% endif %}
+ {% if uom != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Unit</span><span class="dw-spec-value">{{ uom }}</span></div>{% endif %}
+ {% if coo != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Origin</span><span class="dw-spec-value">{{ coo }}</span></div>{% endif %}
+ {% if dur != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Wyzenbeek</span><span class="dw-spec-value">{{ dur }}</span></div>{% endif %}
+ {% if mart != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Martindale</span><span class="dw-spec-value">{{ mart }}</span></div>{% endif %}
+ {% if abr != blank %}<div class="dw-spec-row"><span class="dw-spec-label">Abrasion</span><span class="dw-spec-value">{{ abr }}</span></div>{% endif %}
+ {% assign ai_note = product.metafields.specs.ai_rooms_note.value %}
+ {% if ai_note != blank %}
+ <div class="dw-spec-row" style="margin-top: 10px; padding-top: 10px; border-top: 1px solid #eee;">
+ <span class="dw-spec-label" style="color: #b45309; font-size: 13px;">AI Rooms</span>
+ <span class="dw-spec-value" style="color: #92400e; font-size: 13px; font-style: italic;">{{ ai_note }}</span>
+ </div>
+ {% endif %}
+</div>
+{% endif %}
+
+{% comment %}
+Contact popup form
+{% endcomment %}
+<div id="custom-popup-overlay">
+ <div class="custom-popup-main">
+ <div class="custom-close">×</div>
+ <div class="content-main">
+ <div class="popup-content">
+ <h2 style="text-align:center;">Send us a message</h2>
+ {% form 'contact' %}
+ {% if form.errors %}<div class="error-message" style="display:none;"><span>{{ 'general.contact.error' | t }}</span></div>{% endif %}
+ {% if form.posted_successfully? %}<div class="success-message" style="display:none;">{{ 'general.contact.success' | t }}</div>{% endif %}
+ <div class="field-wrap sku"><input type="text" value="{{ product.variants.last.sku }}" name="contact[SKU]" readonly="true"></div>
+ <div class="field-wrap name"><input type="text" placeholder="Name" value="" name="contact2[name]" class="{% if form.errors contains 'author' %}error{% endif %}" required></div>
+ <div class="field-wrap email"><input type="email" name="contact[email]" autocorrect="off" autocapitalize="off" value="{% if form.email %}{{ form.email }}{% elsif customer %}{{ customer.email }}{% endif %}" class="{% if form.errors contains 'email' %}input--error{% endif %}" placeholder="Email" required></div>
+ <div class="field-wrap phone"><input type="text" placeholder="Phone number" value="" name="contact[phone number]" required></div>
+ <div class="field-wrap message"><textarea name="contact[message]" placeholder="Message" rows="5" required></textarea></div>
+ <input type="hidden" class="comment-check" value="" />
+ <input style="background-color:#000;color:#fff;" type="submit" value="{{ 'general.contact.submit' | t }}">
+ {% endform %}
+ </div>
+ </div>
+ </div>
+</div>
\ No newline at end of file
diff --git a/TK-12166/restore.sh b/TK-12166/restore.sh
new file mode 100755
index 0000000..e88c4be
--- /dev/null
+++ b/TK-12166/restore.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+# TK-12166 — UNDO: PUT the 2026-09-24 backup of the snippet back to the LIVE theme.
+source "$(cd "$(dirname "$0")" && pwd)/_lib.sh"
+BK="$HERE/product-description-meta.liquid.backup"; EXPECT="$(cat "$HERE/backup.sha256")"
+[ "$(sha_of "$BK")" = "$EXPECT" ] || { echo "ABORT: backup file sha mismatch — backup corrupted"; exit 2; }
+put_asset "$BK"
+LIVE="$(mktemp)"; fetch_live "$LIVE"; AFTER="$(sha_of "$LIVE")"
+echo "live sha256 after restore: $AFTER (expect $EXPECT)"; [ "$AFTER" = "$EXPECT" ] && echo "OK: restored." || { echo "WARN: mismatch"; exit 3; }
diff --git a/TK-12166/shots/pre-deploy/brand-mckenzie-groove-inferno-bmgi006-03.png b/TK-12166/shots/pre-deploy/brand-mckenzie-groove-inferno-bmgi006-03.png
new file mode 100644
index 0000000..f3f5c42
Binary files /dev/null and b/TK-12166/shots/pre-deploy/brand-mckenzie-groove-inferno-bmgi006-03.png differ
diff --git a/TK-12166/shots/pre-deploy/dwbm-260001.png b/TK-12166/shots/pre-deploy/dwbm-260001.png
new file mode 100644
index 0000000..398d00b
Binary files /dev/null and b/TK-12166/shots/pre-deploy/dwbm-260001.png differ
diff --git a/TK-12166/shots/pre-deploy/dwbm-260059.png b/TK-12166/shots/pre-deploy/dwbm-260059.png
new file mode 100644
index 0000000..620e071
Binary files /dev/null and b/TK-12166/shots/pre-deploy/dwbm-260059.png differ
diff --git a/TK-12166/shots/pre-deploy/dwtt-70112-designer-wallcoverings-los-an.png b/TK-12166/shots/pre-deploy/dwtt-70112-designer-wallcoverings-los-an.png
new file mode 100644
index 0000000..29fffe5
Binary files /dev/null and b/TK-12166/shots/pre-deploy/dwtt-70112-designer-wallcoverings-los-an.png differ
diff --git a/TK-12166/verify.mjs b/TK-12166/verify.mjs
new file mode 100644
index 0000000..9e34244
--- /dev/null
+++ b/TK-12166/verify.mjs
@@ -0,0 +1,44 @@
+// TK-12166 post-deploy check (READ-ONLY): 3 Brand McKenzie PDPs show a "Lead Time" spec row = "3-5 Weeks";
+// 1 Thibaut PDP has NO Lead Time row; no Liquid errors on any page. Screenshots -> ./shots/
+// Usage: node verify.mjs (exit 0 = PASS, 1 = FAIL)
+import { createRequire } from 'module';
+import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url';
+const require = createRequire(import.meta.url);
+const PW = process.env.PW_PATH || '/Users/macstudio3/Projects/astek-landing/node_modules/playwright';
+const { chromium } = require(PW);
+const HERE = path.dirname(fileURLToPath(import.meta.url)); const SHOTS = path.join(HERE, 'shots'); fs.mkdirSync(SHOTS, { recursive: true });
+const BASE = 'https://www.designerwallcoverings.com/products/';
+const CASES = [
+ { h: 'dwbm-260059', expect: '3-5 Weeks' },
+ { h: 'dwbm-260001', expect: '3-5 Weeks' },
+ { h: 'brand-mckenzie-groove-inferno-bmgi006-03a-wallcovering-sancarwallcoverings', expect: '3-5 Weeks' },
+ { h: 'dwtt-70112-designer-wallcoverings-los-angeles', expect: null }, // Thibaut: must have NO Lead Time row
+];
+const b = await chromium.launch();
+const pg = await b.newPage({ userAgent: 'Mozilla/5.0 (Macintosh) AppleWebKit/537.36 Chrome/128 Safari/537.36', viewport: { width: 1400, height: 1000 } });
+let fail = 0;
+for (const c of CASES) {
+ const url = BASE + c.h + '?_tk12166=' + Date.now(); // cache-bust
+ const resp = await pg.goto(url, { waitUntil: 'load', timeout: 90000 }); await pg.waitForTimeout(5000);
+ const r = await pg.evaluate(() => {
+ const rows = [...document.querySelectorAll('.dw-specs .dw-spec-row')].map(e => ({
+ label: e.querySelector('.dw-spec-label')?.innerText.trim(), value: e.querySelector('.dw-spec-value')?.innerText.trim(), vis: e.offsetParent !== null }));
+ const specsPresent = !!document.querySelector('.dw-specs');
+ const liquidErr = /Liquid (syntax )?error|Liquid error/i.test(document.body.innerText);
+ const vendor = (window.ShopifyAnalytics?.meta?.product?.vendor) || null;
+ return { rows, specsPresent, liquidErr, vendor };
+ });
+ const lt = r.rows.find(x => /^lead time$/i.test(x.label || ''));
+ let ok, why;
+ if (resp.status() !== 200) { ok = false; why = 'HTTP ' + resp.status(); }
+ else if (r.liquidErr) { ok = false; why = 'Liquid error on page'; }
+ else if (!r.specsPresent) { ok = false; why = 'NOT-MEASURED: .dw-specs block absent (cannot judge)'; }
+ else if (c.expect) { ok = !!lt && lt.vis && lt.value === c.expect; why = lt ? `Lead Time row = "${lt.value}" visible=${lt.vis}` : 'no Lead Time row'; }
+ else { ok = !lt; why = lt ? `UNEXPECTED Lead Time row "${lt.value}"` : `no Lead Time row (${r.rows.length} spec rows)`; }
+ const shot = path.join(SHOTS, c.h.slice(0, 40) + '.png');
+ const el = await pg.$('.dw-specs'); if (el) await el.screenshot({ path: shot }).catch(() => pg.screenshot({ path: shot })); else await pg.screenshot({ path: shot });
+ console.log(`${ok ? 'PASS' : 'FAIL'} ${c.h} vendor=${r.vendor} ${why} shot=${shot}`);
+ if (!ok) fail++;
+}
+await b.close();
+console.log(fail ? `VERDICT: FAIL (${fail})` : 'VERDICT: PASS'); process.exit(fail ? 1 : 0);
(oldest)
·
back to Dw Theme Patches
·
TK-12166: deployed + verified PASS; retry read-back to absor 2812485 →