[object Object]

← back to Designer Wallcoverings

Coastal Mist live fix APPLIED + verified: real image, oil filter removed, Red/Needs-Image stripped; script hardened (no-eval, idempotent, token-masked)

d436f9a2764309d7764b305d3322b2af091d66dc · 2026-06-24 08:04:58 -0700 · Steve

Files touched

Diff

commit d436f9a2764309d7764b305d3322b2af091d66dc
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Jun 24 08:04:58 2026 -0700

    Coastal Mist live fix APPLIED + verified: real image, oil filter removed, Red/Needs-Image stripped; script hardened (no-eval, idempotent, token-masked)
---
 .../coastal-mist-oil-filter-image-fix.md           | 12 +++-
 pending-approval/fix-coastal-mist-BA469.sh         | 81 +++++++++++++---------
 2 files changed, 61 insertions(+), 32 deletions(-)

diff --git a/pending-approval/coastal-mist-oil-filter-image-fix.md b/pending-approval/coastal-mist-oil-filter-image-fix.md
index 8bdd5cf0..be354e92 100644
--- a/pending-approval/coastal-mist-oil-filter-image-fix.md
+++ b/pending-approval/coastal-mist-oil-filter-image-fix.md
@@ -1,9 +1,19 @@
 # PENDING APPROVAL — Live Shopify fix: "Portofino Bamboo - Coastal Mist" wrong image + bad color tag
 
-**Status:** awaiting Steve sign-off (customer-facing prod write — NOT executed by agent)
+**Status:** ✅ DONE — applied live 2026-06-24 (Steve-authorized; "use old tokens" / "run the live fix")
 **Drafted:** 2026-06-24
 **Decision basis:** DTD verdict A (3/3 unanimous — Claude/Codex/Qwen)
 
+## ✅ APPLIED 2026-06-24 — verified live
+- New image `37570967371827` (960×1200, alt = "Portofino Bamboo - Coastal Mist Wallcovering | Phillipe Romano") at position 1.
+- Oil-filter image `37091234611251` deleted.
+- Tags `Red` + `Needs-Image` stripped; `color:Smoke` + `display_variant` + all else kept.
+- Confirmed on the public storefront (designerwallcoverings.com): single correct image, clean tags.
+- Canonical CDN image now: `https://cdn.shopify.com/s/files/1/0015/4117/7456/files/portofino-bamboo-coastal-mist.jpg?v=1782313353`
+- philipperomano local data repointed to that canonical CDN URL.
+- ⚠ Token rotation declined by Steve ("use old tokens") — `shpat_…75b9` was printed once to a local terminal; not externally transmitted. Script now masks it.
+- ℹ️ dw_unified `shopify_products` mirror still holds the old image/tags; it self-heals from Shopify on the daily sync (sync-shopify-mirror.js).
+
 ## UPDATE 2026-06-24 — genuine image FOUND + ready-to-run script
 - **Real SKU:** Washington Wallcoverings **BA469** "Smoke Gray Sisal" (Best of Asia IV). "Phillipe Romano Portofino Bamboo – Coastal Mist" is the DW private-label rebrand. `color:Smoke` was correct all along.
 - **Root cause (confirmed):** importer fuzzy-matched real SKU **BA469 → B7469** (a Baldwin oil filter) — a 2-char string collision.
diff --git a/pending-approval/fix-coastal-mist-BA469.sh b/pending-approval/fix-coastal-mist-BA469.sh
index a9cb6986..ae8d2922 100755
--- a/pending-approval/fix-coastal-mist-BA469.sh
+++ b/pending-approval/fix-coastal-mist-BA469.sh
@@ -6,13 +6,14 @@
 # (designerwallcoverings.com), which currently shows a Baldwin B7469 automotive
 # oil filter. Root cause: importer fuzzy-matched real SKU BA469 -> file B7469.
 #
