[object Object]

← back to Designerwallcoverings

TK-11404: theme-fix spec for handoff (reorder dead; leak is in theme.liquid JSON-LD + product-form-content microdata/visible price)

68bf79a20270d3e7209a5a29b3a1cfc546d63114 · 2026-09-10 16:20:58 -0700 · Steve Abrams

Forensics: the rendered Product JSON-LD comes from layout/theme.liquid (not
the orphaned structured-data.liquid), and the $4.25 microdata from
product-form-content.liquid. Available-sample-at-pos1 makes the sample the
first_available_variant -> $4.25 on JSON-LD + microdata + visible price on
~1,780 products. Spec gives the drop-in rep resolver, exact files/lines, the
verified staged apply/restore pastes, the preview_theme_id verify method, and
the TK-11357 (qty=2026 sample stamp) upstream link. Live theme left pristine.

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

Files touched

Diff

commit 68bf79a20270d3e7209a5a29b3a1cfc546d63114
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Sep 10 16:20:58 2026 -0700

    TK-11404: theme-fix spec for handoff (reorder dead; leak is in theme.liquid JSON-LD + product-form-content microdata/visible price)
    
    Forensics: the rendered Product JSON-LD comes from layout/theme.liquid (not
    the orphaned structured-data.liquid), and the $4.25 microdata from
    product-form-content.liquid. Available-sample-at-pos1 makes the sample the
    first_available_variant -> $4.25 on JSON-LD + microdata + visible price on
    ~1,780 products. Spec gives the drop-in rep resolver, exact files/lines, the
    verified staged apply/restore pastes, the preview_theme_id verify method, and
    the TK-11357 (qty=2026 sample stamp) upstream link. Live theme left pristine.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_018en9DwRg3hJwbpy5dL7xGT
---
 scripts/tk11404-theme-fix-SPEC.md | 90 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 90 insertions(+)

