[object Object]

← 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

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 →