-# Real product  : Washington Wallcoverings BA469 "Smoke Gray Sisal" (Best of Asia IV)
-# Verified image: https://www.interiordecorating.com/images/cat/wall/washington/BA469.jpg
-#                 (confirmed grey sisal grasscloth; matches color:Smoke tag)
-#                 ⚠ low-res 200x250 — see HI-RES note at bottom before going live.
+# Real product : Washington Wallcoverings BA469 "Smoke Gray Sisal" (Best of Asia IV).
+# Image        : coastal-mist-hires.jpg (full-res 960x1200), uploaded as a base64
+#                attachment so Shopify rehosts on the DW CDN.
+# Alt text     : OUR info only ("Portofino Bamboo - Coastal Mist Wallcovering |
+#                Phillipe Romano") — source vendor never exposed.
 #
-# Three steps: (1) add the real BA469 image, (2) delete the oil-filter image,
-# (3) strip the wrong `Red` tag and (image now present) `Needs-Image`.
+# Three steps: (1) add the real image, (2) delete the oil-filter image,
+# (3) strip the wrong `Red` tag and `Needs-Image` (keep color:Smoke + all else).
 #
 # To execute for real:   CONFIRM=1 bash fix-coastal-mist-BA469.sh
 # ─────────────────────────────────────────────────────────────────────────────
@@ -22,9 +23,8 @@ SHOP="designer-laboratory-sandbox.myshopify.com"
 API="2024-10"
 PRODUCT_ID="7808267157555"
 OIL_FILTER_IMAGE_ID="37091234611251"
-NEW_IMAGE_FILE="$(dirname "$0")/coastal-mist-hires.jpg"   # full-res 960x1200 (in this dir)
-NEW_IMAGE_ALT="Portofino Bamboo - Coastal Mist Wallcovering | Phillipe Romano"  # OUR info only — no source-vendor leak
-TAGS_TO_REMOVE=("Red" "Needs-Image")   # keep color:Smoke and everything else
+NEW_IMAGE_FILE="$(dirname "$0")/coastal-mist-hires.jpg"
+NEW_IMAGE_ALT="Portofino Bamboo - Coastal Mist Wallcovering | Phillipe Romano"
 
 TOKEN="${SHOPIFY_ADMIN_TOKEN:-}"
 if [ -z "$TOKEN" ]; then
@@ -33,12 +33,12 @@ fi
 [ -z "$TOKEN" ] && { echo "ERROR: SHOPIFY_ADMIN_TOKEN not in env or secrets-manager/.env"; exit 1; }
 
 BASE="https://$SHOP/admin/api/$API"
-H_TOK=(-H "X-Shopify-Access-Token: $TOKEN")
+H_TOK=(-H "X-Shopify-Access-Token: $TOKEN")   # always passed as a quoted array — never eval'd
 H_JSON=(-H "Content-Type: application/json")
 DRY=1; [ "${CONFIRM:-0}" = "1" ] && DRY=0
 
 say(){ printf '\n\033[1m%s\033[0m\n' "$*"; }
-run(){ if [ "$DRY" = "1" ]; then echo "  [dry-run] ${*//$TOKEN/shpat_****${TOKEN: -4}}"; else eval "$*"; fi; }  # mask token in dry echoes
+DESC(){ echo "  [dry-run] would: $*"; }   # human description only — never prints the token
 
 say "Target: $PRODUCT_ID on $SHOP (api $API)  | mode: $([ "$DRY" = 1 ] && echo DRY-RUN || echo LIVE)"
 
@@ -48,7 +48,10 @@ curl -s "${H_TOK[@]}" "$BASE/products/$PRODUCT_ID.json" \
 
 say "1) Add full-res image as base64 attachment (Shopify hosts it on its CDN)"
 [ -f "$NEW_IMAGE_FILE" ] || { echo "ERROR: image file not found: $NEW_IMAGE_FILE"; exit 1; }