diff --git a/scripts/tk11404-theme-fix-SPEC.md b/scripts/tk11404-theme-fix-SPEC.md
new file mode 100644
index 0000000..c174ac7
--- /dev/null
+++ b/scripts/tk11404-theme-fix-SPEC.md
@@ -0,0 +1,90 @@
+# TK-11404 — theme fix spec: sample price ($4.25) leaking as the default product price
+
+**For:** whoever owns the DW Shopify theme · **From:** vp-dw-commerce · **Date:** 2026-09-10
+**Store:** designer-laboratory-sandbox (LIVE) · **Published theme:** `DW Sample-Shipping DEV` (id 145556635699)
+**Live theme was left PRISTINE** — no edits from this investigation remain.
+
+## The defect
+~2,586 ACTIVE products have their `Sample` variant at REST **position 1**. On the ~1,780 of them whose
+sample is **available** (inventory_policy=`continue` + qty=2026 — the TK-11357 stamp lineage),
+`product.selected_or_first_available_variant` resolves to the **sample** (first *available* variant). So a
+$300–$400 wallcovering renders and advertises **$4.25** on:
+1. the **rendered JSON-LD** `offers.price` (Google organic rich results),
+2. the **`itemprop="price"` microdata** (also read by Google), and
+3. the **on-page visible price**.
+
+The remaining ~800 (sample qty 0 / unavailable) already render correctly — `selected_or_first_available`
+skips the unavailable sample — which is why an earlier bare-PDP check wrongly concluded "not-a-defect."
+
+**Not fixable by reordering variants:** `productSet` (option-value reorder) and `productVariantsBulkReorder`
+both return `userErrors:[]` but leave REST variant position unchanged (proven on n=3 real + synthetic). The
+only reorder that works (delete+recreate the sample last) changes the sample variant id / `-sample` SKU,
+which is forbidden. So the fix must be in the **theme**.
+
+## The exact emitters (verified by grepping all 233 theme assets)
+> ⚠️ `snippets/structured-data.liquid` and `snippets/product.price.liquid` look like the source but are the
+> WRONG targets — `structured-data.liquid` is **orphaned** (nothing renders it). Do not edit those.
+
+| Surface | File | Line(s) | Current |
+|---|---|---|---|
+| JSON-LD offer | `layout/theme.liquid` | 1214, 1242, 1245, 1253–1255 | prices off `selected_variant` (= sample) |
+| microdata | `snippets/product-form-content.liquid` | 11, 339 | `<meta itemprop="price" content="{{ selected_variant.price … }}">` |
+| visible price | `snippets/product-form-content.liquid` | 358–360, 509+, 551–555, 722–744 | mixed: partial sample-skip already exists (`target_product = product.variants[1]` when `variants[0].title == 'Sample'`), but `data-vprice`/microdata still use the sample |
+
+Note the theme author **already knew** about sample-first (the `target_product = variants[1]` pattern at
+358–360) but never applied it to the price surfaces. A `variants[1]` assumption is fragile though — prefer
+the robust "first non-sample available" resolver below.
+
+## The fix — a `rep` resolver (drop-in Liquid)
+Compute a representative **non-sample** variant, and use `rep` in place of `selected_variant` for every
+PRICE surface. Gate on whether the *resolved* variant is a sample (this also correctly honors an explicit
+non-sample `?variant` selection). **Do not** gate on `product.selected_variant` being nil — on this store it
+is truthy (returns the default variant), which silently no-ops the fix.
+
+```liquid
+{%- assign rep = selected_variant -%}
+{%- assign dw_base_sample = false -%}
+{%- if selected_variant.title == 'Sample' -%}{%- assign dw_base_sample = true -%}{%- endif -%}
+{%- if selected_variant.sku contains '-sample' or selected_variant.sku contains '-SAMPLE' -%}{%- assign dw_base_sample = true -%}{%- endif -%}
+{%- if dw_base_sample -%}
+  {%- assign rep = null -%}{%- assign dw_repfb = null -%}
+  {%- for v in product.variants -%}
+    {%- assign dw_vs = false -%}
+    {%- if v.title == 'Sample' -%}{%- assign dw_vs = true -%}{%- endif -%}
+    {%- if v.sku contains '-sample' or v.sku contains '-SAMPLE' -%}{%- assign dw_vs = true -%}{%- endif -%}
+    {%- unless dw_vs -%}
+      {%- unless dw_repfb -%}{%- assign dw_repfb = v -%}{%- endunless -%}
+      {%- if v.available -%}{%- assign rep = v -%}{%- break -%}{%- endif -%}
+    {%- endunless -%}
+  {%- endfor -%}
+  {%- assign rep = rep | default: dw_repfb | default: selected_variant -%}
+{%- endif -%}
+```
+- **JSON-LD** (`theme.liquid`): insert after line 1214; swap `selected_variant` → `rep` on the offer
+  `sku` / `mpn` / `price` / `availability` / `url` (keep `image` + `name` on `selected_variant`).
+- **microdata** (`product-form-content.liquid`): insert before line 339; change line 339 to `{{ rep.price | money_without_currency }}`.
+- **visible price** (`product-form-content.liquid`): route the displayed price + `data-vprice` + the
+  variant-change JS default through the same `rep` (extend the existing `target_product` logic). This is the
+  more entangled part — test the add-to-cart + sample button + variant switching after.
+
+## Staged, ready-to-run (the structured surfaces only — JSON-LD + microdata)
+A verified apply for the two structured surfaces is staged; it edits the LIVE theme, verifies via
+`preview_theme_id` (cache-bypass), and **auto-restores on any failure**:
+```
+node /tmp/tk11404-theme-backup/apply-v2.mjs      # apply + verify JSON-LD + microdata (keeps only if pass)
+node /tmp/tk11404-theme-backup/restore-v2.mjs     # undo
+```
+(These live in a scratch dir; copy the `rep` block above into the theme repo for a durable change.)
+
+## Verify (bypasses Shopify's full-page cache — a plain `?_=ts` does NOT)
+```
+https://designerwallcoverings.com/products/dwve-429500?preview_theme_id=145556635699   # expect sellable $359
+https://designerwallcoverings.com/products/pacific-paperweave-grass-prg-71016?preview_theme_id=…  # control, unchanged $424
+```
+Check `offers.price` in JSON-LD, the `itemprop="price"` meta, and the visible price; confirm `?variant=<sample>`
+still shows $4.25 and collections/add-to-cart still work.
+
+## Related / upstream
+The sample being **available** (qty=2026 + `continue`) is what promotes it to `first_available_variant`.
+That stamp is the **TK-11357** zero-price/2026-orderable lineage — a data-side fix there (samples not
+default-available) would also resolve this without any theme edit. Worth coordinating.

← e52390b TK-11404: correct FINDINGS — DTD+live audit found a real JSO  ·  back to Designerwallcoverings  ·  TK-11307: fix silent-no-op rollback — reconstruct baseline e 5db8255 →