This masterclass is the reproducible 18-round playbook used to upgrade Design Delight Studio from a generic Atelier Shopify theme to a brand-compliant, dark-themed, SEO-optimized, performance-tuned store in May 2026. The pattern is built around four non-negotiables: a Brand Bible (Verified Facts plus Forbidden Strings) comes before any code; one concern per round so failure attribution stays clean; an 8-check Python pre-push validation gate runs on every push; and a live browser audit via Chrome MCP verifies behavior after every push. The result: a 57.65% image weight reduction, a 4.14-second LCP improvement on product pages, zero brand-bible violations, and a self-contained document a fresh Claude Code instance can use on any Shopify Online Store 2.0 theme.
The 60-Second TL;DR
Six measured outcomes that define what this playbook actually delivered when applied end-to-end to a live Shopify store:
5 Strategic Takeaways
- A Shopify theme upgrade is never one prompt. It is a sequence of focused operations, each gated by validation and live verification. Mixing concerns guarantees broken attribution when a push fails.
- The Brand Bible is the unfair advantage. Without verified facts and forbidden strings, AI generates plausible but non-compliant copy. With them, the agent becomes a reliable brand-voice executor.
- Static validation is necessary but not sufficient. The 8-check Python gate catches syntax and structural failures. Live browser audit catches CSS specificity wars, DOM mutation bugs, and Atelier-specific quirks that static checks never see.
- Atelier has two killer gotchas. Custom UUID color schemes do not emit CSS, so palette tokens must be hardcoded in section CSS. The framework morphs the DOM during navigation, so event listeners attached to elements die. Both eat hours when undiagnosed.
- The playbook compounds. Round 1 took longest because the Brand Bible had to be assembled. Round 18 was fast because the validation script and prompt library were already in place. The next project starts at round 8 because the infrastructure carries over.
Why This Document Exists
A Shopify theme upgrade done well is not a single prompt. It is a series of focused operations, each gated by validation and live verification. This masterclass captures the exact pattern used to upgrade DDS Boston from a generic Atelier installation to a brand-compliant, dark-themed, SEO-optimized, performance-tuned store.
The pattern works because of four reinforcing constraints:
Brand Bible Comes First
Without a verified-facts whitelist and a forbidden-strings list, AI generates plausible but non-compliant copy. The Brand Bible is assembled before any code is written and acts as both the source of truth for legitimate claims and the abort trigger for violations.
One Concern Per Round
Mixing concerns (dark theme plus new SEO plus content reframe plus image swap) makes pre-push validation impossible to interpret. If a 12-file push breaks, you cannot tell which concern caused the failure. Push one concern, verify, then push the next.
Live Audit Gates Every Push
Static validation catches syntax. Live browser audit catches CSS specificity wars and DOM mutation bugs. After every push, a Chrome MCP audit runs against the testing theme: H1 count, JSON-LD validity, OG meta presence, forbidden-string sweep on rendered text, interactive checks for any changed behavior.
8-Check Pre-Push Is Non-Negotiable
NULL byte sweep, JSON parse and symmetry, schema name limits, no empty text defaults, range step alignment, Liquid block balance, no comment tags inside Liquid blocks, forbidden strings sweep. If any check fails, the push is aborted and the failure is fixed before retry.
The combination is the unlock. Any one of these constraints feels excessive in isolation. Together they make the 18-round upgrade not just possible but predictable.
Foundation: Brand Bible Setup
This is the most important section of the playbook. Skip it at your peril.
Before any code is written, the AI agent must internalize three things: a Verified Facts Whitelist, a Forbidden Strings Whitelist, and a set of Brand Color Tokens plus Typography. The Brand Bible is the document the entire build sequence references — every reframe pulls from it, every pre-push validation checks against it, every live audit verifies the rendered page does not violate it.
Robert's standing rule, captured verbatim because it defines the entire posture: "Don't invent facts, numbers, pricing, certifications, partners, policies, results, reviews, or timelines. If unsure, state 'Not verified' and what to check." Every claim must trace back to the Verified Facts Whitelist. Every reframe must be supplier-attributed. Every number must cite a source.
1. Verified Facts Whitelist
A document listing every claim the brand can make. For DDS specifically, this covers the supply chain, the material specs, the geographic claims, and the certification ownership chain. The shape is more important than the specific values — a fresh agent on a new project re-builds this for the new brand's supply chain.
Some specific commercial figures are redacted on this public page (Printful list pricing, exact OEKO-TEX cert numbers). The structure stays:
Certification ownership chain (the structure that matters)
DDS holds NO direct cert licenses.
Stanley/Stella holds: GOTS + GRS + OCS + OEKO-TEX + PETA-Approved Vegan
econscious holds: GOTS + OCS + OEKO-TEX + PETA-Approved Vegan
Printful holds: GRS + OEKO-TEX (for in-house cut & sew on AOP products)
Stanley/Stella OEKO-TEX cert: [supplier-held, verified in OEKO-TEX registry]
issued by Centexbel
econscious OEKO-TEX cert: [supplier-held, verified in OEKO-TEX registry]
issued by Hohenstein
DDS is a print-on-demand brand. The blank apparel ships from Stanley/Stella's mills. Printful prints the designs. DDS does not manufacture, does not hold direct cert licenses, and does not legally control the production chain. Any claim that says "DDS is GOTS certified" is false. Any claim that says "Stanley/Stella holds GOTS certification for the blanks we use" is true and verifiable. The Brand Bible enforces this distinction so the agent cannot drift into legally questionable copy.
Material specs per product line
These are Stanley/Stella and econscious public spec-sheet numbers. They are not commercial secrets and they survive in this masterclass:
| SKU | Type | Composition | Weight | Size Range |
|---|---|---|---|---|
STTU169 | Unisex tee | 100% organic ring-spun cotton | 5.3 oz/yd² (180 g/m²) | XS-3XL |
SATU007 | Crafter mid-light tee | 100% organic combed ring-spun cotton | 4.6 oz/yd² (155 g/m²) | S-2XL |
SASU009 | Drummer 2.0 hoodie | 80% organic cotton + 20% recycled poly (US) / 85+15 (EU) | 8.3 oz/yd² (280 g/m²) | S-2XL |
SASU010 | Roller sweatshirt | Same as SASU009 | 8.3 oz/yd² (280 g/m²) | S-2XL |
AOP #141 | AOP recycled sweatshirt | 96% recycled poly + 4% elastane (US/MX) / 95+5 (EU) | 9.08 oz/yd² (308 g/m²) | XS-2XL |
EC8000 | econscious tote | 100% organic cotton 3/1 twill | 8 oz/yd² (272 g/m²) | 16"×14.5"×5" |
Geographic claims allow / deny
| Claim | Status | Reason |
|---|---|---|
| Designed in Boston | ALLOWED | The design work happens in Boston. Verifiable. |
| Boston-designed | ALLOWED | Same as above, just phrasing variant. |
| Made-to-order, designed in Boston | ALLOWED | Both halves verifiable. |
| Made in Boston | FORBIDDEN | Manufacturing happens at Printful's POD network, not in Boston. |
| Boston-made | FORBIDDEN | Same reason. |
| Boston-crafted | FORBIDDEN | Same reason. |
| Hand-finished in Boston | FORBIDDEN | No finishing happens in Boston. |
| Crafted in Boston | FORBIDDEN | Same reason as Made in Boston. |
| Made-to-order in Boston | FORBIDDEN | Production is global POD, not Boston-based. |
Public list pricing
Specific Printful list prices and shipping rates are redacted in this public version. The pattern that matters for any agent applying this playbook: every price quoted in the storefront must be reconciled monthly against the supplier's public catalog, with a dated annotation in the Verified Facts document.
2. Forbidden Strings Whitelist (Pre-Push Blocker)
Any string in this list found in any pushed file triggers an immediate ABORT at the pre-push gate. The list is paste-ready Python. Copy it directly into the validation script.
FORBIDDEN_STRINGS = [
# === Geographic — manufacturing claims (DDS doesn't manufacture) ===
'Made in Boston', 'Boston-made', 'Boston-crafted',
'Hand-finished in Boston', 'Made-to-order in Boston',
'Crafted in Boston',
# === Certification ownership (DDS holds no direct cert licenses) ===
'We hold 5 certifications', 'We hold 6 certifications',
'Our products are GOTS', 'DDS is X-certified',
'DDS is GOTS', 'DDS is GRS', 'DDS is OEKO',
'OEKO-TEX certified', 'OEKO-TEX-certified',
'Fair Trade certified', 'Fair Trade-certified',
'Fair Trade Manufacturing',
'Verify Our Certifications',
# === Absolute / unverifiable claims ===
'100% Defect Guarantee', 'Toxin Free Dyes',
'Toxin-Free Dyes', 'Sustainable fashion only',
'Zero waste', 'Zero-waste', 'Zero-Waste',
'Zero-Waste Packaging', 'sustainable packaging',
'recyclable packaging', 'exclusively organic',
'exclusively use that 1%',
# === Stale marketing copy ===
'10-Year Care Guide', '10+ Year Lifespan',
'10-Year Lifespan', '10-Year Organic Cotton Care Guide',
'Free 10-Year', 'Weekly sustainable style tips',
'95% Less Water', '95% less water',
'91% less water', '% less water',
# === Geographic shipping false-claims ===
'Free US Shipping', 'Free U.S. Shipping',
'U.S.A delivery', 'U.S.A shipping',
# === Old CTA copy (R3.17 reframed) ===
'Social Media Ready', # use lowercase 'Social-media ready'
'100% Private', # use 'Private' without the absolute
'GOTS Organic. Sustainable fashion only',
'GOTS certified organic cotton apparel', # too narrow
'Shop Sustainable Fashion with Total Confidence',
]
Compliant reframe pairs
Every forbidden string maps to a verified, supplier-attributed alternative. The reframe table that drove the R3.13 bulk sweep:
| Forbidden | Compliant Reframe | Why |
|---|---|---|
OEKO-TEX certified | OEKO-TEX Standard 100 substrate | Attributes cert to the material, not the brand |
Fair Trade certified | Fair Trade supplier-certified | Cert is held by supplier, not by DDS |
Made-to-order in Boston | Made-to-order, designed in Boston | Splits design (verifiable) from production (POD network) |
95% Less Water | Lower water footprint | Removes uncited absolute percentage |
10+ Year Lifespan | Built to last for years | Removes unverifiable timeline |
Free US Shipping | Free Worldwide Shipping | DDS ships globally, not US-only |
Verify Our Certifications | Verify Material Certifications | Cert ownership at material level |
Shop Sustainable Fashion with Total Confidence | See the Fit. Skip the Returns. | Substrate-attributed copy that works across product types |
3. Brand Color Tokens + Typography
The locked palette and typography stack. Hardcode these in every section CSS. Do not rely on Atelier emitting them via custom color schemes — Principle 5 explains why.
/* DDS Brand Palette */
--dds-deep: #0a1a0f; /* darkest forest */
--dds-forest: #0d2818; /* card surface bg */
--dds-teal: #1a5c45; /* secondary success */
--dds-emerald: #2d8a6e; /* CTA accent */
--dds-mint: #4fb38c; /* meta text on dark */
--dds-gold: #c9a84c; /* primary CTA */
--dds-gold-bright: #e8c84a; /* hover state */
--dds-cream: #f5f0e8; /* primary text on dark */
--dds-cream-soft: rgba(245, 240, 232, 0.72); /* muted text */
--dds-white: #fafaf7; /* high-contrast text */
/* Typography — self-hosted WOFF2 only, NEVER Google Fonts CDN */
font-display: 'Playfair Display', Georgia, serif;
font-body: 'DM Sans', system-ui, -apple-system, sans-serif;
font-mono: 'JetBrains Mono', 'Menlo', 'Consolas', monospace;
Self-hosted, not Google Fonts CDN. Atelier 2.1.1 ships with Playfair Display and DM Sans pre-loaded as WOFF2. Adding a Google Fonts CDN link would duplicate the loads, hurt LCP, and introduce a cross-origin dependency. The stack uses what the theme already serves.
"Don't invent facts, numbers, pricing, certifications, partners, policies, results, reviews, or timelines. If unsure, state 'Not verified' and what to check."
Every claim must come from the Verified Facts Whitelist. Every reframe must be supplier-attributed. Every number must cite a source. This is the rule that makes AI-generated brand copy actually publishable instead of plausibly fabricated.
The 8 Operating Principles
These principles govern every build round. Some are workflow discipline (one concern per round). Some are framework-specific gotchas (Atelier custom-scheme CSS does not emit). All eight had to be in place before the 18-round sequence could run cleanly. Skip any one of them and the validation gate stops being a gate.
Principle 1 — Silent Multi-Phase Builds
For any task touching more than 4 files or more than 1,000 lines of code, the agent works silently in a scratch workspace, validates each phase, and only presents the final assembled output via present_files. Intermediate code is never shown.
Report format during silent build: "Phase N complete (one-line description)."
Principle 2 — One Concern Per Round
Each push must address exactly one concern:
| Round type | What it does |
|---|---|
| R3.0 — Reframe sweep | Bulk string replacement for verified-facts compliance |
| R3.6 — New section | Build a new Liquid section + wire into templates |
| R3.10 — Dark theme | Convert one section's CSS palette |
| R3.13 — Audit cleanup | Fix violations from a live audit run |
| R3.15 — JS interaction fix | Fix one broken interaction (size guide, variant picker) |
Mixing concerns makes failure attribution impossible. If a 12-file push breaks, was it the dark theme tokens or the JSON-LD parse fix or the cert reframe? Push one concern, verify, then push the next concern.
Principle 3 — 8-Check Pre-Push Validation (Mandatory Gate)
Every file in a push set must pass all 8 checks. The full Python script lives in the Validation Toolkit section. The check list:
[1] NULL byte sweep — strip any \x00 bytes that snuck in
[2] JSON validity + symmetry — parse cleanly; sections.keys() == order
[3] Schema validity — JSON parses; name ≤ 25 chars
[4] No "default": "" on text — Shopify rejects empty text defaults
[5] Range step alignment — default value aligned to step from min
[6] Liquid block balance — if/endif, for/endfor, etc. paired
[7] No comment/endcomment inside {% liquid %} block (Shopify parser bug)
[8] Forbidden strings sweep — ABORT if any banned term present
If any check fails, the push is aborted and the failure is fixed before retry. Not "fix and push anyway." Not "fix as part of the next push." Stop, fix, re-validate, then push.
Principle 4 — Live Browser Audit Between Rounds
After every push, run a Chrome MCP audit against the testing theme preview URL. The audit captures:
- H1 count per page — must be exactly 1
- JSON-LD valid count + error count — 0 errors
- OG and Twitter meta presence — page-type appropriate
- Robots meta — noindex on /404 and /search
- Forbidden-string sweep on rendered text — not just source code
- Color contrast spot-checks — no dark-on-dark
- Interactive checks — size guide accordions open, variant pickers swap images, color toggles work
Static validation passes ≠ live behavior works. Atelier morphs DOM, hides selectors, overrides CSS. Only live audit catches these.
Principle 5 — Atelier Does NOT Auto-Emit Custom Color-Scheme CSS
- Atelier emits CSS for built-in schemes (
scheme-1throughscheme-6) - Atelier does NOT emit CSS for custom UUID schemes (
scheme-09beadd5-…) - Even if you add
color_schemeto a section schema and default it to a UUID scheme,--color-backgroundfalls back to white at runtime - The fix is to HARDCODE the palette in the section's CSS, not rely on scheme emission
Read it twice. This is the single most expensive Atelier gotcha in the entire project.
Principle 6 — Atelier Morphs the DOM, Killing Event Listeners
Stock section IIFE pattern ((function(){...})() at end of section file) adds event listeners on page load. Atelier morphs sections during navigation — listeners attached to morphed elements no longer fire.
Solution: Document-level event delegation with capture phase. Listeners on document survive any morphing.
// IIFE at end of section file
(function(){
var container = document.querySelector(
'.ops-' + sectionId
);
if (!container) return;
var trigger = container.querySelector(
'[data-sizeguide-trigger]'
);
trigger.addEventListener('click', function(){
// toggle accordion — dies after navigation
});
})();
// Document-level, capture phase
document.addEventListener('click', function(e){
var trigger = e.target.closest &&
e.target.closest('[data-sizeguide-trigger]');
if (!trigger) return;
var content = trigger.parentElement
.querySelector('[data-sizeguide-content]');
if (content) {
content.classList.toggle('is-open');
trigger.setAttribute('aria-expanded',
content.classList.contains('is-open'));
}
}, true); // capture phase
Principle 7 — Section Wrapper Must Set Its Own background-color
Do not assume the wrapper inherits a background. If your section has dark theme tokens but no background-color: var(--ops-bg) on the root wrapper, the section sits transparent over Atelier's white body.
.ops-{{ section.id }} {
background-color: var(--ops-bg) !important; /* !important defeats Atelier */
color: var(--ops-text);
/* ...rest of layout... */
}
The !important is not stylistic — it is functional. Atelier's body-level rules have higher specificity than ordinary section wrapper rules. The override is the only thing that ensures the dark palette wins.
Principle 8 — Settings Persist Even When Schema Changes
If a section instance's stored settings include text_color: "#1f2937" (dark gray, from an old light-theme default) and you later change the section schema default to #f5f0e8 (cream), the OLD value persists on existing instances. Your | default: '#f5f0e8' Liquid fallback only fires when the setting is blank, not when it is set to the wrong value.
--ops-text: {{
section.settings.text_color
| default: '#f5f0e8'
}};
--ops-text: #f5f0e8;
Hardcode brand colors in CSS. Do not read from section.settings.text_color for tokens that should be brand-locked. The schema setting can stay for compatibility, but the runtime CSS variable should never depend on it.
The 18-Round Build Sequence
Each round is a discrete push. The trigger shows what user signal or audit finding kicked it off. The prompt shows what the AI agent was asked, paste-ready for re-use on the next project. The pattern describes what actually shipped.
Read the rounds in order the first time. After that, treat them as a reference index — most upgrade projects will not need all 18 rounds, but knowing which one matches your current signal saves the design work.
Verified Facts Reframe Sweep
sections/ and templates/. For every match against the forbidden-strings list, replace with the supplier-attributed equivalent from the verified-facts whitelist. Report all changes in a diff. Validate JSON files still parse. Do not push yet — show me the diff first.
The Round 0 of any upgrade. Forbidden → reframed mapping defined upfront, mass-replaced, and gated on diff review before push. This is the round where you discover how much non-compliant copy lives in your theme.
Build Cert Verification Table for Transparency Page
dds-cert-verification-table.liquid. Render a 6-row table: GOTS / GRS / OCS / OEKO-TEX / PETA / Fair Trade. Each row has supplier name, cert number, registry URL, and a "Verify" link that opens the registry in a new tab. Use DDS dark palette. Include WebPage JSON-LD with about[] as DefinedTerm array. Wire into templates/page.product-transparency.liquid after the main content block.
Answer-First Product Spec Block
dds-product-spec-block.liquid rendering a "Core Specifications" card. Read from product.metafields.specs.material, specs.weight, specs.fit, etc., with section-setting fallbacks. Render as semantic <dl>/<dt>/<dd> for AI extraction. Visible eyebrow tag "Answer First". Dark palette. Add section setting for product handle if used as a static block. Wire into 3 product templates.
Product Passport JSON-LD Section
dds-product-passport.liquid. Emit a single <script type="application/ld+json"> with @type: Product, full DPP-aligned fields: material, weight (QuantitativeValue), manufacturer (Printful), brand (DDS), and additionalProperty[] array for each cert. Schema settings: brand, manufacturer, supplier_default, cert toggles (cert_gots, cert_grs, cert_ocs, cert_oeko, cert_peta, cert_fairtrade), weight_oz, weight_gsm, oeko_cert_no, oeko_testing_institute, country_of_print. Wire into 3 product templates.
SEO Optimizer Product (the SEO Magnet V2 product layer)
seo-optimizer-product.liquid. Emit OG product meta (og:type=product with price + currency), Twitter product card, Organization JSON-LD, WebSite JSON-LD, BreadcrumbList (Home → Collection → Product), FAQPage with 6 question/answer slots, Speakable on product title + spec block, visible Quick Answer card + FAQ accordion (template-scoped CSS). Schema name: "SEO Optimizer (Product)" (must be ≤ 25 chars). Wire into 3 product templates as section id dds_seo_optimizer_product.
SEO Optimizers for Homepage, Article, Search
•
seo-optimizer-homepage.liquid — OG/Twitter meta + WebSite + SearchAction JSON-LD + Speakable hint. Default to og:type=website.•
seo-optimizer-article.liquid — og:type=article + article:author/published_time/modified_time/tag + scoped BlogPosting JSON-LD + Breadcrumb. Visible Quick Answer card.•
seo-optimizer-search.liquid — <meta name="robots" content="noindex,follow"> + canonical to base /search (no query string) + visible H1 + WebPage JSON-LD with SearchResultsPage mainEntity.Wire each into respective JSON templates (
index.json, article.json, search.json).
Post-Audit Cleanup (6 items, one push)
Specific actions taken:
collection-hero-grid-v4.liquidline 573 — demote<h1>to<h2>(was producing 2 H1s on collection pages)dds-collection-hero-genesis.liquid— inject defensive JS that demotes any non-canonical H1 to H2 on DOMContentLoaded (defense against Ateliermain-collection-listH1)dds-404.liquid— add<meta name="robots" content="noindex,nofollow">- 3 product templates — remove duplicate orphan
seo_optimizer_product_*instances (keep onlydds_seo_optimizer_product) seo-optimizer-search.liquid— defensive client-side canonical strip (removes any<link rel="canonical">containing/search?…and installs clean base URL)
Dark Theme Conversion (Per-Section)
sections/<name>.liquid from light theme to dark. Hardcode the DDS palette (--ops-bg: #0a1a0f, --ops-text: #f5f0e8, etc.) in the section's <style> block. Add background-color: var(--ops-bg) !important on the .ops-{{ section.id }} wrapper. Add defensive color: var(--ops-text) !important on all text children. Do NOT rely on Atelier emitting CSS for the custom UUID scheme — it does not. Push, then run live audit to verify the section renders dark in browser.
Applying color: var(--ops-text) !important to all label elements broke the size-guide unit toggle in R3.15b: the toggle uses a cream-on-cream pill to indicate the selected state, which became invisible. The fix was a more-specific !important rule for the checked-state label. Lesson: defensive blanket rules need defensive exceptions.
Cert-Bar to Marquee Swap on Collections
templates/collection.organic-t-shirts.json (and other collection templates) from live. Find the cert_bar_* section instance. Disable it (set "disabled": true). The existing dds_trust_bar_v2_* at the top of each collection template already provides the scrolling marquee — no replacement needed, just disable the old cert-bar. Validate symmetry + push.
Audit Cleanup From Antigravity Browser Agent Report
The reframe table that drove this round, paste-ready Python:
# OEKO-TEX brand-level → substrate-only
('OEKO-TEX certified', 'OEKO-TEX Standard 100 substrate'),
# Water/lifespan unverified claims
('95% Less Water', 'Lower water footprint'),
(r'(\d+)% less water', 'lower water footprint'),
('10+ Year Lifespan', 'Built to last for years'),
# Fair Trade attribution
('Fair Trade certified', 'Fair Trade supplier-certified'),
# US-only shipping → worldwide
('Free US Shipping', 'Free Worldwide Shipping'),
(r'(\d+)-(\d+) days U\.S\.A delivery',
r'\1-\2 days US delivery (worldwide 5-22 days)'),
Document-Level Event Delegation (Size Guide + Variant Picker)
This is the round that operationalized Principle 6 across the rest of the project. Every interactive element built after this round uses document-level delegation by default.
Size Guide Unit Toggle Text Contrast
.ops-{id} .ops-sizeguide__toggle-radio:checked + .ops-sizeguide__toggle-label {
color: var(--ops-bg) !important; /* dark text on cream pill */
background: var(--ops-text) !important;
border-color: var(--ops-text) !important;
}
The general lesson: defensive blanket CSS needs scoped exceptions. The validation gate catches structural failures; only live audit catches contrast violations.
Academy Tab-Nav Phantom Gap Fix
CTA Copy Revision Across 11 Templates
Heading:
"Shop Sustainable Fashion with
Total Confidence."
Description:
"🌿 GOTS Organic.
Sustainable fashion only."
Heading:
"See the Fit. Skip the Returns."
Description:
"Virtually try on our
certified-substrate apparel —
GOTS organic cotton,
GRS recycled polyester,
OEKO-TEX Standard 100 — to see
the perfect fit before you buy.
Verifiable certifications.
Marketing-fluff free."
The new copy is substrate-attributed (so it works across cotton + recycled poly + AOP product types) and explicitly anti-fluff. That's the brand voice locked in: verifiable certifications and marketing-fluff free.
Reassign Collection Template via Shopify Admin GraphQL
mutation {
collectionUpdate(input: {
id: "gid://shopify/Collection/<ID>",
templateSuffix: "organic-sweatshirts"
}) {
collection { templateSuffix }
userErrors { field message }
}
}
No theme file changes. No push. Pure metadata swap. The collection now renders via templates/collection.organic-sweatshirts.json and inherits its full section stack including the ItemList schema.
Image Optimization Pass (Antigravity Agent)
Full 5-phase workflow documented in the Image Optimization Playbook section below.
Reusable Prompt Library
Seven prompts that compose into the 18-round upgrade workflow. Each is paste-ready into Antigravity 2.0, Claude Code, or any agent with file-system and Shopify CLI access. The placeholders (in <ANGLE_BRACKETS>) get filled with your project specifics.
Prompt 1 — Build a New Liquid Section
Build sections/<NAME>.liquid following these requirements:
1. Schema name ≤ 25 chars (Shopify validation).
2. NO 'default': '' on text/textarea fields (Shopify rejects).
3. Range fields: default must align to (min + N × step).
4. Number fields use JSON numbers, not strings.
5. Use # comments inside {% liquid %} blocks (NOT comment/endcomment).
6. Render block wrapped in <section id="<PREFIX>-{{ section.id }}"> with the
DDS dark palette CSS tokens hardcoded (don't depend on Atelier scheme emit).
7. Include `color_scheme` schema setting for future use, but don't depend on it.
8. background-color: var(--ops-bg) !important on wrapper (defeats Atelier body).
9. Defensive `color: var(--ops-text) !important` rules on all text children.
10. If section has JS interactivity, use document-level event delegation in
capture phase (NOT IIFE addEventListener which dies on Atelier DOM morph).
11. Schema settings include section_id-scoped CSS variables only.
12. Include a working preset block at end of schema.
13. Validate against the 8-check pre-push gate before declaring done.
Section purpose: <PURPOSE>
Settings needed: <LIST>
HTML structure: <SKETCH>
JSON-LD schemas to emit: <LIST>
Prompt 2 — Bulk Reframe Forbidden Strings
Scan every .liquid and .json in sections/ and templates/.
For each forbidden string in the list below, find/replace with the verified
equivalent. Then re-grep to confirm zero remaining matches.
Validate every JSON file still parses after the swap (strip /*...*/ headers
first if present, then json.loads).
Show me a diff before pushing. Do not push if validation fails on any file.
Forbidden → Reframed:
"OEKO-TEX certified" → "OEKO-TEX Standard 100 substrate"
"Made-to-order in Boston" → "Made-to-order, designed in Boston"
"95% Less Water" → "Lower water footprint"
"Free US Shipping" → "Free Worldwide Shipping"
<...rest of the swap table...>
Bundle all changes into one push.
Prompt 3 — Live Browser Audit a Single Page
Use Claude in Chrome to:
1. Navigate to <URL>?preview_theme_id=<THEME_ID>
2. Wait 6 seconds for full render.
3. Capture a screenshot (save to disk).
4. Run this JS audit and return result as JSON:
(function(){
const all = s => Array.from(document.querySelectorAll(s));
const ldB = all('script[type="application/ld+json"]');
let v = 0, e = 0, types = [];
for (const b of ldB) {
try { const j = JSON.parse(b.textContent); types.push(j['@type']); v++; }
catch (x) { e++; }
}
const meta = n => document.querySelector('meta[' + n + ']')?.getAttribute('content');
return JSON.stringify({
page: location.href,
title: document.title,
h1Count: all('h1').length,
h1Texts: all('h1').map(h => h.textContent.trim().slice(0, 60)),
ogType: meta('property="og:type"'),
twitterCard: meta('name="twitter:card"'),
robotsMeta: meta('name="robots"'),
canonical: document.querySelector('link[rel="canonical"]')?.href,
jsonLdValid: v,
jsonLdErr: e,
jsonLdTypes: types.slice(0, 15)
});
})()
5. Report findings as a scorecard:
- PASS if H1=1, jsonLdErr=0, og type matches expectation
- FAIL otherwise with specific deltas
Pages to audit:
- Homepage: /
- Article: /blogs/news/<slug>
- Search: /search?q=organic
- 404: /this-does-not-exist
- Collections list: /collections
- Collection page: /collections/<slug>
- Product page: /products/<slug>
- Cart (empty): /cart
- Transparency: /pages/<custom-page>
Prompt 4 — Push to Shopify CLI
Push the following files to testing theme:
shopify theme push --store <STORE>.myshopify.com `
--theme <THEME_ID> `
--nodelete `
--only <file1> `
--only <file2> `
...
Verify push completes with no errors. Save the CLI output to push-log.txt.
If push fails on a specific file, abort. Do not push partial.
After push succeeds, immediately run the live browser audit prompt on the
affected pages.
Prompt 5 — Pull Files From Live Theme (When Local Rebuild Is Stale)
Use Shopify Admin GraphQL to pull these files from theme <THEME_ID> into
./rebuild/<original-path>:
query {
theme(id: "gid://shopify/OnlineStoreTheme/<THEME_ID>") {
files(filenames: <LIST>, first: 50) {
nodes {
filename
body { ... on OnlineStoreThemeFileBodyText { content } }
}
}
}
}
If the response exceeds your context limit, the tool will save to disk. Read
the disk file, parse the JSON, and write each file's body to the matching
local path (preserving the original directory structure).
WARNING: this overwrites local edits. Diff local vs pulled before overwriting
if local has unpushed work.
Prompt 6 — Reassign Shopify Collection Template
Use Shopify Admin GraphQL:
mutation {
collectionUpdate(input: {
id: "gid://shopify/Collection/<ID>",
templateSuffix: "<custom-template-suffix-without-collection.-prefix>"
}) {
collection { id handle templateSuffix }
userErrors { field message }
}
}
If userErrors is non-empty, abort and report. Otherwise verify the change
took effect by re-running the audit prompt on the affected collection URL.
Prompt 7 — Sweep All CTA / Trust / Promise Copy
For every template in templates/, inspect every section instance whose
type matches CTA-Block, cta-block, dds-trust-bar, or contains "cta" or
"trust" or "promise" in the type name.
Pull ALL text-bearing settings (heading, subheading, eyebrow_text,
description, button_text, feature_*, benefit_*, manifesto_*).
For each text value:
1. Run against the forbidden-strings whitelist
2. Check brand-voice compliance (no marketing-fluff claims;
supplier-attributed certs; verifiable specifics only)
3. If issue found, propose a brand-compliant rewrite
Output a CSV: file_path, section_id, setting_key, current_value,
proposed_value, reason.
Wait for approval before applying.
The 8-Check Pre-Push Validation Script
The complete validate.py. Save this to your project root. Run before every push. If exit code is non-zero, fix the failure and re-run before pushing.
import json, re, sys
push_set = {
'sections/file1.liquid': 'liquid',
'templates/file2.json': 'json',
# ... fill with your push set
}
forbidden = [
'Made in Boston', 'Boston-made', 'Boston-crafted',
'OEKO-TEX certified', 'Fair Trade certified',
'Fair Trade Manufacturing', 'Toxin Free Dyes',
'10-Year Care Guide', '10+ Year Lifespan',
'Weekly sustainable style tips', 'Zero waste',
'95% Less Water', '% less water',
'Free US Shipping', 'U.S.A delivery', 'U.S.A shipping',
'We hold 5', 'We hold 6', '100% Defect Guarantee',
'Verify Our Certifications', 'exclusively organic',
'Sustainable fashion only',
]
fail = False
# [1] NULL bytes
for f in push_set:
with open(f, 'rb') as fh:
n = fh.read().count(b'\x00')
if n:
print(f'NULL: {f} has {n} bytes ✗')
fail = True
# [2] JSON validity + sections/order symmetry
for f, t in push_set.items():
if t != 'json':
continue
with open(f) as fh:
raw = fh.read()
body = re.sub(r'^/\*.*?\*/\s*', '', raw, flags=re.S)
try:
d = json.loads(body)
if 'sections' in d and 'order' in d:
sym = set(d['sections'].keys()) == set(d['order'])
if not sym:
print(f'SYM: {f} sections != order ✗')
fail = True
except Exception as e:
print(f'JSON: {f} ✗ {e}')
fail = True
# [3] Schema validity + name ≤ 25
for f, t in push_set.items():
if t != 'liquid':
continue
with open(f) as fh:
src = fh.read()
m = re.search(r'{%\s*schema\s*%}(.*?){%\s*endschema\s*%}',
src, flags=re.S)
if not m:
continue
try:
sch = json.loads(m.group(1))
n = sch.get('name', '')
if len(n) > 25:
print(f'SCHEMA NAME: {f} "{n}" {len(n)} chars > 25 ✗')
fail = True
except Exception as e:
print(f'SCHEMA PARSE: {f} ✗ {e}')
fail = True
# [4] No "default": "" on text/textarea
for f, t in push_set.items():
if t != 'liquid':
continue
with open(f) as fh:
src = fh.read()
m = re.search(r'{%\s*schema\s*%}(.*?){%\s*endschema\s*%}',
src, flags=re.S)
if not m:
continue
sch = json.loads(m.group(1))
for s in sch.get('settings', []):
if s.get('type') in ('text', 'textarea') and s.get('default') == '':
print(f'EMPTY DEFAULT: {f} setting={s.get("id")} ✗')
fail = True
# [5] Range step alignment
for f, t in push_set.items():
if t != 'liquid':
continue
with open(f) as fh:
src = fh.read()
m = re.search(r'{%\s*schema\s*%}(.*?){%\s*endschema\s*%}',
src, flags=re.S)
if not m:
continue
sch = json.loads(m.group(1))
for s in sch.get('settings', []):
if s.get('type') == 'range':
mn = s.get('min', 0)
st = s.get('step', 1)
df = s.get('default')
if df is not None and (df - mn) % st != 0:
print(f'RANGE STEP: {f} {s.get("id")} '
f'default={df} not aligned to step={st} ✗')
fail = True
# [6] Liquid block balance
pairs = [('if', 'endif'), ('for', 'endfor'), ('case', 'endcase'),
('comment', 'endcomment'), ('schema', 'endschema'),
('style', 'endstyle'), ('javascript', 'endjavascript'),
('paginate', 'endpaginate'), ('form', 'endform'),
('capture', 'endcapture'), ('unless', 'endunless')]
for f, t in push_set.items():
if t != 'liquid':
continue
with open(f) as fh:
src = fh.read()
for o, c in pairs:
oc = len(re.findall(r'{%-?\s*' + o + r'\b', src))
cc = len(re.findall(r'{%-?\s*' + c + r'\b', src))
if oc != cc:
print(f'BALANCE: {f} {o}={oc}/{c}={cc} ✗')
fail = True
# [7] No comment/endcomment inside {% liquid %} blocks
for f, t in push_set.items():
if t != 'liquid':
continue
with open(f) as fh:
src = fh.read()
blocks = re.findall(r'{%-?\s*liquid\b(.*?)-?%}',
src, flags=re.S)
for i, b in enumerate(blocks):
if re.search(r'\bcomment\b|\bendcomment\b', b):
print(f'LIQUID BLOCK COMMENT: {f} block {i} contains '
f'comment/endcomment ✗')
fail = True
# [8] Forbidden strings
for f in push_set:
with open(f) as fh:
src = fh.read()
hits = [s for s in forbidden if s in src]
if hits:
print(f'FORBIDDEN: {f} contains {hits} ✗')
fail = True
print('\nRESULT:', 'FAIL — DO NOT PUSH' if fail else 'PASS — safe to push')
sys.exit(1 if fail else 0)
How to Run It
python3 validate.py && shopify theme push --store ... --only ...
The && means the push only runs if validation exits zero. This is the gate. Without it, you are pushing on hope.
Live Browser Audit Workflow
After every push, this 4-step verification runs against the testing theme. Use Chrome MCP (or any browser-control agent — the pattern works the same).
Step 1 — Resize Chrome to Standard Viewport
mcp__Claude_in_Chrome__resize_window {
width: 1440,
height: 900
}
Step 2 — Navigate + Screenshot + JS Audit (Batched)
mcp__Claude_in_Chrome__browser_batch [
{ name: "navigate",
input: { url: "<URL>?preview_theme_id=<ID>" }},
{ name: "computer",
input: { action: "wait", duration: 6 }},
{ name: "computer",
input: { action: "screenshot", save_to_disk: true }},
{ name: "javascript_tool",
input: { text: "<audit JS — same as Prompt 3 above>" }}
]
Step 3 — Scroll-Screenshot for Long Pages
mcp__Claude_in_Chrome__browser_batch [
{ name: "computer",
input: { action: "scroll", scroll_direction: "down",
scroll_amount: 10 }},
{ name: "computer",
input: { action: "wait", duration: 2 }},
{ name: "computer",
input: { action: "screenshot", save_to_disk: true }},
// repeat 2-3 times for full-page coverage
]
Step 4 — Interactive Checks (Only If Behavior Changed)
// Test variant picker
const sel = document.querySelector('select[name="options[Color]"]');
sel.value = sel.options[1].value;
sel.dispatchEvent(new Event('change', { bubbles: true }));
// Then verify image src changed, label updated, URL updated
// Test size guide
document.querySelector('[data-sizeguide-trigger]').click();
// Then verify .ops-sizeguide__content has .is-open class
If your JS returns a string that includes ?width=1200 or similar query-string-looking content, the response gets blocked with [BLOCKED: Cookie/query string data]. Workaround: Strip query strings in the JS output. Replace s with s.replace(/[?&].*$/, '') or output a hash/fingerprint instead of raw URLs.
Architecture Reference
The final shape of the rebuilt DDS theme: which sections exist, what each does, where each is wired, and the standardized section orders for product, collection, and homepage templates. Use this as a transplant manifest when porting the pattern to another store.
Section Files (Final Set)
| File | Purpose | Wired Into |
|---|---|---|
dds-cert-verification-table.liquid | Outbound cert registry links + supply-chain disclosure + WebPage JSON-LD | transparency page template |
dds-product-spec-block.liquid | Answer-First Core Specifications card | 7 product templates |
dds-product-passport.liquid | DPP-aligned Product JSON-LD | 7 product templates |
seo-optimizer-product.liquid | Product OG/Twitter + FAQPage + Breadcrumb + Speakable + visible Quick Answer | 7 product templates |
seo-optimizer-homepage.liquid | Homepage OG/Twitter + WebSite + SearchAction | index.json |
seo-optimizer-article.liquid | Article OG + BlogPosting + Breadcrumb + Quick Answer | article.json |
seo-optimizer-search.liquid | Search noindex + base canonical + H1 + SearchResultsPage JSON-LD | search.json |
seo-optimizer-collection.liquid | Collection OG + CollectionPage + ItemList + OfferCatalog | all collection templates |
dds-blog-articles.liquid | Blog filter + search + 3-col grid with manual pagination | blog.json |
seo-optimizer-blog-news.liquid | Blog SEO Magnet | blog.json |
dds-collection-hero-genesis.liquid | Per-collection branded hero + dual CTAs + defensive H1 demote JS | all collection templates |
dds-collection-editorial.liquid | Dark-themed editorial section under product grid | all collection templates |
dds-collection-cross-strip.liquid | Cross-collection cert strip | all collection templates |
dds-collection-quick-answer.liquid | Answer-First card for collections | all collection templates |
dds-collection-trust-strip.liquid | Trust signals strip | all collection templates |
organic-product-v2.liquid | Main product card (overrides Atelier's product-information) | 7 product templates |
Standardized Product Template Section Order
0. dds-trust-bar-v2 (scrolling marquee, dark)
1. organic-product-v2 (main product card — image, variants, price, Add to Cart)
2. main (product-information) [disabled]
3. dds-product-spec-block (Answer-First Core Specifications)
4. dds-product-passport (Product JSON-LD)
5. CTA-Block (Try Before You Buy / brand CTA)
6. product-recommendations
7. seo_optimizer_product_* [disabled legacy]
8. dds_seo_optimizer_product (R3.5 SEO Magnet, enabled)
Standardized Collection Template Section Order
0. dds-collection-hero-genesis (per-collection branded hero)
1. dds-trust-bar-v2 (scrolling marquee)
2. dds-collection-quick-answer (Answer-First card)
3. dds-collection-trust-strip (trust signals)
4. collection-hero-grid-v4 (product grid with filters)
5. dds-collection-editorial (editorial section)
6. dds-collection-cross-strip (cross-cert strip)
7. CTA-Block (brand CTA)
8. main (main-collection) [disabled]
9. seo-optimizer-collection (CollectionPage + ItemList + OfferCatalog)
Standardized Homepage Section Order
0. dds-hero-genesis (genesis hero with ClothingStore + FAQPage + Speakable)
1. dds-trust-bar-v2 (marquee)
2. dds-showcase-v3 (featured collections)
3. judge-account-cta (account CTA)
4. cta-block (brand CTA)
5. dds-organic-quiz-v2 (interactive quiz)
6. dds-signal-beacon (Boston local time + cert count)
7. dds-platform-links (social/external)
8. ai-homepage-seo-manager-v5 (rich JSON-LD for AI overviews)
9. dds_seo_home (R3.6 OG/Twitter + WebSite + SearchAction)
Custom Color Scheme (config/settings_data.json)
"scheme-09beadd5-6088-4295-adf3-071103c616a9": {
"settings": {
"background": "#0a1a0f",
"foreground_heading": "#fafaf7",
"foreground": "#f5f0e8",
"primary": "#c9a84c",
"primary_hover": "#e8c84a",
"border": "#1a5c45",
"input_background": "#0d2818",
"input_text_color": "#f5f0e8",
"input_border_color": "#1a5c45",
"primary_button_background": "#c9a84c",
"primary_button_text": "#0a1a0f",
"primary_button_border": "#c9a84c",
"primary_button_hover_background": "#e8c84a",
"primary_button_hover_text": "#0a1a0f"
}
}
The scheme exists in settings_data but does not produce --color-background: 10 26 15 at runtime. Always hardcode in section CSS instead of depending on --color-* vars. See Principle 5 for the full diagnosis.
Shopify Liquid + JSON Gotchas
The traps that the 8-check pre-push validation exists to catch. Every gotcha below caused at least one failed push during the DDS rebuild before the gate was built. The gate exists because of these.
Liquid: Don't Use comment / endcomment Inside {% liquid %} Shorthand Blocks
The Shopify parser breaks with "comment tag was never closed" when comment tags appear inside the liquid shorthand block. Use the # single-line comment syntax instead.
{%- liquid
comment
Why this section exists
endcomment
assign x = 1
-%}
{%- liquid
# Why this section exists
assign x = 1
-%}
Top-level {%- comment -%} outside any liquid block works fine. The bug is specifically the liquid shorthand parser failing on nested comment tags.
Schema JSON: No "default": "" on Text/Textarea Fields
Shopify rejects empty string defaults on text and textarea settings. Omit the default entirely instead.
{
"type": "text",
"id": "foo",
"default": ""
}
{
"type": "text",
"id": "foo"
}
Range default Must Align to step
The default value must satisfy (default - min) % step == 0. Otherwise Shopify rejects the schema.
{
"type": "range",
"id": "x",
"min": 0,
"max": 100,
"step": 5,
"default": 33
}
{
"type": "range",
"id": "x",
"min": 0,
"max": 100,
"step": 5,
"default": 30
}
Schema name Max 25 Characters
Anything longer fails push. Names like "SEO Optimizer (Product)" are exactly 23 chars and pass; "SEO Optimizer for Products" is 26 chars and fails. The validation script catches this before the CLI ever sees the file.
Template JSONs: sections Keys Must Match order Array Exactly
Orphan sections (present in sections object but not in order array) cause "Section id 'X' must exist in order" rejection. Same for the reverse — IDs in order that don't exist in sections.
# Always validate after edits:
assert set(d['sections'].keys()) == set(d['order'])
Number Fields Must Be JSON Numbers, Not Strings
"free_shipping_threshold": "0"
"free_shipping_threshold": 0
Template JSONs May Have /* … */ Comment Headers — Strip Before Parsing
import re
with open(p) as f:
raw = f.read()
# Strip the header before json.loads
body = re.sub(r'^/\*.*?\*/\s*', '', raw, flags=re.S)
d = json.loads(body)
# Preserve header when writing back
out = (re.match(r'^(/\*.*?\*/\s*)', raw, flags=re.S).group(1) +
json.dumps(d, indent=2) + '\n')
File Writing: The 17KB Silent Truncation Risk
Use bash cat > file << 'EOF' heredoc for large files. Always verify with ls -la after writing. The truncation is silent — no error, no warning — and the truncated file passes JSON.parse on its head but renders broken in the browser. This burned an entire afternoon during the DDS rebuild.
NULL Bytes: Grep After Every File Write
Some tools (Edit, certain bash redirects) can append trailing NULL bytes. NULL bytes break JSON parsers and Shopify validation. Always grep for \x00 after any file write.
with open(f, 'rb') as fh:
if b'\x00' in fh.read():
# Strip them, then rewrite the file
...
Image Optimization Playbook
The WebP conversion that achieved 57.65% storage savings and −4.14s LCP on product pages followed this 5-phase pattern. The full pass took roughly 90 minutes of Antigravity agent time on a theme with ~150 images.
Phase 1 — Inventory
Pull the live theme. Scan every templates/*.json + sections/*.liquid + snippets/*.liquid + assets/* for image references:
- Collection record images (Shopify Admin GraphQL:
collections { image { url } }) - Template JSON keys matching
image,background_image,hero_image,og_image,featured_image,*_image - Section Liquid:
{{ section.settings.X | image_url }}patterns - Section Liquid: hardcoded
https://cdn.shopify.com/...URLs assets/*.{png,jpg,jpeg}
Save inventory to CSV: file_path, setting_key, image_url, source_format, current_bytes, dimensions.
Phase 2 — Convert (sharp encoder)
import sharp from 'sharp';
// Apparel + lifestyle photography
await sharp(input)
.webp({ quality: 82, smartSubsample: true, effort: 6 })
.toFile(output);
// Cert badges / icons / logos (small, transparent PNG)
await sharp(input)
.webp({ lossless: true, alphaQuality: 100, effort: 6 })
.toFile(output);
// OG share images (1200×630)
await sharp(input)
.webp({ quality: 85, effort: 6 })
.toFile(output);
// Keep original PNG/JPG as fallback (LinkedIn/Twitter inconsistent on WebP cards)
Skip if:
- Source < 10 KB
- Source is SVG
- Source already WebP
- Conversion increased file size (re-check after encode)
Phase 3 — Upload
Use Shopify Admin GraphQL stagedUploadsCreate → upload to S3 staging URL → fileCreate mutation:
mutation StagedUploadsCreate($input: [StagedUploadInput!]!) {
stagedUploadsCreate(input: $input) {
stagedTargets {
url
resourceUrl
parameters { name value }
}
userErrors { field message }
}
}
mutation FileCreate($files: [FileCreateInput!]!) {
fileCreate(files: $files) {
files {
... on MediaImage { id image { url } }
}
userErrors { field message }
}
}
Use original filename with .webp extension so shopify://shop_images/foo.webp URLs in template JSON keep working without editing every reference. Build url-map.json mapping old URL → new WebP URL.
Phase 4 — Replace URLs
For each entry in url-map.json:
- Scan modified templates — replace exact old URL with new WebP URL
- Scan modified sections — same for any hardcoded CDN URLs (NOT for
image_urlfiltered references — those auto-negotiate) - For collection record images:
collectionUpdatemutation with new image URL
Validate after each batch:
- Every old URL appears ZERO times in modified files
- Every JSON still parses
Phase 5 — Push + Lighthouse
shopify theme push --store ... --theme ... --nodelete --only <changed files>
# Then run Lighthouse 3 times per page and average (avoid dev-mode variance)
npx lighthouse "<URL>" --output json --output-path before/after.json
Expected Savings
| Image Type | Quality Setting | Expected Savings |
|---|---|---|
| Apparel JPGs (hero/lifestyle) | WebP q82 | 60-75% |
| Cert badges/logos (PNG with alpha) | WebP lossless | 30-50% |
| OG share images | WebP q85 (keep original fallback) | 20-40% |
| Tiny mono-color PNGs | (often skipped) | 0% — sometimes WebP is larger |
Total theme image weight should drop 55-70%. The DDS pass landed at 57.65%, square in the expected range.
Shopify's CDN auto-negotiates WebP via Accept: image/webp header on {{ image | image_url }} calls. Pages using that filter were ALREADY serving WebP to modern browsers — your storage savings are real, but transfer-byte savings to end users are zero.
Pages with hardcoded <img src="https://cdn.shopify.com/..."> (direct URLs, no image_url filter) DID transfer the raw original. Those are where you see LCP improvements after the swap. Product hero images are the most common case — which is exactly why the DDS −4.14s win landed on product pages specifically.
Troubleshooting
Five specific failure modes encountered during the DDS rebuild, with the exact diagnosis sequence that resolved each one. If you hit a symptom matching one of these, walk the checklist before debugging from scratch.
Q: My dark theme section renders white. What's wrong?
Walk this checklist in order:
- Does
.ops-{{ section.id }}(or your wrapper class) havebackground-color: var(--ops-bg) !important? - Does the section root
<div>actually have your wrapper class? (Inspect in DevTools — Atelier sometimes nests sections differently than expected.) - Are your
--ops-*CSS variables HARDCODED in the section's CSS, notrgb(var(--color-background))? (Atelier doesn't emit CSS for custom UUID schemes.) - Does
section.settings.text_colorpersist an old#1f2937value from a previous schema iteration? Hardcode your text color in CSS, don't read from settings.
If all 4 pass and it's still white, run the live DOM inspection JS from R3.10g to see what's actually computed.
Q: My event listeners don't fire. What's wrong?
- Is your IIFE running? (Check by adding
window.__myFlag = true;early in the IIFE and inspect in console.) - Is
containernull? (Atelier wraps sections inshopify-section-{id}div — yourdocument.querySelector('.ops-' + sectionId)might miss.) - Are the elements you're binding to inside
containerat IIFE-run time? (Watch for sub-section rendering.) - Most likely: Atelier morphs the DOM during navigation. Switch to document-level event delegation in capture phase. See Principle 6.
Q: Push succeeds but my changes don't appear. What's wrong?
- Shopify CDN serves CSS with a few-minute cache. Hard-refresh (Ctrl+Shift+R) or visit with
?cache_bust=<random>query string. - You may be viewing the LIVE theme, not the testing theme. Always include
?preview_theme_id=<ID>in the URL. - Check the section file was actually saved (
ls -la <file>) and the push command output included your file.
Q: JSON-LD parse errors on /collections list page
Live test via JSON.parse on every <script type="application/ld+json">. Most common cause: a Liquid variable inserts blank/undefined in a position that needs a number or quoted string:
"numberOfItems": {{ product_count }}
"numberOfItems": {{ product_count | default: 0 }}
Q: My OG image URL fallback isn't appearing in twitter:image
- Verify the section setting
og_image_urlis populated in the template JSON. - Verify the section Liquid emits
<meta name="twitter:image">in an{%- elsif og_image_url != blank -%}branch. - Re-test by pasting the URL into LinkedIn's Post Inspector — that forces a fresh fetch.
Closing Notes for the Next Agent
If you're a fresh Claude Code or Antigravity instance reading this, here's how to use this playbook on a new Shopify store:
- Read the entire foundation section first. Brand Bible + forbidden strings + operating principles before any code.
- Adapt the Verified Facts to the new brand. Most projects will have their own supplier chain, certs, materials. The PATTERN is the same; the specific facts change.
- Don't skip the live audit between rounds. Static validation is necessary but not sufficient. The Atelier morphing bug doesn't show in static checks.
- One concern per push. Resist the temptation to batch unrelated fixes. If the push fails, you'll thank yourself.
- The 8-check pre-push gate is non-negotiable. Build the validation script first, then build the features it gates.
- Save all reports to
rebuild/docs/. Future agents (or human auditors) need the trail. - When you don't know if something works, ASK THE USER TO TEST. Push to a TESTING theme, never directly to live. Let the user verify before publishing.
The pattern that took 18 rounds on DDS Boston should take fewer rounds on the next project because:
- Brand Bible is upfront, no mid-stream reframing
- Forbidden strings list is comprehensive
- Atelier scheme/morphing/cascade gotchas are documented
- The 8-check gate catches all classes of failure before push
Build, validate, push, audit, repeat. Then write the next masterclass.
Frequently Asked Questions
The 18 most common questions about applying this playbook on a new Shopify store. The FAQPage JSON-LD schema at the top of the page surfaces these in AI overviews and rich results.
What is the DDS Homepage Upgrade Masterclass?
A reproducible AI-driven playbook for upgrading a Shopify Online Store 2.0 theme end-to-end. It captures the exact pattern used to upgrade DDS Boston from a generic Atelier installation to a brand-compliant, dark-themed, SEO-optimized store: foundation Brand Bible, 8 operating principles, 18 build rounds, reusable prompt library, 8-check pre-push validation, image optimization workflow, and the Atelier-specific gotchas that burned hours of debugging.
Why do you need a Brand Bible before writing any code?
Without verified facts and a forbidden-strings whitelist, AI generates plausible but non-compliant copy. The Brand Bible includes three things: a Verified Facts Whitelist (every claim the brand can make), a Forbidden Strings Whitelist (every claim that triggers an abort), and Brand Color Tokens plus Typography. Skipping this step guarantees compliance violations downstream.
Why one concern per round?
Mixing concerns (dark theme plus new SEO plus content reframe plus image swap) makes pre-push validation impossible to interpret. If a 12-file push breaks, you cannot tell whether the dark theme tokens or the JSON-LD parse fix or the cert reframe caused the failure. Push one concern, verify, then push the next concern.
What are the 8 pre-push validation checks?
(1) NULL byte sweep. (2) JSON validity plus sections-equals-order symmetry. (3) Schema validity plus name 25-char limit. (4) No default empty-string on text fields. (5) Range step alignment. (6) Liquid block balance. (7) No comment/endcomment inside liquid blocks. (8) Forbidden strings sweep. If any check fails, the push is aborted and the failure is fixed before retry.
Why does Atelier not auto-emit CSS for custom color schemes?
Atelier emits CSS for built-in schemes (scheme-1 through scheme-6) but does NOT emit CSS for custom UUID schemes. Even if you add color_scheme to a section schema and default it to a UUID scheme, color-background falls back to white at runtime. The fix is to hardcode the palette in the section's CSS, not rely on scheme emission. This burned 5 hours of debugging on the DDS project.
Why do event listeners stop firing after Atelier navigation?
Stock section IIFE patterns add event listeners on page load. Atelier morphs sections during navigation, so listeners attached to morphed elements no longer fire. The solution is document-level event delegation in capture phase: document.addEventListener('click', ...) with closest data-trigger and capture phase true. Listeners on document survive any morphing.
How much can WebP conversion improve theme performance?
The DDS conversion saved 71.54 MB of image weight (57.65% reduction) and improved product page LCP by 4.14 seconds. Apparel and lifestyle JPGs at WebP quality 82 yield 60-75% savings. Cert badges and logos at lossless WebP yield 30-50%. OG share images at quality 85 yield 20-40%. Pages using image_url filter were already serving WebP via Shopify CDN auto-negotiation, so storage savings are real but transfer savings to modern browsers were zero on those paths.
Can you reassign a Shopify collection to a different template without rebuilding?
Yes. Use one GraphQL mutation against Shopify Admin API: collectionUpdate with id and templateSuffix. The collection now renders via templates/collection.<suffix>.json. No theme push needed, no file changes. Verify in DevTools that the new template sections appear.
How do you stop AI from generating fake certifications?
Build the Forbidden Strings Whitelist before the AI sees the codebase. Any push that contains a banned phrase (We hold 5 certifications, OEKO-TEX certified at the brand level, Made in Boston when the brand is POD-based) aborts at the pre-push gate. The whitelist is comprehensive and gets updated every time a new violation is caught in audit.
What is the live browser audit workflow?
Four steps after every push: (1) Resize Chrome to standard viewport. (2) Navigate, screenshot, run a JS audit returning H1 count, JSON-LD valid count, OG meta, canonical, robots meta, and forbidden-string sweep on rendered text. (3) Scroll-screenshot for long pages. (4) Interactive checks for any user-facing interaction that changed (variant picker, size guide accordion). Static validation passes do not equal live behavior works.
Can a fresh Claude Code instance use this playbook?
Yes. The playbook is intentionally self-contained: all prompts, validation criteria, gotchas, and architectural decisions are documented inline. A fresh agent reads the foundation first (Brand Bible, forbidden strings, operating principles), adapts the Verified Facts to the new brand, then proceeds through the 18 rounds. The PATTERN is reusable, the specific facts vary per project.
How do you keep settings from breaking when schemas change?
If a section instance has a stored text_color value like dark gray from an old light-theme default, and you later change the schema default to cream, the old value persists on existing instances. The Liquid default filter only fires when the setting is blank, not when it is set to the wrong value. Solution: hardcode brand colors in CSS, do not rely on section.settings.text_color. Use CSS variables defined in the section's style block instead.
What is the Shopify schema name limit?
25 characters maximum. Anything longer fails push. This is one of the most common gotchas when migrating section schemas. The 8-check pre-push validation script catches this before the file ever hits the CLI.
Why can templates JSON fail with "Section id must exist in order"?
Templates have two parallel keys: sections (object of section instances) and order (array of section IDs). Both must reference the same keys exactly. Orphan sections in sections but not in order cause rejection. Same for IDs in order that don't exist in sections. Always validate after edits with assert set(sections.keys()) == set(order).
What is the silent multi-phase build pattern?
For any task touching more than 4 files or more than 1,000 lines of code, the agent works silently in a scratch workspace, validates each phase, and only presents the final assembled output via present_files. Intermediate code is never shown. Report format during silent build: "Phase N complete" with a one-line description. This prevents context bloat and lets the user focus on the final deliverable.
Should you push directly to your live theme?
Never. Always push to a testing theme first, run the live browser audit, verify the affected pages render correctly, and only then publish via Shopify admin. Even production-grade tools can ship breaking behavior on edge cases. The testing theme is the buffer between a bad push and a customer-visible regression.
How do you handle NULL bytes in pushed files?
Some tools (Edit, certain bash redirects) can append trailing NULL bytes that break JSON parsers and Shopify validation. The pre-push validation script reads each file in binary mode and counts NULL bytes. Any non-zero count aborts the push. The fix is to rewrite the file using a clean writer (Python open in text mode, or bash heredoc with EOF guard).
What is the reframe pattern for OEKO-TEX claims?
DDS holds NO direct cert licenses. OEKO-TEX is held at the substrate level by Stanley/Stella, econscious, and Printful. The forbidden phrase is "OEKO-TEX certified" at the brand level. The compliant reframe is "OEKO-TEX Standard 100 substrate", which attributes the certification accurately to the material rather than the brand. Same pattern for Fair Trade: forbidden "Fair Trade certified", compliant "Fair Trade supplier-certified".