-PAYLOAD="$(mktemp /tmp/cm-img.XXXXXX)"   # BSD mktemp: X's must be at the end (no suffix)
+# idempotency: skip if our image is already attached (so a re-run can't duplicate it)
+ALREADY="$(curl -s "${H_TOK[@]}" "$BASE/products/$PRODUCT_ID.json" | python3 -c 'import json,sys;print("yes" if any("portofino-bamboo-coastal-mist" in (i.get("src") or "") for i in json.load(sys.stdin)["product"]["images"]) else "no")')"
+if [ "$ALREADY" = "yes" ]; then echo "  already attached — skipping add"; SKIP1=1; else SKIP1=0; fi
+PAYLOAD="$(mktemp /tmp/cm-img.XXXXXX)"
 python3 - "$NEW_IMAGE_FILE" "$NEW_IMAGE_ALT" > "$PAYLOAD" <<'PY'
 import base64, json, sys
 path, alt = sys.argv[1], sys.argv[2]
@@ -57,15 +60,29 @@ with open(path, "rb") as f:
 print(json.dumps({"image": {"attachment": b64, "filename": "portofino-bamboo-coastal-mist.jpg",
                             "position": 1, "alt": alt}}))
 PY
-run "curl -s -X POST \"$BASE/products/$PRODUCT_ID/images.json\" ${H_TOK[*]} ${H_JSON[*]} \
-  --data-binary @\"$PAYLOAD\" \
-  | python3 -c 'import json,sys;i=json.load(sys.stdin).get(\"image\",{});print(\"  added image id\",i.get(\"id\"),i.get(\"width\"),\"x\",i.get(\"height\"),i.get(\"src\"))'"
-[ "$DRY" = "0" ] && rm -f "$PAYLOAD" || echo "  [dry-run] payload built at $PAYLOAD (alt: $NEW_IMAGE_ALT)"
+if [ "$SKIP1" = 1 ]; then
+  rm -f "$PAYLOAD"
+elif [ "$DRY" = 1 ]; then
+  DESC "POST $BASE/products/$PRODUCT_ID/images.json  (base64 attachment, alt: $NEW_IMAGE_ALT)"
+  echo "  [dry-run] payload built at $PAYLOAD ($(wc -c < "$PAYLOAD") bytes)"
+  rm -f "$PAYLOAD"
+else
+  RESP="$(curl -s -w $'\n%{http_code}' -X POST "$BASE/products/$PRODUCT_ID/images.json" "${H_TOK[@]}" "${H_JSON[@]}" --data-binary @"$PAYLOAD")"
+  CODE="${RESP##*$'\n'}"; BODY="${RESP%$'\n'*}"
+  rm -f "$PAYLOAD"
+  echo "  POST HTTP $CODE"
+  case "$CODE" in 200|201) echo "$BODY" | python3 -c 'import json,sys;i=json.load(sys.stdin).get("image",{});print("  added image id",i.get("id"),i.get("width"),"x",i.get("height"),i.get("src"))';;
+    *) echo "  ERROR body: $BODY"; exit 1;; esac
+fi
 
 say "2) Delete the oil-filter image ($OIL_FILTER_IMAGE_ID)"
-run "curl -s -X DELETE \"$BASE/products/$PRODUCT_ID/images/$OIL_FILTER_IMAGE_ID.json\" ${H_TOK[*]} -o /dev/null -w '  delete HTTP %{http_code}\n'"
+if [ "$DRY" = 1 ]; then
+  DESC "DELETE $BASE/products/$PRODUCT_ID/images/$OIL_FILTER_IMAGE_ID.json"
+else
+  curl -s -X DELETE "$BASE/products/$PRODUCT_ID/images/$OIL_FILTER_IMAGE_ID.json" "${H_TOK[@]}" -o /dev/null -w '  delete HTTP %{http_code}\n'
+fi
 
