← back to Designer Wallcoverings
stage: 20-dot color-palette cap — LIVE push script + pending-approval memo (gated)
83701d7588baa1262cc532fa88c3ad77efa4211f · 2026-07-09 13:58:14 -0700 · Steve
Files touched
A pending-approval/color-palette-20dot-cap-2026-07-09.mdA shopify/push-color-palette-20dot-cap-to-LIVE.sh
Diff
commit 83701d7588baa1262cc532fa88c3ad77efa4211f
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Jul 9 13:58:14 2026 -0700
stage: 20-dot color-palette cap — LIVE push script + pending-approval memo (gated)
---
.../color-palette-20dot-cap-2026-07-09.md | 52 +++++++++++++++++++
shopify/push-color-palette-20dot-cap-to-LIVE.sh | 58 ++++++++++++++++++++++
2 files changed, 110 insertions(+)
diff --git a/pending-approval/color-palette-20dot-cap-2026-07-09.md b/pending-approval/color-palette-20dot-cap-2026-07-09.md
new file mode 100644
index 00000000..5dfa6323
--- /dev/null
+++ b/pending-approval/color-palette-20dot-cap-2026-07-09.md
@@ -0,0 +1,52 @@
+# PENDING APPROVAL — PDP color palette: cap total dots at 20 (customer-facing, LIVE theme)
+
+**Date:** 2026-07-09
+**Owner:** vp-dw-commerce
+**Gate reason:** customer-facing change to the LIVE Shopify theme (#144076931123, 5.9.1)
+**Status:** BUILT + VERIFIED + COMMITTED locally. NOT pushed live. Awaiting Steve's go.
+
+## Steve's ask (verbatim)
+> "only show a total of 20 dots."
+
+## What changed
+File: `snippets/color-palette.liquid` (the PDP "Colors In This Pattern" swatch spectrum).
+
+- Added named constant **`MAX_SWATCHES = 20`** — a HARD cap on the TOTAL rendered dots (base + interpolated).
+- Added **`buildSteps(n)`**: given the real base-color count, returns how many interpolated in-between dots go in each of the `(n-1)` gaps so that `base + interpolated <= 20`.
+- Replaced the old single `STEPS = 20`-per-gap loop (which produced `base + 20×gaps` dots) with a per-gap step count from `buildSteps`.
+
+## Before → after count
+- **Before:** ~64 dots. Logic was every base color + a FIXED `STEPS=20` interpolated dots per pair → e.g. 4 bases = 4 + 3×20 = 64; up to 148 at the 8-base cap.
+- **After:** **exactly ≤ 20 dots**, always.
+
+## How the 20 are distributed
+Every REAL base color is KEPT (bases are already capped at ≤8, well under 20; if bases alone ever exceeded 20 the 20 most-dominant are kept — list is sorted by % desc). The leftover budget of interpolated dots is spread EVENLY across the gaps — `floor(budget/gaps)` each, with the remainder added +1 to the earliest gaps so the spectrum stays balanced left-to-right (NOT truncated at one end). Verified distribution for every base count:
+
+| bases | steps per gap | total |
+|------:|---------------|------:|
+| 1 | [] | 1 |
+| 2 | [18] | 20 |
+| 3 | [9,8] | 20 |
+| 4 | [6,5,5] | 20 |
+| 5 | [4,4,4,3] | 20 |
+| 6 | [3,3,3,3,2] | 20 |
+| 7 | [3,2,2,2,2,2] | 20 |
+| 8 | [2,2,2,2,2,1,1] | 20 |
+
+Each remaining dot keeps its existing click behavior (the 10% CIELAB color-index grid), hex fill, and the legend + proportion bar still reflect the real base colors. Unchanged.
+
+## Local verification (all $0 local)
+- `node --check` on both extracted `<script>` blocks → **SYNTAX OK**.
+- `shopify theme check` (before live vs after, minimal scaffold): **4 warnings before, 4 after — identical set** (`_dw_sku`/`_dw_hex`/`_dw_style` VariableName + `img_url` DeprecatedFilter). All pre-existing; **0 new offenses** from this change.
+- Headless Chromium render of a real PDP-shaped harness (6-band product image → 6 base colors): **`total_dots: 20` (6 base + 14 interpolated), within_cap = true, js_errors = []**. Clicking a base swatch **un-hid the color-index grid and rendered cards → `click_opens_index: true`.**
+
+## Commit
+- `c7633ec4` on branch `collection-layout-overhaul` (author steve@designerwallcoverings.com) captured the edited `snippets/color-palette.liquid` with `MAX_SWATCHES` + `buildSteps`. Clean vs HEAD. No git remote push.
+
+## EXACT push command for Steve to ship (LIVE, gated)
+```bash
+bash ~/Projects/Designer-Wallcoverings/shopify/push-color-palette-20dot-cap-to-LIVE.sh --yes-live
+```
+The script: resolves the LIVE (role=main) theme, backs up the current live snippet to `shopify/theme-backups/color-palette.LIVE-<id>.<stamp>.bak`, verifies the source contains the cap logic, then PUTs `snippets/color-palette.liquid` via the Asset API (store `designer-laboratory-sandbox.myshopify.com`, API `2024-10`, token `SHOPIFY_THEME_TOKEN`). It **refuses to run without `--yes-live`** (verified). Read-after-write replica lag ~5s before the change shows on a PDP.
+
+Rollback if needed: PUT the `.bak` back via the same Asset API.
diff --git a/shopify/push-color-palette-20dot-cap-to-LIVE.sh b/shopify/push-color-palette-20dot-cap-to-LIVE.sh
new file mode 100755
index 00000000..c275687d
--- /dev/null
+++ b/shopify/push-color-palette-20dot-cap-to-LIVE.sh
@@ -0,0 +1,58 @@
+#!/usr/bin/env bash
+# GATED (customer-facing) — Steve's go required. Ships the 20-dot cap on the PDP
+# "Colors In This Pattern" palette to the LIVE theme (#144076931123, 5.9.1).
+#
+# Change: snippets/color-palette.liquid — TOTAL rendered dots hard-capped at
+# MAX_SWATCHES=20 (was ~64). Keeps every real base color; interpolated in-between
+# dots distributed EVENLY across the gaps (buildSteps). Click/hex/legend/bar
+# behavior unchanged. Verified locally: node --check OK, theme check 0 NEW
+# offenses (4 pre-existing), headless render = exactly 20 dots + click opens the
+# color-index grid.
+#
+# Requires explicit confirmation: bash push-color-palette-20dot-cap-to-LIVE.sh --yes-live
+set -euo pipefail
+cd "$(dirname "$0")"
+
+[ "${1:-}" = "--yes-live" ] || { echo "Refusing: pass --yes-live to push to the LIVE theme."; exit 2; }
+
+STORE="designer-laboratory-sandbox.myshopify.com"
+API="2024-10"
+SECRETS="$HOME/Projects/secrets-manager/.env"
+SRC="theme-LIVE-591-freshpull-20260709/snippets/color-palette.liquid"
+KEY="snippets/color-palette.liquid"
+[ -f "$SRC" ] || { echo "Source snippet missing: $SRC"; exit 1; }
+
+TOK="${THEME_TOKEN:-$(grep -hE '^SHOPIFY_THEME_TOKEN=' "$SECRETS" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '"'"'"' ')}"
+[ -z "$TOK" ] && { echo "No theme token (SHOPIFY_THEME_TOKEN in secrets)."; exit 1; }
+echo "token …${TOK: -4}"
+
+mkdir -p theme-backups
+python3 - "$STORE" "$API" "$TOK" "$KEY" "$SRC" "theme-backups" <<'PY'
+import sys, json, urllib.request, urllib.parse, urllib.error, datetime
+store, api, tok, key, srcpath, bkdir = sys.argv[1:7]
+H = {"X-Shopify-Access-Token": tok, "Content-Type": "application/json"}
+def req(url, method="GET", body=None):
+ r = urllib.request.Request(url, headers=H, method=method,
+ data=json.dumps(body).encode() if body else None)
+ return json.load(urllib.request.urlopen(r))
+
+live = next(t for t in req(f"https://{store}/admin/api/{api}/themes.json")["themes"] if t["role"] == "main")
+tid = live["id"]
+print(f'=== LIVE target: {tid} "{live["name"]}" (role=main) ===')
+
+# backup current live snippet
+q = urllib.parse.urlencode({"asset[key]": key})
+cur = req(f"https://{store}/admin/api/{api}/themes/{tid}/assets.json?{q}")["asset"]["value"]
+stamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
+bk = f"{bkdir}/color-palette.LIVE-{tid}.{stamp}.bak"
+open(bk, "w").write(cur); print(f"backup: {bk} ({len(cur)} bytes)")
+
+new = open(srcpath).read()
+if "MAX_SWATCHES" not in new or "buildSteps" not in new:
+ print("Refusing: source snippet does not contain the 20-dot-cap logic."); sys.exit(1)
+
+resp = req(f"https://{store}/admin/api/{api}/themes/{tid}/assets.json",
+ method="PUT", body={"asset": {"key": key, "value": new}})
+print("pushed:", resp["asset"]["key"], "updated_at", resp["asset"].get("updated_at"))
+print("=== LIVE color-palette.liquid updated — dots now capped at 20 ===")
+PY
← e3b55716 cadence: Quadrille-house 54-inch rule → Fabric (Steve 2026-0
·
back to Designer Wallcoverings
·
cadence: QH real-width classifier — 54in→Fabric, 27in→Wallco f72482f6 →