← back to Designer Wallcoverings

pending-approval/done/collection-hero-duptitle-mobileheader.DONE.md

123 lines

# Collection-page presentation fixes — hero band (F1), duplicate title (F2), mobile header gap (F3)

**Owner:** vp-dw-commerce · **Drafted:** 2026-06-23 · **Status:** built + staged on DEV + verified on real DOM; LIVE push + 1 customizer setting + per-collection images AWAITING STEVE
**Live theme (role==main, verified):** **142250278963 "Steves Version 5.0"**
**Dev theme staged on:** **143947038771 "★ 5.0 Grid+Infinite FIX (approve)"** — the SAME dev theme carrying the companion grid.js + oversized-image fixes, so one Preview shows ALL collection-page fixes together.
**Gate reason:** customer-facing writes to the published theme + a customizer setting + setting `collection.image` on collections.

Pairs with `pending-approval/grid-js-init-abort-and-dev-build.md` (the two JS console errors below are owned by THAT memo) and `pending-approval/collection-grid-toolbar-and-image-perf.md` (oversized grid images). Do all three; they don't conflict — all staged on 143947038771.

---

## What was wrong (root-caused on the real live DOM, both viewports)

| # | Defect | Root cause (measured) |
|---|---|---|
| **F1** | Ugly grey "pill" hero | The title "Products" renders as `h1.boost-sd__header-title` with `background: rgba(0,0,0,.55)`, radius 6px, white text, padding — a treatment **designed to overlay a hero IMAGE for legibility**. But **NO `collection.image` is set** (`hasFeaturedImage:false`), so the dark pill floats on a flat grey block = "horrible." Violates Steve's HARD rule that every collection MUST have a background hero image. |
| **F2** | Title printed twice | Both Boost's `h1.boost-sd__header-title` AND the OOTS theme leftover `h1.page-title` render the same "Products" stacked. |
| **F3** | Mobile header dead-gap | `theme.css` mobile media query sets `.header-branding{padding:36px 0}` → 118px-tall logo block with a big empty gap; `CART(0)` tools bar (42px) marooned at the very top. Measured mobile header height 160px, branding block 118px. |

JS console errors on the page (`Cannot read properties of null (reading 'addEventListener')` and `... (reading 'dataset')`) are the **StaticHeader / ProductListItem init-abort** — already root-caused + fixed in `grid-js-init-abort-and-dev-build.md`, staged on this same dev theme. NOT re-fixed here (would duplicate). The CSP/403/410 console lines are third-party (shop.app embed + a pixel), not theme-fixable, out of scope.

---

## Decisions — DTD panel (Claude + Codex + Qwen), 2026-06-23

- **F1 = B** (2/1; Claude dissented, accepts): always-on hero with a branded fallback. Use `collection.image` first → else a customizer default hero → else a polished imageless state. So a shopper NEVER sees the grey dead-state, and per-collection images take over the moment they're set. Claude's dissent (a single shared default makes collections look identical) is absorbed: B uses `collection.image` first, the default is only a floor.
- **F2 = A** (3/3): hide the theme `page-title`, keep Boost's (Boost is the live grid + header renderer, carries the image + description).
- **F3 = A** (3/3): minimal surgical mobile CSS, reversible.

---

## What is STAGED on dev 143947038771 (reversible, dev-only — NOT live)

| Asset | Change | Backup |
|---|---|---|
| `assets/custom.css` | appended a single marked block `/* >>> dw-collection-hero-fix START >>> */ … END` — F1 hero band styling, F2 dup-title hide, F3 mobile-header CSS. Idempotent (re-appends cleanly). | `shopify/collection-hero-fix/backups/assets_custom.css.{live,dev}-*.before` |
| `snippets/dw-collection-hero-bg.liquid` | NEW snippet — emits the per-collection hero `background-image` inline (`collection.image` → `settings.collection_default_hero` → none) and sets `body.dw-collection-has-hero` so the title switches between white-on-image pill and clean dark type. | n/a (new file; remove to revert) |
| `sections/collection.liquid` | ONE line added near the top: `{% render 'dw-collection-hero-bg' %}` (needs `collection` scope). | `…/backups/sections_collection.liquid.{live,dev}-*.before` |
| `config/settings_schema.json` | added a "Collection hero" group with an `image_picker` `collection_default_hero` (the branded fallback Steve picks in the customizer). | `…/backups/settings_schema.json.dev-*.before` |

Source of truth for all four: `shopify/collection-hero-fix/` (`dw-collection-hero.css`, `snippets_dw-collection-hero-bg.liquid`, `patched/sections_collection.liquid`, `patched/settings_schema.json`).

---

## Verified (real DOM, both viewports) — `shopify/collection-hero-fix/screenshots/`

The staged CSS + the snippet's exact inline output were applied to the **real live `/collections/all` DOM** at 1440px and 390px (iPhone-13), measured + screenshotted:

