Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
880768d
Build Glow Verve storefront on the Skeleton theme
claude Oct 3, 2026
2ccfbbd
Hide description "How to Use" when Directions metafield exists
claude Oct 3, 2026
d9022a4
Add bag drawer, predictive search, and sticky mobile add-to-bag
claude Oct 3, 2026
e3bfff1
Merge pull request #1 from aqsm/claude/amazing-rubin-aw8qga
aqsm Oct 3, 2026
227b9b3
Replace preview {% block %} tag with standard Liquid
claude Oct 3, 2026
35c3077
Merge pull request #2 from aqsm/claude/amazing-rubin-aw8qga
aqsm Oct 3, 2026
df8c7b4
Add collection filters, card hover image, routine badges, gallery dots
claude Oct 3, 2026
8cbde7f
Merge pull request #3 from aqsm/claude/amazing-rubin-aw8qga
aqsm Oct 3, 2026
a00bf1a
Add skin quiz, interactive routine builder, and personalization
claude Oct 3, 2026
47619f3
Merge pull request #4 from aqsm/claude/amazing-rubin-aw8qga
aqsm Oct 3, 2026
3a44305
Turn the skin quiz into The Skin Lab
claude Oct 3, 2026
6a3ea93
Redesign product cards
claude Oct 3, 2026
22d92fc
Merge pull request #5 from aqsm/claude/amazing-rubin-aw8qga
aqsm Oct 3, 2026
dfe3171
Replace routine builder with a colour-panel product showcase on the h…
claude Oct 3, 2026
0e92f1c
Add product-showcase to the AGENTS.md theme map
claude Oct 3, 2026
f9fb22b
Merge pull request #6 from aqsm/claude/amazing-rubin-aw8qga
aqsm Oct 3, 2026
b1066e6
Remove the "Your routine" / Retake the quiz section from the home page
claude Oct 3, 2026
3d52dfb
Merge pull request #7 from aqsm/claude/amazing-rubin-aw8qga
aqsm Oct 3, 2026
5316d9f
Rebuild the home page with theme editor sections
claude Oct 3, 2026
c53a1ae
Make product image backgrounds white
claude Oct 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 108 additions & 124 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
# Skeleton Theme Agent Guide
# Glow Verve Theme Agent Guide

A minimal Shopify theme that defines page structure directly in Liquid. This
file lists the repository conventions that aren't obvious from an individual
file. The code is the source of truth.

## Non-negotiables when editing this theme

- **No sections:** no `sections/` folder, no `{% section %}`/`{% sections %}`
tags, no JSON templates, no schema `presets`.
- **Standard Liquid only:** the store's theme parser rejects any file that
uses an unreleased tag, and a rejected `layout/theme.liquid` makes the theme
unpublishable ("missing required file"). Do not use the preview
`{% block %}` or `{% partial %}` tags. Local Theme Check may accept tags the
store does not, so passing Theme Check is not proof a file will upload.
- **Sections on the home page only:** `templates/index.json` composes the
home page from `sections/*.liquid` so the merchant can reorder, hide, add
and edit them in the theme editor. Every other page stays a direct
`templates/*.liquid` template: don't add JSON templates, section groups or
`{% section %}`/`{% sections %}` tags elsewhere.
- **No Liquid-embedded assets:** no `{% stylesheet %}`, no `{% javascript %}`.
All CSS and JavaScript live in `assets/`.
- **Direct Liquid templates:** templates render page content from blocks,
snippets, and inline markup. Don't introduce a section for markup that is
used by only one page.
- **Template-owned containers:** each `templates/*.liquid` file is the
composition root and wraps its page content in one or more `container`
blocks — one per vertical slice. `layout/theme.liquid` wraps only the
`header` and `footer` blocks in their own `container` blocks and renders
`content_for_layout` in a plain `<main>`; `layout/password.liquid` renders
`content_for_layout` in a plain `<main>`, and its template owns the
container. The exception is `gift_card.liquid` (`{% layout none %}`): it
manages its own document structure.
- **Direct Liquid templates:** templates render page content from snippets
and inline markup.
- **Template-owned containers:** each `templates/*.liquid` file wraps its page
content in one or more `<section class="block-container">` elements, one per
vertical slice (add tone or spacing classes such as `tone-surface` or
`block-container--flush`). `layout/theme.liquid` wraps only the header and
footer in their own `<div class="block-container">` and renders
`content_for_layout` in a plain `<main>`. Exceptions with
`{% layout none %}`: `gift_card.liquid` manages its own document, and
`cart.drawer.liquid` / `search.predictive.liquid` are fragments that
`assets/theme.js` fetches with `?view=drawer` / `?view=predictive`.
- **Whitespace matters:** include whitespace between an HTML tag name and a
following Liquid delimiter (`<li {% ... %}`, not `<li{% ... %}`).
- **Translated UI only:** every user-facing string uses a literal
Expand All @@ -31,127 +39,103 @@ file. The code is the source of truth.
## Page structure

