← back to Dw Marketing Reels
TK-10427: add OS 2.0 section file + rollback-first one-command homepage push
ec5eff33a0fb11b75d8127693152fa742c358697 · 2026-09-03 14:12:31 -0700 · Claude
The staged artifact was a Custom-Liquid paste blob with no schema, which is
why the only ship path was a manual theme-editor paste. Adds:
- dw-collection-films.SECTION.liquid (same validated 3-card markup + schema)
- PUSH-collection-films.sh (--dry-run/--apply/--rollback): snapshots
templates/index.json, PUTs the section asset, wires it into the homepage
order, verifies dwcf markers on the live page.
Dry-run verified: mutation adds exactly 1 section, removes 0, changes 0,
preserves relative order. All 3 CTA collections 200, all 6 media assets 206.
Live fire stays Steve-gated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015XxMVSyKj1qHnXH2fLCT49
Files touched
A storefront/staged-tk10427/PUSH-collection-films.shA storefront/staged-tk10427/dw-collection-films.SECTION.liquid
Diff
commit ec5eff33a0fb11b75d8127693152fa742c358697
Author: Claude <steve@designerwallcoverings.com>
Date: Thu Sep 3 14:12:31 2026 -0700
TK-10427: add OS 2.0 section file + rollback-first one-command homepage push
The staged artifact was a Custom-Liquid paste blob with no schema, which is
why the only ship path was a manual theme-editor paste. Adds:
- dw-collection-films.SECTION.liquid (same validated 3-card markup + schema)
- PUSH-collection-films.sh (--dry-run/--apply/--rollback): snapshots
templates/index.json, PUTs the section asset, wires it into the homepage
order, verifies dwcf markers on the live page.
Dry-run verified: mutation adds exactly 1 section, removes 0, changes 0,
preserves relative order. All 3 CTA collections 200, all 6 media assets 206.
Live fire stays Steve-gated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015XxMVSyKj1qHnXH2fLCT49
---
storefront/staged-tk10427/PUSH-collection-films.sh | 89 +++++++++++++++++
.../dw-collection-films.SECTION.liquid | 105 +++++++++++++++++++++
2 files changed, 194 insertions(+)
diff --git a/storefront/staged-tk10427/PUSH-collection-films.sh b/storefront/staged-tk10427/PUSH-collection-films.sh
new file mode 100755
index 0000000..448b7e3
--- /dev/null
+++ b/storefront/staged-tk10427/PUSH-collection-films.sh
@@ -0,0 +1,89 @@
+#!/usr/bin/env bash
+# TK-10427 — DW homepage "Collection Films" band (3 cards).
+# GATED: customer-facing LIVE Shopify theme write. Do NOT run without Steve's go.
+#
+# ./PUSH-collection-films.sh --dry-run # $0, no writes: prints the exact index.json diff
+# ./PUSH-collection-films.sh --apply # 2 PUTs (section asset + index.json), then verifies live
+# ./PUSH-collection-films.sh --rollback # restores index.json from the newest snapshot
+#
+# Reversible by construction: both assets are snapshotted to ROLLBACK-*.json BEFORE any PUT.
+# Placement is controlled by AFTER_SECTION (default: directly below New Arrivals).
+set -euo pipefail
+MODE="${1:---dry-run}"
+DOMAIN="designer-laboratory-sandbox.myshopify.com"; VER="2024-10"; THEME="145121607731"
+SECTION_KEY="sections/dw-collection-films.liquid"
+INDEX_KEY="templates/index.json"
+SECTION_ID="collection_films_tk10427"
+AFTER_SECTION="${AFTER_SECTION:-new_arrivals_banner}" # insert immediately after this section id
+HERE="$(cd "$(dirname "$0")" && pwd)"
+NEW="$HERE/dw-collection-films.SECTION.liquid"
+STAMP="$(date +%Y%m%d-%H%M%S)"
+TOK="$(grep -E '^SHOPIFY_THEME_TOKEN=' "$HOME/Projects/secrets-manager/.env" | head -1 | cut -d= -f2-)"
+[ -n "$TOK" ] || { echo "✗ no SHOPIFY_THEME_TOKEN"; exit 1; }
+[ -f "$NEW" ] || { echo "✗ missing $NEW"; exit 1; }
+api(){ curl -s --max-time 60 -H "X-Shopify-Access-Token: $TOK" "$@"; }
+
+if [ "$MODE" = "--rollback" ]; then
+ BAK="$(ls -t "$HERE"/ROLLBACK-index-*.json 2>/dev/null | head -1)"
+ [ -n "$BAK" ] || { echo "✗ no index rollback snapshot found"; exit 1; }
+ echo "== ROLLBACK index.json from $BAK"
+ BAK="$BAK" KEY="$INDEX_KEY" DOMAIN="$DOMAIN" VER="$VER" THEME="$THEME" TOK="$TOK" node -e '
+ const fs=require("fs"),https=require("https");
+ const val=JSON.parse(fs.readFileSync(process.env.BAK,"utf8")).asset.value;
+ const body=JSON.stringify({asset:{key:process.env.KEY,value:val}});
+ const r=https.request({method:"PUT",host:process.env.DOMAIN,path:`/admin/api/${process.env.VER}/themes/${process.env.THEME}/assets.json`,headers:{"X-Shopify-Access-Token":process.env.TOK,"Content-Type":"application/json","Content-Length":Buffer.byteLength(body)}},res=>{let s="";res.on("data",d=>s+=d);res.on("end",()=>{console.log(" HTTP",res.statusCode);process.exit(res.statusCode<300?0:1)})});
+ r.on("error",e=>{console.error(e);process.exit(1)});r.write(body);r.end();'
+ echo " rolled back. (section asset left in place — harmless once unreferenced)"
+ exit 0
+fi
+
+echo "== 1. SNAPSHOT current $INDEX_KEY → ROLLBACK-index-$STAMP.json"
+api "https://$DOMAIN/admin/api/$VER/themes/$THEME/assets.json?asset%5Bkey%5D=templates%2Findex.json" > "$HERE/ROLLBACK-index-$STAMP.json"
+grep -q '"value"' "$HERE/ROLLBACK-index-$STAMP.json" || { echo " ✗ snapshot empty — ABORT"; exit 1; }
+echo " saved ($(wc -c < "$HERE/ROLLBACK-index-$STAMP.json") bytes)"
+
+echo "== 2. COMPUTE new index.json (add $SECTION_ID after $AFTER_SECTION)"
+SNAP="$HERE/ROLLBACK-index-$STAMP.json" OUT="$HERE/index.NEW-$STAMP.json" SID="$SECTION_ID" AFTER="$AFTER_SECTION" node -e '
+const fs=require("fs");
+const t=JSON.parse(JSON.parse(fs.readFileSync(process.env.SNAP,"utf8")).asset.value);
+const sid=process.env.SID, after=process.env.AFTER;
+if(t.sections[sid]){console.error(" ✗ section id already present — ABORT (already shipped?)");process.exit(1);}
+const i=t.order.indexOf(after);
+if(i<0){console.error(" ✗ anchor section not found: "+after);process.exit(1);}
+t.sections[sid]={type:"dw-collection-films",settings:{}};
+t.order.splice(i+1,0,sid);
+fs.writeFileSync(process.env.OUT, JSON.stringify(t,null,2));
+console.log(" inserted at order position "+(i+1)+" of "+t.order.length);
+console.log(" new order: "+t.order.map((k,n)=>(k===sid?"➤ ":"")+n+":"+k).join(" "));
+'
+echo "== 3. PREFLIGHT: CTA destinations"
+for h in maya-romanoff glitter-walls majilite; do
+ printf " /collections/%-15s %s\n" "$h" "$(curl -s --max-time 20 -o /dev/null -w '%{http_code}' https://www.designerwallcoverings.com/collections/$h)"
+done
+
+if [ "$MODE" = "--dry-run" ]; then
+ echo; echo "== DRY RUN COMPLETE — nothing written. New template staged at index.NEW-$STAMP.json"
+ echo " To ship: $0 --apply"
+ exit 0
+fi
+
+echo "== 4. PUT section asset ($SECTION_KEY)"
+NEW="$NEW" KEY="$SECTION_KEY" DOMAIN="$DOMAIN" VER="$VER" THEME="$THEME" TOK="$TOK" node -e '
+ const fs=require("fs"),https=require("https");
+ const body=JSON.stringify({asset:{key:process.env.KEY,value:fs.readFileSync(process.env.NEW,"utf8")}});
+ const r=https.request({method:"PUT",host:process.env.DOMAIN,path:`/admin/api/${process.env.VER}/themes/${process.env.THEME}/assets.json`,headers:{"X-Shopify-Access-Token":process.env.TOK,"Content-Type":"application/json","Content-Length":Buffer.byteLength(body)}},res=>{let s="";res.on("data",d=>s+=d);res.on("end",()=>{console.log(" HTTP",res.statusCode);if(res.statusCode>=300)console.log(" ",s.slice(0,400));process.exit(res.statusCode<300?0:1)})});
+ r.on("error",e=>{console.error(e);process.exit(1)});r.write(body);r.end();'
+
+echo "== 5. PUT $INDEX_KEY (wires the section into the homepage)"
+OUT="$HERE/index.NEW-$STAMP.json" KEY="$INDEX_KEY" DOMAIN="$DOMAIN" VER="$VER" THEME="$THEME" TOK="$TOK" node -e '
+ const fs=require("fs"),https=require("https");
+ const body=JSON.stringify({asset:{key:process.env.KEY,value:fs.readFileSync(process.env.OUT,"utf8")}});
+ const r=https.request({method:"PUT",host:process.env.DOMAIN,path:`/admin/api/${process.env.VER}/themes/${process.env.THEME}/assets.json`,headers:{"X-Shopify-Access-Token":process.env.TOK,"Content-Type":"application/json","Content-Length":Buffer.byteLength(body)}},res=>{let s="";res.on("data",d=>s+=d);res.on("end",()=>{console.log(" HTTP",res.statusCode);if(res.statusCode>=300)console.log(" ",s.slice(0,400));process.exit(res.statusCode<300?0:1)})});
+ r.on("error",e=>{console.error(e);process.exit(1)});r.write(body);r.end();'
+
+echo "== 6. VERIFY live homepage"
+sleep 6
+N=$(curl -s -L --max-time 60 https://www.designerwallcoverings.com/ | grep -c "dwcf" || true)
+echo " dwcf markers on live homepage: $N"
+if [ "$N" -gt 0 ]; then echo " ✅ SHIPPED"; else
+ echo " ⚠️ not visible yet — recheck in ~60s; rollback: $0 --rollback"; fi
diff --git a/storefront/staged-tk10427/dw-collection-films.SECTION.liquid b/storefront/staged-tk10427/dw-collection-films.SECTION.liquid
new file mode 100644
index 0000000..3117afb
--- /dev/null
+++ b/storefront/staged-tk10427/dw-collection-films.SECTION.liquid
@@ -0,0 +1,105 @@
+{%- comment -%}
+ DW Collection Films — homepage video band featuring the flip-book lookbooks.
+ TK-10427. Three cards: Maya Romanoff / Glitter Walls / Majilite Metallics.
+
+ SECTION-FILE form: carries a schema block, so it can be wired into
+ templates/index.json by API — no theme-editor paste required. The identical
+ markup also exists as a Custom-Liquid paste blob in PATH-B-paste-3card.liquid.
+
+ Self-contained: all styles are .dwcf-prefixed, no theme objects, no
+ per-section settings. Videos + posters served from
+ marketing.designerwallcoverings.com; Shop CTAs are same-domain relative links
+ to live, published collections.
+{%- endcomment -%}
+
+<style>
+ .dwcf { max-width: 1280px; margin: 0 auto; padding: 56px 20px; font-family: inherit; }
+ .dwcf__head { text-align: center; margin-bottom: 34px; }
+ .dwcf__eyebrow { font-size: 12px; letter-spacing: .28em; text-transform: uppercase; color: #9a8f80; margin: 0 0 10px; }
+ .dwcf__title { font-size: clamp(26px, 4vw, 40px); font-weight: 400; letter-spacing: .01em; margin: 0; color: #1a1a1a; }
+ /* auto-fit + centered so the grid stays balanced at 2 OR 3 cards (no orphan column when a card is held) */
+ .dwcf__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 384px)); justify-content: center; gap: 24px; margin-top: 34px; }
+ @media (max-width: 680px){ .dwcf__grid { grid-template-columns: minmax(0, 420px); } }
+ .dwcf__card { position: relative; border-radius: 6px; overflow: hidden; background: #efe9df; box-shadow: 0 1px 0 rgba(0,0,0,.06); }
+ .dwcf__vidwrap { position: relative; aspect-ratio: 16/9; background:#000; }
+ .dwcf__vidwrap video { position:absolute; inset:0; width:100%; height:100%; object-fit: cover; display:block; }
+ .dwcf__play { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; cursor:pointer; background: linear-gradient(180deg, rgba(0,0,0,.05), rgba(0,0,0,.35)); transition: opacity .25s; border:0; padding:0; width:100%; }
+ .dwcf__play:focus-visible { outline: 3px solid #fff; outline-offset: -3px; }
+ .dwcf__play svg { width:64px; height:64px; filter: drop-shadow(0 2px 8px rgba(0,0,0,.4)); }
+ .dwcf__meta { padding: 18px 20px 22px; }
+ .dwcf__name { font-size: 20px; margin: 0 0 4px; color:#1a1a1a; }
+ .dwcf__sub { font-size: 13px; color:#8a8072; margin: 0 0 14px; letter-spacing:.02em; }
+ .dwcf__cta { display:inline-block; font-size: 12px; letter-spacing:.16em; text-transform:uppercase; color:#1a1a1a; text-decoration:none; border-bottom:1px solid #1a1a1a; padding-bottom:2px; }
+ .dwcf__cta:hover { color:#7a6f5f; border-color:#7a6f5f; }
+</style>
+
+<section class="dwcf">
+ <div class="dwcf__head">
+ <p class="dwcf__eyebrow">Collection Films</p>
+ <h2 class="dwcf__title">Leaf Through Our Lookbooks</h2>
+ </div>
+ <div class="dwcf__grid">
+
+ <div class="dwcf__card">
+ <div class="dwcf__vidwrap">
+ <video preload="none" playsinline controls
+ poster="https://marketing.designerwallcoverings.com/public/collections/maya-romanoff-poster.jpg">
+ <source src="https://marketing.designerwallcoverings.com/reels/maya-romanoff-flipbook.mp4" type="video/mp4">
+ </video>
+ <button type="button" class="dwcf__play" onclick="var v=this.previousElementSibling; v.play(); this.style.display='none';" aria-label="Play video">
+ <svg viewBox="0 0 64 64" fill="none" aria-hidden="true"><circle cx="32" cy="32" r="31" fill="rgba(255,255,255,.92)"/><path d="M26 20l20 12-20 12z" fill="#1a1a1a"/></svg>
+ </button>
+ </div>
+ <div class="dwcf__meta">
+ <h3 class="dwcf__name">Maya Romanoff</h3>
+ <p class="dwcf__sub">A Study in Materials — beadwork, marquetry, metallic luster & natural fiber</p>
+ <a class="dwcf__cta" href="/collections/maya-romanoff">Shop the Collection</a>
+ </div>
+ </div>
+
+ <div class="dwcf__card">
+ <div class="dwcf__vidwrap">
+ <video preload="none" playsinline controls
+ poster="https://marketing.designerwallcoverings.com/public/collections/glitter-walls-poster.jpg">
+ <source src="https://marketing.designerwallcoverings.com/reels/glitter-walls-flipbook.mp4" type="video/mp4">
+ </video>
+ <button type="button" class="dwcf__play" onclick="var v=this.previousElementSibling; v.play(); this.style.display='none';" aria-label="Play video">
+ <svg viewBox="0 0 64 64" fill="none" aria-hidden="true"><circle cx="32" cy="32" r="31" fill="rgba(255,255,255,.92)"/><path d="M26 20l20 12-20 12z" fill="#1a1a1a"/></svg>
+ </button>
+ </div>
+ <div class="dwcf__meta">
+ <h3 class="dwcf__name">Glitter Walls</h3>
+ <p class="dwcf__sub">Bring the Sparkle Home — sequins & glass beads in gold, silver, black & jewel tones</p>
+ <a class="dwcf__cta" href="/collections/glitter-walls">Shop the Collection</a>
+ </div>
+ </div>
+
+ <div class="dwcf__card">
+ <div class="dwcf__vidwrap">
+ <video preload="none" playsinline controls
+ poster="https://marketing.designerwallcoverings.com/public/collections/majilite-metallics-poster.jpg">
+ <source src="https://marketing.designerwallcoverings.com/reels/majilite-metallics-flipbook.mp4" type="video/mp4">
+ </video>
+ <button type="button" class="dwcf__play" onclick="var v=this.previousElementSibling; v.play(); this.style.display='none';" aria-label="Play video">
+ <svg viewBox="0 0 64 64" fill="none" aria-hidden="true"><circle cx="32" cy="32" r="31" fill="rgba(255,255,255,.92)"/><path d="M26 20l20 12-20 12z" fill="#1a1a1a"/></svg>
+ </button>
+ </div>
+ <div class="dwcf__meta">
+ <h3 class="dwcf__name">Majilite Metallics</h3>
+ <p class="dwcf__sub">A Colorway Story in Four Metals — faux-leather metallics on walls & furniture</p>
+ <a class="dwcf__cta" href="/collections/majilite">Shop the Collection</a>
+ </div>
+ </div>
+
+ </div>
+</section>
+
+{% schema %}
+{
+ "name": "Collection Films",
+ "tag": "section",
+ "class": "dwcf-section",
+ "settings": [],
+ "presets": [{ "name": "Collection Films" }]
+}
+{% endschema %}
← eeb5b35 auto-data-snapshot: 2026-09-03T11:10:51 (2 data files) — sto
·
back to Dw Marketing Reels
·
TK-10427: codex-review hardening — drop tag:section (no nest f637f7d →