-say "3) Strip wrong tags (${TAGS_TO_REMOVE[*]}) — reads LIVE tags first, removes only these"
+say "3) Strip wrong tags (Red, Needs-Image) — reads LIVE tags first, removes only these"
 CUR_TAGS="$(curl -s "${H_TOK[@]}" "$BASE/products/$PRODUCT_ID.json" | python3 -c 'import json,sys;print(json.load(sys.stdin)["product"]["tags"])')"
 NEW_TAGS="$(python3 - "$CUR_TAGS" <<'PY'
 import sys
@@ -76,21 +93,23 @@ PY
 )"
 echo "  before: $CUR_TAGS"
 echo "  after : $NEW_TAGS"
-run "curl -s -X PUT \"$BASE/products/$PRODUCT_ID.json\" ${H_TOK[*]} ${H_JSON[*]} \
-  -d \"\$(python3 -c 'import json,sys;print(json.dumps({\"product\":{\"id\":int(\"$PRODUCT_ID\"),\"tags\":sys.argv[1]}}))' \"\$NEW_TAGS\")\" \
-  -o /dev/null -w '  tag update HTTP %{http_code}\n'"
+if [ "$DRY" = 1 ]; then
+  DESC "PUT $BASE/products/$PRODUCT_ID.json  tags -> (the 'after' line above)"
+else
+  TAGJSON="$(python3 -c 'import json,sys;print(json.dumps({"product":{"id":int(sys.argv[1]),"tags":sys.argv[2]}}))' "$PRODUCT_ID" "$NEW_TAGS")"
+  curl -s -X PUT "$BASE/products/$PRODUCT_ID.json" "${H_TOK[@]}" "${H_JSON[@]}" -d "$TAGJSON" -o /dev/null -w '  tag update HTTP %{http_code}\n'
+fi
 
 say "4) Verify"
-[ "$DRY" = "0" ] && curl -s "${H_TOK[@]}" "$BASE/products/$PRODUCT_ID.json" \
-  | python3 -c 'import json,sys;p=json.load(sys.stdin)["product"];print("  images:",[i["src"].split("/")[-1] for i in p["images"]]);print("  tags  :",p["tags"])' \
-  || echo "  (dry-run: skipped verify)"
+if [ "$DRY" = 0 ]; then
+  curl -s "${H_TOK[@]}" "$BASE/products/$PRODUCT_ID.json" \
+    | python3 -c 'import json,sys;p=json.load(sys.stdin)["product"];print("  images:",[i["src"].split("/")[-1] for i in p["images"]]);print("  tags  :",p["tags"])'
+else
+  echo "  (dry-run: skipped verify)"
+fi
 
 say "Done ($([ "$DRY" = 1 ] && echo 'DRY-RUN — re-run with CONFIRM=1 to apply' || echo 'LIVE applied'))."
 # ── Notes ────────────────────────────────────────────────────────────────────
-# • Image: full-res 960x1200 (coastal-mist-hires.jpg, sourced from the real
-#   product BA469). Uploaded as a base64 attachment so Shopify rehosts it on the
-#   DW CDN — no hotlink/referer dependency on the source site.
-# • Alt text is OUR info only ("Portofino Bamboo - Coastal Mist Wallcovering |
-#   Phillipe Romano") — the source vendor (Washington Wallcoverings/BA469) is
-#   never exposed customer-facing.
-# • To keep Needs-Image set for now, remove "Needs-Image" from TAGS_TO_REMOVE.
+# • Curl always receives the auth header via the "${H_TOK[@]}" array (never eval),
+#   so the token is correctly quoted and never printed.
+# • To keep Needs-Image set for now, edit the drop set in step 3 to {"red"}.

← e0e417a2 fix script: mask admin token in dry-run echoes + BSD-safe mk  ·  back to Designer Wallcoverings  ·  Boost infinite-scroll override: force boostSDAppConfig pagin fafab4ef →