```
layout/theme.liquid → {% block 'container' %} (header) + <main> content_for_layout + {% block 'container' %} (footer)
layout/theme.liquid → announcement-bar + .block-container (header) + <main> content_for_layout + .block-container (footer) + cart-drawer
layout/password.liquid → <main> content_for_layout
templates/*.liquid → {% block 'container' %} → blocks / snippets / inline HTML
templates/*.liquid → <section class="block-container"> → snippets / inline HTML
templates/index.json → sections/*.liquid → <section class="block-container"> → snippets / inline HTML
```

Neither layout wraps `content_for_layout` in a `container` block; each renders
it in a plain `<main>`. In `theme.liquid` the `header` and `footer` blocks each
get their own `container` block. Every template is the composition root and
wraps its page content in one or more `container` blocks — a template may hold
any number of containers, one per vertical slice.

## The block tag

```liquid
{% block 'name', named_parameter: value %}
Body content
{% endblock %}
```

The tag works like `{% render %}`, but renders `blocks/name.liquid`. Every
named parameter is available as a plain variable inside the block. If its name
matches a setting declared in the block's schema, it also sets
`block.settings.<id>`. A parameter with no matching schema setting is only a
variable.

For example, the container schema declares `alignment`, but not `tag`:

```liquid
{% block 'container', alignment: 'center', tag: 'div' %}
{{ page.content }}
{% endblock %}
```

Inside the container, both `alignment` and `block.settings.alignment` return
`center`; `tag` returns `div` and does not create `block.settings.tag`. Continue
to use `block.settings.<id>` for schema-backed controls in block implementations.
`class` is an ordinary parameter with no special platform behavior.

`{% doc %}` documents parameters; it does not declare, validate, or bind them.
A parameter documented only in LiquidDoc is read as a plain variable and does
not become a schema setting. Use `{% schema %}` for merchant-editable controls.
Inline literal arrays are supported in `{% block %}` arguments, as shown by
`tips` in `templates/index.liquid`; `{% render %}` and `{% partial %}` do not
accept inline literal arrays.

The content between `{% block %}` and `{% endblock %}` is available inside the
block as `{{ content }}`. Always include the closing `{% endblock %}` tag,
even when the call has no body content. The body is rendered in the caller's
scope; it cannot read the callee's settings or local assignments. Prefer body
content for display-only text and markup instead of adding `title`, `body`, or
`heading` parameters. Add parameters when the block needs data or must change
how it renders.

Executable `{% block %}` tags are allowed only in `layout/` and `templates/`.
Keep child calls in the caller-owned body, never in block or snippet
implementations. LiquidDoc examples may show block calls, but must be authored
in a layout or template when used.

## The partial tag

```liquid
{% partial 'name' %}...{% endpartial %}
```

Partials name inline regions of server-rendered HTML. JavaScript can request a
region by name and replace the matching region in the DOM. The name in the
Liquid template and the name in JavaScript must match.

The `liquid-tips` block is the theme's canonical partial-refresh example: its
tip sentence lives in a `{% partial 'liquid-tip' %}` region, and
`assets/liquid-tips.js` calls `partials.refresh("liquid-tip")` to swap in a
fresh server-rendered tip. Import `partials` from
`@shopify/partial-rendering`. Use `refresh()` to fetch and apply regions from
the current page URL, or `fetch()` followed by `apply()` when you need control
over the request URL, method, body, or when the update appears. Fetch related
regions together so one response keeps them synchronized. Build request URLs
from the current page URL or Liquid `routes.*` to preserve locale and market
routing.

`apply()` preserves focus, text selection, form values, and scroll position.
It also preserves input, textarea, and select values when returned markup
changes them: explicitly update server-adjusted controls after applying the
partial (for example, a cart quantity corrected by inventory validation).
Cancel stale requests with an `AbortSignal`, use `aria-busy` while loading,
announce meaningful results in a live region, and restore transient DOM state
such as open disclosures. Read URL state from `window.location.search` so
shared links and browser navigation produce the same result.

The `{% partial %}` tag renders on the storefront only when
`shop.features.agentic_editor_enabled?` is on and the page is served by
StorefrontRenderer; otherwise the storefront raises `Unknown tag 'partial'`.

## Blocks

Every block must:

- Start with a `{% doc %}` header with typed params.
- Include a `{% schema %}` tag without `presets`.
- Document each named parameter that the block reads, such as `tag` or `class`.
- Document `content` in LiquidDoc and indicate whether it is required or
optional: `@param {string} content` or `@param {string} [content]`. For
self-contained blocks, describe that callers must leave the body empty.
- Render `{{ content }}` where caller-supplied body content belongs.
Self-contained blocks may omit the outlet and use an empty caller body.
- Keep executable child block calls in layouts or templates.
- Keep `{{ block.shopify_attributes }}` on the root element so the theme editor
can identify the block.
## Merchant settings

Without sections or theme blocks, every merchant-editable control is a theme
setting in `config/settings_schema.json` (Brand, Typography, Layout, Colors,
Announcement bar, Header, Footer). Snippets read them from the
global `settings` object. Add new controls there rather than hardcoding
content.

## Home page sections