| State | BEFORE | AFTER |
|---|---|---|
| **F1 no-image** (the real current state of most collections) | dark `rgba(0,0,0,.55)` pill on grey dead-block | title bg → **transparent**, color → **#1a1a1a clean centered uppercase** on a soft band — `dev-{desktop,mobile}-AFTER-noimage.png` |
| **F1 with-image** (once `collection.image`/default is set) | n/a | photo fills the hero band, title returns to **white-on-dark pill** (`rgba(0,0,0,.42)`, white) — `dev-desktop-AFTER-withimage.png` |
| **F2 dup title** | theme `page-title` visible:true | theme `page-title` **visible:false** (Boost's kept) |
| **F3 mobile gap** | branding block **118px**, header **160px** | branding **74px**, header **124px** — cart + logo tightened up |

**Honest scope of proof:** CSS application is deterministic — these are the real collection-page DOM with the exact staged CSS/inline-style applied. The staged assets are confirmed present on dev 143947038771 via the Admin API. The one thing not done headless: load the served dev theme via `?preview_theme_id=` (anonymous requests are served the LIVE theme). The pixel-perfect confirm on the *served* dev theme is the authenticated **Preview** button — Steve's step below.

---

## STEVE — verify on dev, then approve

### Step 1 — verify the dev Preview (authenticated)
Shopify admin → Online Store → Themes → **"★ 5.0 Grid+Infinite FIX (approve)" (143947038771)** → **Preview** → open `/collections/all` and any real collection (e.g. `/collections/acoustical`). Expect:
- "Products" is a single clean centered title (no grey pill, no duplicate) on collections with no image; and a real photo hero with white pill on any collection where you set an image.
- Mobile: cart + logo tight at top, no dead gap.

### Step 2 — pick the branded default hero (gated; customizer)
In that same theme: **Customize → Theme settings → "Collection hero" → Default collection hero image** → upload a wide DW brand image (≈2000×600). This is the floor every imageless collection falls back to. *(Optional — without it, imageless collections show the polished clean-type band, which already looks good.)*

### Step 3 — set per-collection images (gated; the HARD rule, do over time)
For each important collection: **Collections → [collection] → Image** → set a representative hero. These always win over the default. This is the actual fulfillment of the `dw_every_collection_needs_bg_hero_image` rule; the theme fix above guarantees nothing looks broken while you work through them.

### Step 4 — approve the LIVE push (I run on your go)
Applies the SAME four staged changes to LIVE **142250278963** (backups already captured), with re-verify after. Commands (theme token via `/secrets` `SHOPIFY_THEME_TOKEN`, never hardcoded; CSS block idempotent + backed up first):

```bash
TOK=$(grep -m1 '^SHOPIFY_THEME_TOKEN=' ~/Projects/secrets-manager/.env | cut -d= -f2- | tr -d '"' | tr -d "'")
LIVE=142250278963
cd ~/Projects/Designer-Wallcoverings
python3 - "$TOK" "$LIVE" <<'PY'
import sys,json,urllib.request,urllib.parse,re,time
tok,LIVE=sys.argv[1],sys.argv[2]; STORE='designer-laboratory-sandbox.myshopify.com'
def get(k):
    q=urllib.parse.urlencode({'asset[key]':k})
    return json.load(urllib.request.urlopen(urllib.request.Request(f"https://{STORE}/admin/api/2024-10/themes/{LIVE}/assets.json?{q}",headers={'X-Shopify-Access-Token':tok})))['asset']['value']
def put(k,v):
    body=json.dumps({'asset':{'key':k,'value':v}}).encode()
    s=json.load(urllib.request.urlopen(urllib.request.Request(f"https://{STORE}/admin/api/2024-10/themes/{LIVE}/assets.json",data=body,method='PUT',headers={'X-Shopify-Access-Token':tok,'Content-Type':'application/json'})))['asset']['size']
    print('PUT',k,s,'bytes'); time.sleep(2)
B='shopify/collection-hero-fix'
# backup live custom.css + collection.liquid + settings_schema first
for k in ['assets/custom.css','sections/collection.liquid','config/settings_schema.json']:
    open(f"{B}/backups/{k.replace('/','_')}.live-{LIVE}.pushbak",'w').write(get(k))
# 1) snippet (new)
put('snippets/dw-collection-hero-bg.liquid', open(f'{B}/snippets_dw-collection-hero-bg.liquid').read())
# 2) collection.liquid (render call) — verify live still matches the backup we patched from
put('sections/collection.liquid', open(f'{B}/patched/sections_collection.liquid').read())
# 3) settings_schema (adds the picker; merge live's current to avoid clobbering other edits)
live_ss=json.loads(get('config/settings_schema.json'))
if not any(isinstance(g,dict) and any(s.get('id')=='collection_default_hero' for s in g.get('settings',[])) for g in live_ss):
    live_ss.insert(1,{"name":"Collection hero","settings":[
      {"type":"paragraph","content":"Fallback hero image shown on collection pages that do not have their own Collection image set. Per-collection images always take priority."},
      {"type":"image_picker","id":"collection_default_hero","label":"Default collection hero image","info":"Used only when a collection has no image of its own. Recommended 2000x600."}]})
    put('config/settings_schema.json', json.dumps(live_ss,indent=2,ensure_ascii=False))
else: print('settings_schema already has collection_default_hero, skipped')
# 4) append marked CSS block to live custom.css (idempotent)
S='/* >>> dw-collection-hero-fix START >>> */'; E='/* <<< dw-collection-hero-fix END <<< */'
base=re.sub(re.escape(S)+r'.*?'+re.escape(E),'',get('assets/custom.css'),flags=re.S).rstrip()+'\n'
put('assets/custom.css', base+'\n'+S+'\n'+open(f'{B}/dw-collection-hero.css').read()+'\n'+E+'\n')
print('LIVE push complete')
PY
```

**Rollback:** re-PUT the `*.live-142250278963.pushbak` backups, or strip the marked CSS block + delete the snippet + revert the one render line.

### Checkboxes
- [ ] Step 1 verified on the dev Preview (single clean title; photo-hero w/ pill when image set; mobile gap gone)
- [ ] Step 2 default hero picked (optional)
- [ ] Step 3 per-collection images being set (HARD rule)
- [ ] **Approve LIVE push of the 4 staged changes to 142250278963** (commands above)

**$ cost:** $0 (local Playwright + read-only public pages + read/write theme API to the UNPUBLISHED dev theme only). One DTD panel: local Qwen (free) + Codex **$0.0038**. Total ≈ **$0.004**.