- Each section wraps its markup in `<section class="block-container">` (tone
sections offer a `tone` select that adds `tone-surface` / `tone-inverse`)
and ends with a `{% schema %}` that has a `presets` entry so it appears
under "Add section". Schema labels are `t:sections.<name>.*` keys in
`locales/en.default.schema.json`.
- Sections start with a `{% comment %}` header (Theme Check only allows
`{% doc %}` in snippets and blocks).
- Merchant text comes from section settings and blocks; output it with
`| escape`. Fixed UI copy (quiz questions, pairing guide, buttons) still
uses `{{ 'key' | t }}`. Section-only CSS (`skin-lab.css`, `showcase.css`)
is loaded by the section that needs it.
- Scripts initialise sections on load and again on the theme editor's
`shopify:section:load` event, so a re-rendered section keeps working.

## Snippets

- Start every snippet with a `{% doc %}` header documenting each parameter it
reads, plus an `@example`.
- `{% render %}` gives a snippet its own scope: pass everything it needs
(`product`, headings, limits) as parameters. Global objects such as
`settings`, `routes`, `cart`, and `collections` are available directly.
- Translate strings before passing them in (`assign heading = 'key' | t`,
then `heading: heading`).

Skeleton keeps `.theme-check.yml` as a pristine
`extends: theme-check:recommended` with **zero overrides**. Fix Theme Check
errors in the Liquid instead of adding configuration exceptions.

Current blocks: `container`, `hello-world`, `header`, `footer`,
`liquid-tips`.
## Glow Verve brand conventions

- Colours are theme settings (`background`, `foreground`, `surface`, `accent`,
`product_tile`) exposed as `--color-*` tokens in `snippets/css-variables.liquid`.
Never hardcode brand colours in CSS; the palette is changing with the new
packaging. `config/settings_data.json` ships two theme styles: "Brand
guidelines" (cream `#F7EBCD`, tan `#DFAB52`, black) and "Packaging 2026"
(yellow `#F4E604`).
- Logos come from `settings.logo`, `settings.logo_mark` and
`settings.logo_stacked` (images in Content > Files). Never alter them.
- Headings use `--font-heading--family` (Alifira when its file URL is set,
otherwise the heading font picker); body copy uses Montserrat.
- Storefront JavaScript lives in `assets/theme.js` as progressive enhancement:
the bag drawer, predictive search, and sticky mobile add-to-bag all fall
back to plain links and form posts without it. Fetch server-rendered HTML
(alternate `view` templates) rather than formatting prices in JavaScript.
- Product pages read optional `custom.*` metafields: `subtitle`,
`key_ingredient`, `when_to_use`, `format`, `directions`, `ingredients`,
`caution`. `Ingredient_*` product tags are the fallback for key ingredients.
Product cards derive AM/PM routine badges from `when_to_use` by matching
the whole words "AM" and "PM". Each card is tinted with its Skin Lab
element colour (first `custom.concerns` value; the colour map lives in
`snippets/product-card.liquid` and must match the orbs in
`sections/skin-lab.liquid`). The card title link is stretched over the card;
interactive controls inside it need `z-index: 2`.
- The home page quiz is "The Skin Lab" (`assets/skin-lab.css`, loaded only by
`sections/skin-lab.liquid`): answers are element orbs orbiting a flask that
fills with each answer's colour, modelled on the mazenonline magnetic
ingredients lab. Orb colours are fixed design tokens in the template, not
brand settings. Tap/click pours an orb in immediately; keyboard users move
with arrow keys and confirm with Next. Honour `prefers-reduced-motion`.
- Interactive features (skin quiz, "add to my routine" card toggles, "Your
match" badges, recently viewed) live in `assets/routine.js`. It reads the
JSON catalogue from `snippets/product-catalog-json.liquid`, which takes its
routine data from the `custom.concerns`, `custom.layer_order` and
`custom.conflicts_with` metafields. Personal state stays in the visitor's
localStorage only (`glowverve:quiz`, `glowverve:routine`, `glowverve:recent`).
Sections it drives are server-rendered with `hidden` and revealed by the
script; add new translated strings to the catalogue's `strings` object.
- To check that Liquid will upload before merging, zip the theme folders,
upload the zip with `stagedUploadsCreate`, and create a `DEVELOPMENT`
theme with `themeCreate`; any file missing from the new theme was rejected.

## Theme map

```
blocks/ container, hello-world, header, footer, liquid-tips
templates/ *.liquid page structure (no JSON templates)
layout/ theme.liquid document shell: header/footer container blocks + <main>
snippets/ internal utilities (css-variables, image, meta-tags)
templates/ *.liquid page structure; index.json (home page) is the only JSON template
sections/ home page only: hero, concerns, product-showcase, featured-collection,
skin-lab, brand-story, regimen, recently-viewed, promises
layout/ theme.liquid document shell, password.liquid
snippets/ announcement-bar, header, footer, cart-drawer, cart-drawer-content,
collection-filters, product-grid, product-card, product-showcase, product-catalog-json,
price, image, meta-tags, css-variables
assets/ CSS, JavaScript, and other static assets
config/ settings_schema.json (all merchant controls), settings_data.json (theme styles)
```
Loading
Loading