@fluid-app/fluid-cli-theme-dev 0.1.20 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -2
- package/.turbo/turbo-typecheck.log +1 -1
- package/package.json +5 -5
- package/skills/themes-review/SKILL.md +707 -0
- package/skills/themes-review/references/blocks-vs-sections.md +131 -0
- package/skills/themes-review/references/css-js-hygiene.md +153 -0
- package/skills/themes-review/references/dead-code.md +161 -0
- package/skills/themes-review/references/dynamism.md +88 -0
- package/skills/themes-review/references/editor-attributes.md +160 -0
- package/skills/themes-review/references/examples.md +111 -0
- package/skills/themes-review/references/fairshare-attributes.md +185 -0
- package/skills/themes-review/references/global-settings.md +221 -0
- package/skills/themes-review/references/liquid-correctness.md +176 -0
- package/skills/themes-review/references/navigation.md +88 -0
- package/skills/themes-review/references/performance.md +85 -0
- package/skills/themes-review/references/security-accessibility.md +70 -0
- package/skills/themes-review/references/setting-types.md +118 -0
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: themes-review
|
|
3
|
+
description: |
|
|
4
|
+
Iterative reviewer and fixer for Fluid Liquid themes. Themes live in their own Git repos
|
|
5
|
+
(e.g. base-theme) and sync to the Fluid API via the `fluid theme` CLI. Use when reviewing
|
|
6
|
+
a theme PR, auditing a section/component, or working through a theme to fix bad code and
|
|
7
|
+
schema. Companion to the `themes` skill — that one teaches how to *build*, this one
|
|
8
|
+
teaches how to *review and fix*. Works **one section at a time**: review it, fix it,
|
|
9
|
+
re-run `fluid theme lint --json`, and repeat until that section is clean before moving on,
|
|
10
|
+
pausing once in a while to ask the user whether to continue, skip, or stop. Covers: the
|
|
11
|
+
schema validator, every valid setting `type`, singular vs plural selector misuse (e.g.
|
|
12
|
+
multiple `product` settings → one `product_list`), the blocks-first content model, Liquid
|
|
13
|
+
correctness, performance, security, accessibility, and file-size/DRY thresholds. Never
|
|
14
|
+
push to the live theme API without explicit approval.
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Theme Review & Fix
|
|
18
|
+
|
|
19
|
+
This skill is the reviewer counterpart to `themes`. It is opinionated and adversarial: every check has a fix, and every fix has a severity. The agent **edits theme files in place** to fix them (every change is reversible — the user can review or undo it), but it **never writes to the live theme API without explicit approval**.
|
|
20
|
+
|
|
21
|
+
## How to work — one section at a time
|
|
22
|
+
|
|
23
|
+
The goal is to get the theme healthy **one section at a time** — not to fix the whole theme in a single sweep. Pick the smallest unit (a single section, component, or block file) and stay on it until it's right before touching anything else.
|
|
24
|
+
|
|
25
|
+
For each unit:
|
|
26
|
+
|
|
27
|
+
1. **Read it** and run every relevant check below — structure, schema, blocks-first content model, selector singular→list, Liquid correctness, performance, security, accessibility.
|
|
28
|
+
2. **Fix** the findings, one focused change at a time, highest severity first (`blocker` → `should` → `nit`).
|
|
29
|
+
3. **Re-validate** with `fluid theme lint --json` after each change. Parse the JSON (don't eyeball the text), fix what it flags, and run it again. Keep looping until that file is clean.
|
|
30
|
+
4. **Confirm the unit is right** — lint is clean _and_ the relevant checklist boxes pass — then **move on** to the next unit.
|
|
31
|
+
|
|
32
|
+
### Check in with the user
|
|
33
|
+
|
|
34
|
+
Don't run end to end silently. **Once in a while — after finishing a section, or before starting a large or risky change — pause and ask the user how to proceed:** continue to the next section, skip this one, stop here, or look at what changed. Default to pausing at natural boundaries (a section just finished, you're about to touch many files, or you're about to make a change that isn't a clear-cut validator error) rather than interrupting mid-fix.
|
|
35
|
+
|
|
36
|
+
## Where themes actually live
|
|
37
|
+
|
|
38
|
+
Themes are **separate Git repos**, one repo per theme. The reference starter is `git@github.com:fluid-commerce/base-theme.git`. They are not part of the fluid Rails monorepo and not part of fluid-mono — fluid-mono only ships the _tooling_ (CLI + validator) that operates on them.
|
|
39
|
+
|
|
40
|
+
A typical theme repo looks like this (verified against the `base-theme` starter cloned by `fluid theme init`):
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
my-theme/
|
|
44
|
+
├── assets/ ← CSS, JS, images, video — flat at root
|
|
45
|
+
├── components/{name}/ ← shared partials, at root (no templates/ wrapper)
|
|
46
|
+
│ ├── pagination/index.liquid
|
|
47
|
+
│ ├── cart_template/index.liquid
|
|
48
|
+
│ └── ...
|
|
49
|
+
├── config/
|
|
50
|
+
│ ├── settings_schema.json ← theme-wide settings
|
|
51
|
+
│ └── settings_data.json ← compiled preset values (managed; rarely hand-edited)
|
|
52
|
+
├── home_page/default/index.liquid ← page templates: {type}/{variant}/index.liquid
|
|
53
|
+
├── product/default/index.liquid
|
|
54
|
+
├── cart_page/default/index.liquid
|
|
55
|
+
├── collection/default/index.liquid
|
|
56
|
+
├── collection_page/default/index.liquid
|
|
57
|
+
├── enrollment_pack/default/index.liquid
|
|
58
|
+
├── join_page/default/index.liquid
|
|
59
|
+
├── page/default/index.liquid
|
|
60
|
+
├── library/default/index.liquid
|
|
61
|
+
├── navbar/default/index.liquid
|
|
62
|
+
├── footer/default/index.liquid
|
|
63
|
+
├── medium/default/index.liquid
|
|
64
|
+
├── layouts/
|
|
65
|
+
│ └── theme.liquid ← flat layout files, no wrapping dir
|
|
66
|
+
├── locales/
|
|
67
|
+
│ ├── en.json
|
|
68
|
+
│ ├── de.json
|
|
69
|
+
│ └── ... ← flat one-file-per-locale
|
|
70
|
+
├── sections/{name}/ ← reusable sections, at root
|
|
71
|
+
│ ├── hero_section/index.liquid
|
|
72
|
+
│ ├── cta_banner/index.liquid
|
|
73
|
+
│ └── ...
|
|
74
|
+
├── blocks/{name}/ ← standalone (reusable) block templates, at root
|
|
75
|
+
│ ├── review_card/index.liquid
|
|
76
|
+
│ └── ...
|
|
77
|
+
├── styleguide/index.liquid ← optional dev-only preview
|
|
78
|
+
├── cover.png ← theme thumbnail for the editor picker
|
|
79
|
+
├── global_styles.css ← optional theme-wide CSS at root
|
|
80
|
+
├── .fluid-theme.json ← CLI metadata (themeId, company, checksums)
|
|
81
|
+
├── .fluidignore ← like .gitignore
|
|
82
|
+
└── README.md
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**There is no `templates/` wrapper.** Sections, components, standalone blocks, and page-type folders each sit in their own directory at the theme root — the directory name _is_ the artifact type (`sections/`, `components/`, `blocks/`, `layouts/`, plus page types like `home_page/`, `product/`, `cart_page/`). A hand-rolled `templates/sections/...` or `templates/blocks/...` path is wrong; flatten it to the root.
|
|
86
|
+
|
|
87
|
+
`.fluid-theme.json` is the one piece of CLI state worth reading. It is not human-edited, but it's helpful to look at: it records the remote theme ID, the company subdomain, and a SHA256 checksum per file (the CLI uses these to detect remote drift on push). When a review needs to know which remote theme a repo is wired to, this is the file to check.
|
|
88
|
+
|
|
89
|
+
## Directory & file structure — validate first
|
|
90
|
+
|
|
91
|
+
Before reading any Liquid, walk the repo root. `fluid theme lint --json` only checks schema JSON — it doesn't enforce layout. The reviewer enforces layout.
|
|
92
|
+
|
|
93
|
+
A valid theme is recognized by the presence of an `assets/` or `config/` directory. In practice every published theme has `assets/`, `config/`, `layouts/`, and the page-type and section folders below.
|
|
94
|
+
|
|
95
|
+
### Required at the repo root
|
|
96
|
+
|
|
97
|
+
| Path | Severity if missing | Notes |
|
|
98
|
+
| ----------------------------- | --------------------------------------- | ---------------------------------------------------------------------- | --------------------------------------------- |
|
|
99
|
+
| `config/settings_schema.json` | `blocker` for any new theme | Theme-wide settings. Even an empty `[]` is acceptable; absence is not. |
|
|
100
|
+
| `layouts/theme.liquid` | `blocker` if the theme renders any page | At least one layout must exist for pages to render. |
|
|
101
|
+
| `assets/` | `should` | Almost every theme needs at least images. |
|
|
102
|
+
| `locales/en.json` | `should` | If the theme uses `{{ 'foo' | t }}`translations anywhere, this is`blocker`. |
|
|
103
|
+
| `cover.png` | `nit` | Used by the editor as the theme thumbnail. |
|
|
104
|
+
| `README.md` | `nit` | Document install / deploy. |
|
|
105
|
+
| `.fluidignore` | `nit` | Exclude `node_modules`, `.DS_Store`, etc. |
|
|
106
|
+
|
|
107
|
+
### Canonical paths per artifact
|
|
108
|
+
|
|
109
|
+
Every artifact has a fixed depth and shape. **No deeper nesting is allowed under any of these.**
|
|
110
|
+
|
|
111
|
+
| Artifact | Canonical path | Example | Multiple variants? |
|
|
112
|
+
| ----------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
113
|
+
| Page templates | `{page_type}/{variant}/index.liquid` | `home_page/default/index.liquid`, `library/revised/index.liquid` | **Yes** — `default` is conventional, but a page type can have any number of variant directories. |
|
|
114
|
+
| Sections | `sections/{name}/index.liquid` | `sections/hero_section/index.liquid` | No variants. One directory per section type. |
|
|
115
|
+
| Components | `components/{name}/index.liquid` | `components/button/index.liquid` | No variants. |
|
|
116
|
+
| Standalone blocks | `blocks/{name}/index.liquid` | `blocks/review_card/index.liquid` | No variants. Reusable block templates a section opts into via `@theme` or a named-block reference. |
|
|
117
|
+
| Layouts | `layouts/{name}.liquid` (flat file, no wrapping dir) | `layouts/theme.liquid` | Multiple layouts possible; each is a flat `.liquid` file. |
|
|
118
|
+
| Global config | `config/settings_schema.json`, `config/settings_data.json` (flat) | — | — |
|
|
119
|
+
| Locales | `locales/{lang}.json` (flat) | `locales/en.json`, `locales/de.json` | — |
|
|
120
|
+
| Assets | `assets/{file}` (flat) | `assets/featured.css`, `assets/logo.svg` | No sub-folders. |
|
|
121
|
+
| Root-level CSS | `global_styles.css` at the theme root | — | Optional theme-wide stylesheet. |
|
|
122
|
+
| Theme thumbnail | `cover.png` at the theme root | — | — |
|
|
123
|
+
| Styleguide | `styleguide/index.liquid` | — | Optional dev-only preview. |
|
|
124
|
+
|
|
125
|
+
**Page-type folder names recognized** (from the base-theme + reference theme):
|
|
126
|
+
`home_page`, `product`, `cart_page`, `collection`, `collection_page`, `category`, `category_page`, `enrollment_pack`, `join_page`, `page`, `post`, `post_page`, `library`, `medium`, `navbar`, `footer`, `shop_page`, plus any others the engine adds. Each one holds **one or more variant directories** (`default/`, `revised/`, `holiday/`, etc.), each containing an `index.liquid`.
|
|
127
|
+
|
|
128
|
+
**Blocks come in two shapes — prefer standalone.** Fluid themes are **blocks-first** (see [Blocks vs. sections](references/blocks-vs-sections.md)), and standalone blocks are the default shape.
|
|
129
|
+
|
|
130
|
+
1. **Standalone (theme) blocks** — their own template at `blocks/{name}/index.liquid`, reusable across sections. A section opts in by declaring `@theme` (accept any theme block) or a named-block reference (a `{ "type": "<name>" }` entry with no `name`/`settings`) in its schema's `"blocks"` array; the engine resolves that to the matching `blocks/<name>/index.liquid` template. **This is the preferred shape for almost everything** — reusable, testable on its own, and it keeps the section to a few whole-section settings while each element's settings live on its block.
|
|
131
|
+
2. **Inline blocks** — defined inside a section's `{% schema %}` `"blocks": [...]` array, local to that one section. **Use only when the block is very small and genuinely one-off.** Extracting an inline block into `blocks/` is always a valid, encouraged recommendation.
|
|
132
|
+
|
|
133
|
+
Blocks can also nest (a block declaring its own `"blocks"`), up to the engine's depth limit. What is **not** allowed is nesting block files _under_ a section or page-variant directory (`sections/x/blocks/...`) — standalone blocks live in the top-level `blocks/` directory, parallel to `sections/` and `components/`.
|
|
134
|
+
|
|
135
|
+
### The depth rule
|
|
136
|
+
|
|
137
|
+
**No artifact nests deeper than the canonical path above.** Specifically:
|
|
138
|
+
|
|
139
|
+
- ✗ `sections/main_product/blocks/breadcrumb/index.liquid` (block file nested under a section — extract it to top-level `blocks/breadcrumb/index.liquid` instead)
|
|
140
|
+
- ✗ `components/buttons/primary/index.liquid` (sub-grouped components)
|
|
141
|
+
- ✗ `assets/icons/social/twitter.svg` (assets in sub-folders)
|
|
142
|
+
- ✗ `home_page/default/blocks/hero/index.liquid` (block file under a page variant — move to top-level `blocks/hero/index.liquid`)
|
|
143
|
+
- ✓ `sections/main_product/index.liquid`
|
|
144
|
+
- ✓ `blocks/breadcrumb/index.liquid` (standalone block at the top level)
|
|
145
|
+
- ✓ `components/button_primary/index.liquid` (rename to flatten)
|
|
146
|
+
- ✓ `assets/icon-social-twitter.svg` (rename to flatten)
|
|
147
|
+
|
|
148
|
+
If you feel the urge to nest, rename: `assets/icon-social-twitter.svg`, `components/card_product/index.liquid`, etc.
|
|
149
|
+
|
|
150
|
+
### Layout requirements
|
|
151
|
+
|
|
152
|
+
Every layout file under `layouts/` **must emit both magic drops** for the engine to inject head content and page content:
|
|
153
|
+
|
|
154
|
+
- `{{ content_for_header }}` — placed inside `<head>`. The engine injects meta tags, analytics, editor-mode scripts, asset preloads, and the FairShare runtime initialization here.
|
|
155
|
+
- `{{ content_for_layout }}` — placed where the page content goes (inside `<body>`, typically inside the main container). The selected page template renders into this slot.
|
|
156
|
+
|
|
157
|
+
A layout missing either one is broken. Missing `content_for_header` breaks editor mode, analytics, and head-injected assets. Missing `content_for_layout` renders an empty page.
|
|
158
|
+
|
|
159
|
+
```liquid
|
|
160
|
+
<!doctype html>
|
|
161
|
+
<html lang="{{ request.locale.iso_code }}">
|
|
162
|
+
<head>
|
|
163
|
+
<meta charset="utf-8">
|
|
164
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
165
|
+
<title>{{ page_title }}</title>
|
|
166
|
+
|
|
167
|
+
{{ 'theme.css' | asset_url | stylesheet_tag }}
|
|
168
|
+
|
|
169
|
+
{{ content_for_header }} {# ← required #}
|
|
170
|
+
</head>
|
|
171
|
+
<body>
|
|
172
|
+
{% section 'main_navbar' %}
|
|
173
|
+
|
|
174
|
+
<main>
|
|
175
|
+
{{ content_for_layout }} {# ← required #}
|
|
176
|
+
</main>
|
|
177
|
+
|
|
178
|
+
{% section 'main_footer' %}
|
|
179
|
+
</body>
|
|
180
|
+
</html>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Findings to surface
|
|
184
|
+
|
|
185
|
+
| You see | Severity | Fix |
|
|
186
|
+
| -------------------------------------------------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
187
|
+
| Section file outside `sections/{name}/index.liquid` | `blocker` | Move it. The `{% section 'x' %}` resolver only looks in `sections/`. |
|
|
188
|
+
| Component file with a `{% schema %}` block | `blocker` | Components don't have schemas. Move to `sections/`, or strip the schema. |
|
|
189
|
+
| Section file without `{% schema %}` | `blocker` | A section without a schema is just markup — make it a component (move to `components/{name}/index.liquid`). |
|
|
190
|
+
| Page template at `{type}/index.liquid` (missing variant dir) | `blocker` | Wrap in a variant directory: `{type}/default/index.liquid`. |
|
|
191
|
+
| Asset (`.css`, `.js`, image) outside `assets/` or `global_styles.css` / `cover.png` at root | `blocker` | Move to `assets/` and reference via `\| asset_url`. |
|
|
192
|
+
| Sub-folder inside `assets/` (e.g. `assets/icons/`) | `should` | Flatten: rename file to `assets/icon-{whatever}.svg`. |
|
|
193
|
+
| Sub-folder inside `sections/{name}/` or `components/{name}/` (e.g. `sections/hero/blocks/`) | `blocker` | Sections and components don't nest. A reusable block belongs in the top-level `blocks/{name}/` directory, not under a section. |
|
|
194
|
+
| Co-located `styles.css` / `style.css` next to a section, component, or page template variant | `blocker` for new files, `should` for existing | **Co-located stylesheets are deprecated** — all CSS lives under `assets/`. See [CSS hygiene §1a](references/css-js-hygiene.md). |
|
|
195
|
+
| Hand-rolled `templates/sections/...` or `templates/components/...` paths | `blocker` | The base-theme convention has no `templates/` wrapper. Move to root-level `sections/` / `components/`. |
|
|
196
|
+
| `config/settings_schema.json` missing | `blocker` for new themes | Create it. Start with `[]` if no global settings. |
|
|
197
|
+
| Filename casing mismatch (e.g. `ProductCard/index.liquid`) | `should` | Directory and filenames should be `snake_case`. The engine is case-sensitive on most filesystems. |
|
|
198
|
+
| Two sections with the same directory name in different paths | `blocker` | Section types must be globally unique. |
|
|
199
|
+
| `{% section 'foo' %}` referencing a missing `sections/foo/index.liquid` | `blocker` | Validator catches this — but flag it explicitly with the fix. |
|
|
200
|
+
| Files committed under `assets/` but never referenced | `nit` → `should` | Dead assets bloat downloads. Run the dead-asset grep in [Dead code](references/dead-code.md). |
|
|
201
|
+
|
|
202
|
+
### Quick structural audit
|
|
203
|
+
|
|
204
|
+
From the theme repo root, this gives a one-shot health view:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# Required files
|
|
208
|
+
[ -f config/settings_schema.json ] && echo "OK config/settings_schema.json" || echo "MISSING config/settings_schema.json"
|
|
209
|
+
[ -f layouts/theme.liquid ] && echo "OK layouts/theme.liquid" || echo "MISSING layouts/theme.liquid"
|
|
210
|
+
|
|
211
|
+
# Sections without schemas
|
|
212
|
+
for f in sections/*/index.liquid; do
|
|
213
|
+
grep -q '{% schema %}' "$f" || echo "NO SCHEMA $f"
|
|
214
|
+
done
|
|
215
|
+
|
|
216
|
+
# Components with schemas (anti-pattern)
|
|
217
|
+
for f in components/*/index.liquid; do
|
|
218
|
+
grep -q '{% schema %}' "$f" && echo "HAS SCHEMA $f"
|
|
219
|
+
done
|
|
220
|
+
|
|
221
|
+
# Anything nested deeper than 2 dirs from theme root (excluding hidden dirs)
|
|
222
|
+
find . -mindepth 4 -type f -not -path '*/.*' \
|
|
223
|
+
-not -path './layouts/*' -not -path './assets/*' -not -path './locales/*' -not -path './config/*'
|
|
224
|
+
|
|
225
|
+
# Deprecated co-located stylesheets
|
|
226
|
+
find . -type f \( -name 'styles.css' -o -name 'style.css' \) \
|
|
227
|
+
-not -path './assets/*' -not -path './.git/*'
|
|
228
|
+
|
|
229
|
+
# Hand-rolled `templates/` wrapper (wrong convention)
|
|
230
|
+
[ -d templates ] && echo "WRONG templates/ wrapper found — flatten to root"
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
If any line of output is unexpected, raise it as a finding before reading the diff line-by-line.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## The tooling — `fluid theme` CLI
|
|
238
|
+
|
|
239
|
+
The `fluid theme` command group is how a theme repo syncs with the Fluid API. The reviewer references these commands so the author can reproduce checks locally — it never runs the mutating ones (`push`, `pull`) itself.
|
|
240
|
+
|
|
241
|
+
| Command | What it does | When the reviewer references it |
|
|
242
|
+
| ---------------------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
243
|
+
| `fluid theme init` | Clone the base theme as a new repo | Suggest in comments when a PR is creating a new section from scratch with no structure |
|
|
244
|
+
| `fluid theme dev` | Local dev server on `:9292` with hot-reload, proxied to `{company}.fluid.app` | Suggest when debugging visual issues |
|
|
245
|
+
| `fluid theme push` | Validates the schema, then uploads changed files. **Validation runs automatically unless `--force`.** | Suggest before merge |
|
|
246
|
+
| `fluid theme pull` | Download remote theme to disk | Mention when local is stale |
|
|
247
|
+
| `fluid theme lint --json` | Read-only schema validator. **The agent should run/reference this in every review.** | Always reference: "this is what `fluid theme lint --json` would flag" |
|
|
248
|
+
| `fluid theme navigate` | Opens browser to the dev theme editor | Mention for visual verification |
|
|
249
|
+
|
|
250
|
+
**`fluid theme lint --json` is the official self-check.** Any finding the validator surfaces (see next section) maps directly to a `blocker` severity in your review — it would fail validation.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Operating Rules
|
|
255
|
+
|
|
256
|
+
1. **One section at a time; fix, re-lint, repeat.** Stay on a single section / component / block until it's clean before moving on. See [How to work](#how-to-work--one-section-at-a-time).
|
|
257
|
+
2. **No writes to the live API without approval.** When fixing locally you edit theme files in place (changes are reversible — the user can review or undo any of them), but you **never** run `fluid theme push` or otherwise write to the live theme without explicit approval. When reviewing a PR instead of fixing locally, don't push to the author's branch — leave comments or open a follow-up PR.
|
|
258
|
+
3. **Quote the original line.** Every finding must reference `file:line` and show the offending snippet. No vague "the loop is inefficient".
|
|
259
|
+
4. **Severity is required.** Every finding is tagged `blocker` / `should` / `nit`. See [Severity ladder](#severity-ladder).
|
|
260
|
+
5. **Cite the validator.** If `fluid theme lint --json` would flag it, say so — the dev should be able to reproduce the failure locally.
|
|
261
|
+
6. **Don't restyle.** Pure formatting churn is forbidden — if a file needs both a fix and a tidy, keep them as separate, focused changes.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Severity ladder
|
|
266
|
+
|
|
267
|
+
| Tag | Meaning | Examples |
|
|
268
|
+
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
269
|
+
| `blocker` | The validator would reject it on `fluid theme push`, OR it renders wrong / breaks a page / exposes user data. Must be fixed before merge. | Invalid `type:` value, missing `id`, duplicate `id`, missing `type:` on a block, settings shape is object not array, referenced section not on disk, unescaped user HTML, unclosed `{% if %}` |
|
|
270
|
+
| `should` | Works today but will hurt later: perf cliff, DRY violation, missing default, missing whitespace control in a hot loop. | `asset_url` inside a `for` loop, 3+ identical `{% render %}` snippets, multiple `product` settings playing the same role, missing `default:` on a user-facing setting |
|
|
271
|
+
| `nit` | Cosmetic, style, or low-impact. Leave one comment, do not block. | Comment style, optional `default` on internal-only setting, naming nits |
|
|
272
|
+
|
|
273
|
+
Decision rule: if `fluid theme lint --json` would error on it, it's `blocker`. If it would render wrong but the validator lets it through, it's `blocker`. If it changes nothing visible today, it's `should` or `nit`.
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## The schema validator (`fluid theme lint --json`)
|
|
278
|
+
|
|
279
|
+
`fluid theme lint --json` is the read-only schema validator. It's the same check the editor runs and the same one `fluid theme push` runs before upload, so anything it rejects is a `blocker`. It runs on every `{% schema %}` block and enforces the rules below.
|
|
280
|
+
|
|
281
|
+
### Schema-level rules
|
|
282
|
+
|
|
283
|
+
- JSON must be parseable. Syntax errors point to the line number.
|
|
284
|
+
- No duplicate `"blocks"` keys in the same object.
|
|
285
|
+
|
|
286
|
+
### Settings rules
|
|
287
|
+
|
|
288
|
+
For each entry in `"settings": [...]`:
|
|
289
|
+
|
|
290
|
+
- `id` is required, non-empty, non-whitespace — **except** for `type: "header"` settings, which carry no `id` and use `content:` instead (the validator accepts them without one).
|
|
291
|
+
- `id` must be unique across all settings in the same file (excluding `header` entries, which have no `id`).
|
|
292
|
+
- `type` is required.
|
|
293
|
+
- `type` must be one of the canonical types — see the [full list below](references/setting-types.md).
|
|
294
|
+
|
|
295
|
+
### Blocks rules
|
|
296
|
+
|
|
297
|
+
For each entry in `"blocks": [...]`:
|
|
298
|
+
|
|
299
|
+
- `type` is required.
|
|
300
|
+
- `name` is required _unless_ the block is `@app`, `@theme`, or a named-block reference (type only, no `name`/`settings` — these point at a standalone `blocks/{name}/index.liquid` template).
|
|
301
|
+
- `settings` (when present) must be an array `[]`, not an object `{}`.
|
|
302
|
+
- Each setting inside is recursively validated (same rules as above).
|
|
303
|
+
- Nested `blocks` are recursively validated.
|
|
304
|
+
- Duplicate `type` values within a blocks array produce a warning.
|
|
305
|
+
|
|
306
|
+
### Section references
|
|
307
|
+
|
|
308
|
+
- Every `{% section 'name' %}` in a Liquid template must have a matching section file on disk. Missing sections produce errors.
|
|
309
|
+
|
|
310
|
+
### Template vs section block shape
|
|
311
|
+
|
|
312
|
+
- **Section files** (`sections/{name}/index.liquid`): `blocks` is an **array** `[]`.
|
|
313
|
+
- **Page templates** (`{page_type}/{variant}/index.liquid` — e.g. `home_page/default/index.liquid`, `product/default/index.liquid`): `blocks` is an **object** `{}`.
|
|
314
|
+
|
|
315
|
+
If you see the wrong shape, that's a `blocker`.
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
## Reference catalogs
|
|
321
|
+
|
|
322
|
+
The deep check catalogs live in `references/` and load only when a review or fix
|
|
323
|
+
touches them. Read the matching file when you hit its topic:
|
|
324
|
+
|
|
325
|
+
- **[Setting types](references/setting-types.md)** — the canonical `type:` list the validator enforces. Read when checking any `{% schema %}` setting `type:`.
|
|
326
|
+
- **[Navigation](references/navigation.md)** — `link_list` menu selector vs. hardcoded `<a>` rows.
|
|
327
|
+
- **[Dynamism](references/dynamism.md)** — settings/locales over hardcoded values.
|
|
328
|
+
- **[Global settings](references/global-settings.md)** — typography/color/spacing tokens, dark mode, `config/settings_schema.json` + `theme.liquid` wiring.
|
|
329
|
+
- **[Blocks vs. sections](references/blocks-vs-sections.md)** — when a setting belongs in a block; settings-panel ergonomics.
|
|
330
|
+
- **[Liquid correctness](references/liquid-correctness.md)** — variable scope, balanced tags, whitespace control, typoed ids.
|
|
331
|
+
- **[Editor attributes](references/editor-attributes.md)** — `section.fluid_attributes` / `block.fluid_attributes`.
|
|
332
|
+
- **[FairShare attributes](references/fairshare-attributes.md)** — `data-fluid-*` cart / add-to-cart behavioral attributes + the CDN script.
|
|
333
|
+
- **[Performance](references/performance.md)** — `asset_url` in loops, render hygiene.
|
|
334
|
+
- **[Security & accessibility](references/security-accessibility.md)** — escaping user content, alt text, semantic clickables.
|
|
335
|
+
- **[Dead code](references/dead-code.md)** — unused sections/components/assets, unhandled block types.
|
|
336
|
+
- **[CSS / JS hygiene](references/css-js-hygiene.md)** — co-located stylesheet deprecation, inline `<style>`/`<script>` thresholds, `defer`.
|
|
337
|
+
- **[Worked examples](references/examples.md)** — full section reviews, end to end.
|
|
338
|
+
|
|
339
|
+
Every reference links directly from here (one level deep). "The validator" always means
|
|
340
|
+
`fluid theme lint --json` — run it after each change and parse the JSON output rather than
|
|
341
|
+
eyeballing the text.
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
## Selector heuristic — singular → list
|
|
345
|
+
|
|
346
|
+
This is one of the most common smells.
|
|
347
|
+
|
|
348
|
+
**If a section or block has two or more singular resource pickers playing the _same role_, collapse them to one `*_list` setting.**
|
|
349
|
+
|
|
350
|
+
The test: would a user reasonably want N+1 items in the same role? If yes, it's a list. If the section needs _exactly one_ hero product _and separately_ one upsell product, those are two different roles — keep them singular.
|
|
351
|
+
|
|
352
|
+
### Bad — six product settings playing the same role
|
|
353
|
+
|
|
354
|
+
```json
|
|
355
|
+
{
|
|
356
|
+
"settings": [
|
|
357
|
+
{ "type": "product", "id": "product_1", "label": "Product 1" },
|
|
358
|
+
{ "type": "product", "id": "product_2", "label": "Product 2" },
|
|
359
|
+
{ "type": "product", "id": "product_3", "label": "Product 3" },
|
|
360
|
+
{ "type": "product", "id": "product_4", "label": "Product 4" },
|
|
361
|
+
{ "type": "product", "id": "product_5", "label": "Product 5" },
|
|
362
|
+
{ "type": "product", "id": "product_6", "label": "Product 6" }
|
|
363
|
+
]
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Good — one list
|
|
368
|
+
|
|
369
|
+
```json
|
|
370
|
+
{
|
|
371
|
+
"settings": [
|
|
372
|
+
{
|
|
373
|
+
"type": "product_list",
|
|
374
|
+
"id": "products",
|
|
375
|
+
"label": "Products",
|
|
376
|
+
"limit": 6
|
|
377
|
+
}
|
|
378
|
+
]
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Rendering changes too
|
|
383
|
+
|
|
384
|
+
```liquid
|
|
385
|
+
{%- comment -%} Bad: six lookups, six render calls {%- endcomment -%}
|
|
386
|
+
{%- if section.settings.product_1 != blank -%}{% render 'product_card', product: section.settings.product_1 %}{%- endif -%}
|
|
387
|
+
{%- if section.settings.product_2 != blank -%}{% render 'product_card', product: section.settings.product_2 %}{%- endif -%}
|
|
388
|
+
{%- comment -%} ...four more times... {%- endcomment -%}
|
|
389
|
+
|
|
390
|
+
{%- comment -%} Good: one list, one loop {%- endcomment -%}
|
|
391
|
+
{%- for product in section.settings.products -%}
|
|
392
|
+
{% render 'product_card', product: product %}
|
|
393
|
+
{%- endfor -%}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
### Same rule for every resource
|
|
397
|
+
|
|
398
|
+
| Singular smell | List fix |
|
|
399
|
+
| ------------------------------------------------------- | ------------------------------------------- |
|
|
400
|
+
| 2+ `product` (or `products`) settings, same role | `product_list` |
|
|
401
|
+
| 2+ `collection` settings, same role | `collection_list` |
|
|
402
|
+
| 2+ `category` settings, same role | `category_list` |
|
|
403
|
+
| 2+ `post` settings, same role | `posts_list` |
|
|
404
|
+
| 2+ `enrollment` / `enrollment_pack` settings, same role | `enrollment_list` / `enrollment_packs_list` |
|
|
405
|
+
| 2+ `blog` settings, same role | `blog_list` |
|
|
406
|
+
|
|
407
|
+
The benefit isn't just lines of code — a `*_list` setting lets the merchant **add or remove** items without a code change and drops the fixed per-slot count. It does **not** give per-item drag-and-drop reorder, though — that's a *blocks* feature. If the merchant needs to reorder items, model them as blocks instead.
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## File-size and DRY thresholds — when to extract
|
|
412
|
+
|
|
413
|
+
Sections grow. Past certain thresholds, they stop being readable. Severity for "this section is too big" is `should` (rarely `blocker`), but the fix is mechanical.
|
|
414
|
+
|
|
415
|
+
### Section line counts
|
|
416
|
+
|
|
417
|
+
| Lines | Action |
|
|
418
|
+
| ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
419
|
+
| < 200 | Fine. |
|
|
420
|
+
| 200–400 | Watch for repetition. 3+ near-identical render snippets → extract a component. |
|
|
421
|
+
| 400–700 | **`should`.** Split section into named blocks via `{% schema %}` `"blocks": [...]` and dispatch in a single `{% for block in section.blocks %}` loop. |
|
|
422
|
+
| > 700 | **`should`** (or `blocker` if it also has bugs). Decompose. Prefer multiple smaller sections or component extraction over one giant section. |
|
|
423
|
+
|
|
424
|
+
### Component line counts
|
|
425
|
+
|
|
426
|
+
Components are simpler than sections (no schema), so the threshold is lower.
|
|
427
|
+
|
|
428
|
+
| Lines | Action |
|
|
429
|
+
| ------- | -------------------------------------------------------------------------------------- |
|
|
430
|
+
| < 100 | Fine. |
|
|
431
|
+
| 100–250 | Look for sub-component extraction. |
|
|
432
|
+
| > 250 | **`should`.** A pure presentation partial that's 250+ lines is doing too much — split. |
|
|
433
|
+
|
|
434
|
+
### Duplication — the rule of three for Liquid
|
|
435
|
+
|
|
436
|
+
Liquid `{% render %}` snippets often carry 8+ arguments. The parameter list itself is maintenance burden the moment you copy it.
|
|
437
|
+
|
|
438
|
+
**`should`** when:
|
|
439
|
+
|
|
440
|
+
- The same `{% render 'card', ... %}` with the same arg list appears 3+ times in one file → extract to a smaller wrapper component or a `{% capture %}` block.
|
|
441
|
+
- The same chunk of markup (5+ lines) appears 3+ times across two or more sections → move to a new `components/{name}/index.liquid` and `{% render %}` it.
|
|
442
|
+
- The same Liquid logic block (`{% assign … %}` followed by computation) appears in 3+ sections → move to a component and `{% render %}` it.
|
|
443
|
+
|
|
444
|
+
### Block extraction — when an inline branch becomes a block
|
|
445
|
+
|
|
446
|
+
If a section has three or more `{% if section.settings.show_X %}` branches at the top level, that's a sign each `X` wants to be a `{% schema %}` block. Block-driven sections look like this:
|
|
447
|
+
|
|
448
|
+
```liquid
|
|
449
|
+
{% for block in section.blocks %}
|
|
450
|
+
{% case block.type %}
|
|
451
|
+
{% when 'page_header' %} {%- render 'resource_listing_header' -%}
|
|
452
|
+
{% when 'product_grid' %} {%- render 'product_grid', block: block -%}
|
|
453
|
+
{% when 'cta_banner' %} {%- render 'cta_banner', block: block -%}
|
|
454
|
+
{% endcase %}
|
|
455
|
+
{% endfor %}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
Schema side:
|
|
459
|
+
|
|
460
|
+
```json
|
|
461
|
+
"blocks": [
|
|
462
|
+
{ "type": "page_header", "name": "Page Header", "limit": 1, "settings": [...] },
|
|
463
|
+
{ "type": "product_grid", "name": "Product Grid", "settings": [...] },
|
|
464
|
+
{ "type": "cta_banner", "name": "CTA Banner", "settings": [...] }
|
|
465
|
+
]
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
This gives users drag-and-drop reorderability and lets each block's logic and settings live together.
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
## Review workflow — how to actually output findings
|
|
473
|
+
|
|
474
|
+
### A. Reviewing a GitHub PR (default)
|
|
475
|
+
|
|
476
|
+
Themes live in their own repos. PRs typically arrive via `gh pr view <number>` against `base-theme` or a fork of it.
|
|
477
|
+
|
|
478
|
+
1. Read the PR diff. For each changed `.liquid` or `.json` file, walk through these check sections in order:
|
|
479
|
+
- Validator-level rules (would `fluid theme lint --json` reject it?)
|
|
480
|
+
- Setting types (every `type:` in the canonical list)
|
|
481
|
+
- Selector singular→list
|
|
482
|
+
- File-size / DRY thresholds
|
|
483
|
+
- Liquid correctness
|
|
484
|
+
- Performance
|
|
485
|
+
- Security
|
|
486
|
+
- Accessibility
|
|
487
|
+
- CSS/JS hygiene
|
|
488
|
+
2. Group findings by file. Within each file, sort by line number.
|
|
489
|
+
3. For each finding, prepare a **single inline comment** at the offending line. Format:
|
|
490
|
+
|
|
491
|
+
````
|
|
492
|
+
[blocker] Invalid setting type `text_area`
|
|
493
|
+
|
|
494
|
+
The schema validator only accepts the canonical type list. Running `fluid theme lint --json`
|
|
495
|
+
locally would fail on this. Use `textarea` (one word):
|
|
496
|
+
|
|
497
|
+
```json
|
|
498
|
+
{ "type": "textarea", "id": "description", "label": "Description" }
|
|
499
|
+
```
|
|
500
|
+
````
|
|
501
|
+
|
|
502
|
+
4. After all inline comments, post **one summary review** (e.g. `gh pr review --comment --body ...`) with the rollup:
|
|
503
|
+
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
## Theme review
|
|
507
|
+
|
|
508
|
+
**4 findings** — 1 blocker, 2 should, 1 nit
|
|
509
|
+
|
|
510
|
+
Reproduce locally with `fluid theme lint --json`.
|
|
511
|
+
|
|
512
|
+
| Severity | File | Finding |
|
|
513
|
+
| -------- | ---------------------------------- | --------------------------------------------------- |
|
|
514
|
+
| blocker | sections/featured/index.liquid:42 | Invalid setting type `text_area` |
|
|
515
|
+
| should | sections/featured/index.liquid:118 | Six `product` settings — collapse to `product_list` |
|
|
516
|
+
| should | sections/products/index.liquid:155 | Same `{% render 'product_card', ... %}` repeated 4× |
|
|
517
|
+
| nit | components/button/index.liquid:8 | Missing whitespace control in tight loop |
|
|
518
|
+
|
|
519
|
+
**Suggested fix path:** I can open a follow-up PR with the blocker + the two `should` items. Reply with `open the fix PR` to proceed.
|
|
520
|
+
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### B. Auditing without a PR (local)
|
|
524
|
+
|
|
525
|
+
Same checks, but output goes to the user as a single ranked markdown report. End with the same offer: "I can open a PR with these fixes — say the word."
|
|
526
|
+
|
|
527
|
+
If the user has the CLI installed, also suggest:
|
|
528
|
+
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
For a fast first pass: `fluid theme lint --json` from the theme repo root.
|
|
532
|
+
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### C. Applying fixes — opening a follow-up PR
|
|
536
|
+
|
|
537
|
+
**Only after explicit approval** (`open the fix PR`, `yes do it`, `apply the fixes`):
|
|
538
|
+
|
|
539
|
+
1. Branch from the PR's head (not main): `git checkout -b fix/<short-slug>`
|
|
540
|
+
2. Apply ONE fix at a time, one commit per finding. Commit messages must follow Conventional Commits (this is the fluid-mono style and matches what most theme repos use):
|
|
541
|
+
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
fix(theme): collapse six product settings into product_list
|
|
545
|
+
|
|
546
|
+
Six identical `type: "product"` settings on the featured-products block —
|
|
547
|
+
replaced with one `type: "product_list"` with `limit: 6` and a single
|
|
548
|
+
`{% for product in section.settings.products %}` loop.
|
|
549
|
+
|
|
550
|
+
Severity: should
|
|
551
|
+
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
3. Run `fluid theme lint --json` locally as the final check.
|
|
555
|
+
4. Push the branch.
|
|
556
|
+
5. Open the PR with this body (using `gh pr create`):
|
|
557
|
+
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
## Theme review fixes
|
|
561
|
+
|
|
562
|
+
Follow-up to #<original-pr>. Each commit addresses one finding from the review.
|
|
563
|
+
Validated locally with `fluid theme lint --json`.
|
|
564
|
+
|
|
565
|
+
| Commit | Severity | Finding |
|
|
566
|
+
| ------ | -------- | ------------------------------------------ |
|
|
567
|
+
| abc123 | blocker | Invalid setting type `text_area` |
|
|
568
|
+
| def456 | should | Collapse `product` × 6 into `product_list` |
|
|
569
|
+
| ... | ... | ... |
|
|
570
|
+
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
6. **Never amend or force-push.** Each fix is a reversible commit.
|
|
574
|
+
|
|
575
|
+
### D. What never to do
|
|
576
|
+
|
|
577
|
+
- **Never** push directly to the PR's branch without approval.
|
|
578
|
+
- **Never** run `fluid theme push` against a production theme. The agent's job is to comment, lint, and propose. The CLI's `push` writes to the live API.
|
|
579
|
+
- **Never** bundle unrelated style/cosmetic changes with a behavioral fix.
|
|
580
|
+
- **Never** mark a `nit` as `blocker` to force engagement.
|
|
581
|
+
- **Never** rewrite a section the user didn't ask you to rewrite. Propose first.
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
585
|
+
## Quick reference — the checklist
|
|
586
|
+
|
|
587
|
+
When reviewing any theme file, run through this in order. Anything unchecked is a finding.
|
|
588
|
+
|
|
589
|
+
**Structure**
|
|
590
|
+
|
|
591
|
+
- [ ] Repo has `assets/`, `config/`, and at least one page-type directory (e.g. `home_page/`, `product/`) — at minimum the ones it needs
|
|
592
|
+
- [ ] `config/settings_schema.json` exists
|
|
593
|
+
- [ ] `layouts/theme.liquid` exists
|
|
594
|
+
- [ ] Sections live under `sections/{name}/index.liquid`; components under `components/{name}/index.liquid`; page templates under `{page_type}/{variant}/index.liquid` (where `variant` is `default` or any user-defined name)
|
|
595
|
+
- [ ] No `templates/` wrapper directory at the theme root (sections/components/page-types sit at root)
|
|
596
|
+
- [ ] No files nest deeper than two directories from theme root (no `sections/{name}/blocks/{...}`, no `assets/icons/social/{...}`)
|
|
597
|
+
- [ ] No sections without `{% schema %}`; no components *with* `{% schema %}`
|
|
598
|
+
- [ ] No `.css`/`.js`/image assets outside `assets/`
|
|
599
|
+
- [ ] No co-located `styles.css` / `style.css` next to a template, section, or component (deprecated — all CSS lives in `assets/`)
|
|
600
|
+
- [ ] Section/component directory names are `snake_case` and globally unique
|
|
601
|
+
|
|
602
|
+
**Layout**
|
|
603
|
+
|
|
604
|
+
- [ ] Every file in `layouts/` emits `{{ content_for_header }}` inside `<head>`
|
|
605
|
+
- [ ] Every file in `layouts/` emits `{{ content_for_layout }}` inside `<body>`
|
|
606
|
+
|
|
607
|
+
**Schema correctness**
|
|
608
|
+
|
|
609
|
+
- [ ] Every `{% schema %}` `type:` value is one of the canonical setting types (`fluid theme lint --json` rejects unknown types)
|
|
610
|
+
- [ ] Every setting has a non-empty, unique `id` — *except* `type: "header"` settings, which carry `content:` instead of `id`
|
|
611
|
+
- [ ] Every block has a `type` and (where required) a `name`
|
|
612
|
+
- [ ] `range` has `min`/`max`/`step`; `select`/`radio` has `options`; `*_list` has `limit`
|
|
613
|
+
- [ ] User-facing settings have a `default:`
|
|
614
|
+
- [ ] Every `{% section 'name' %}` has a matching `sections/name/index.liquid` on disk
|
|
615
|
+
- [ ] `fluid theme lint --json` runs clean
|
|
616
|
+
|
|
617
|
+
**Settings dynamism**
|
|
618
|
+
|
|
619
|
+
- [ ] No user-visible literal text in markup (use `text`/`textarea`/`richtext`)
|
|
620
|
+
- [ ] No hardcoded colors in styles (use `color` / `color_background` or a global token)
|
|
621
|
+
- [ ] No hardcoded image URLs (use `image_picker` or upload to `assets/`)
|
|
622
|
+
- [ ] No external CDN URLs for theme-owned assets
|
|
623
|
+
- [ ] No hardcoded `href` to fixed paths the company might want to change (use `url`)
|
|
624
|
+
|
|
625
|
+
**Global settings (config + layout)**
|
|
626
|
+
|
|
627
|
+
- [ ] `config/settings_schema.json` defines `typography`, `color_schema`, and `spacing` groups (at minimum)
|
|
628
|
+
- [ ] `layouts/theme.liquid` reads those settings and emits CSS variables on `:root`
|
|
629
|
+
- [ ] Sections consume `var(--font-*)`, `var(--color-*)`, `var(--space-*)` rather than re-reading `settings.*` directly
|
|
630
|
+
- [ ] No section-level setting duplicates a global token (per-section overrides are the exception, default `unset`)
|
|
631
|
+
- [ ] Schema is grouped by `name:` (not a flat array)
|
|
632
|
+
- [ ] If dark mode is in schema, it's wired in `theme.liquid` (`@media (prefers-color-scheme: dark)` or `[data-theme="dark"]`)
|
|
633
|
+
|
|
634
|
+
**Blocks-first content model**
|
|
635
|
+
|
|
636
|
+
- [ ] Section *content* (headings, text, images, CTAs, cards, etc.) is modeled as **blocks**, not fixed section settings — even when there's only one today
|
|
637
|
+
- [ ] Section settings hold only true whole-section config (width, background, padding, columns, alignment)
|
|
638
|
+
- [ ] Blocks are **standalone** (`blocks/{name}/index.liquid`); inline blocks only for very small, one-off cases
|
|
639
|
+
- [ ] No content element a merchant would add / remove / reorder is locked into a section setting
|
|
640
|
+
|
|
641
|
+
**Settings ergonomics**
|
|
642
|
+
|
|
643
|
+
- [ ] Section settings panel under ~15 fields, OR repeated variations moved to blocks
|
|
644
|
+
- [ ] No 2+ singular resource pickers playing the same role (collapse to `*_list`)
|
|
645
|
+
- [ ] No `X_1` / `X_2` / `X_3` parallel-named settings (use blocks)
|
|
646
|
+
- [ ] No `*s_list` where the canonical `*_list` exists (e.g. `product_list`, not `products_list`)
|
|
647
|
+
|
|
648
|
+
**Dead code**
|
|
649
|
+
|
|
650
|
+
- [ ] No section files with zero `{% section 'name' %}` references (after confirming editor metadata)
|
|
651
|
+
- [ ] No component files with zero `{% render 'name' %}` references
|
|
652
|
+
- [ ] No `{% schema %}` `blocks:` declaring a `type:` the template never handles
|
|
653
|
+
- [ ] No `assets/` files with zero references in any `.liquid` or `.css` (greppable from the theme root)
|
|
654
|
+
|
|
655
|
+
**Liquid + size**
|
|
656
|
+
|
|
657
|
+
- [ ] Section under 400 lines; component under 250 lines
|
|
658
|
+
- [ ] No identical `{% render %}` snippet appearing 3+ times
|
|
659
|
+
- [ ] No `asset_url` inside a `for` loop
|
|
660
|
+
- [ ] All `for`/`if` in markup context use `{%-` / `-%}` whitespace control
|
|
661
|
+
- [ ] `{% if %}` / `{% endif %}` (and `for`/`case`/`capture`) tags balance
|
|
662
|
+
|
|
663
|
+
**Variable scope**
|
|
664
|
+
|
|
665
|
+
- [ ] Every `{{ section.settings.* }}` / `{{ block.settings.* }}` read has a matching `id` declared in the same file's `{% schema %}`
|
|
666
|
+
- [ ] No resource drops (`product`, `collection`, etc.) read outside the templates where they're in scope, unless explicitly passed
|
|
667
|
+
- [ ] `block` and `forloop.*` references only appear inside their respective loops
|
|
668
|
+
- [ ] Components only read variables passed via `{% render %}` arguments
|
|
669
|
+
- [ ] No typos in setting `id`s (silently render empty)
|
|
670
|
+
|
|
671
|
+
**Navigation**
|
|
672
|
+
|
|
673
|
+
- [ ] No hardcoded `<a href="/...">` rows that visually form a navigation — use `link_list`
|
|
674
|
+
- [ ] No `text` + `url` setting pairs used to fake a menu
|
|
675
|
+
|
|
676
|
+
**Assets**
|
|
677
|
+
|
|
678
|
+
- [ ] No inline `<style>` block over 10 lines (extract to `assets/*.css`)
|
|
679
|
+
- [ ] No inline `<script>` block over 5 lines (extract to `assets/*.js`)
|
|
680
|
+
- [ ] JS tags have `defer` (or a justification for not)
|
|
681
|
+
- [ ] No dead assets under `assets/`
|
|
682
|
+
|
|
683
|
+
**Editor attributes (`section.fluid_attributes` / `block.fluid_attributes`)**
|
|
684
|
+
|
|
685
|
+
- [ ] Every section file emits `{{ section.fluid_attributes }}` on its root wrapper element
|
|
686
|
+
- [ ] Every block iterated via `{% for block in section.blocks %}` carries `{{ block.fluid_attributes }}` on its own root element
|
|
687
|
+
- [ ] No typos (`fluid_attribute`, `fluid_attr`, `fluidAttributes`)
|
|
688
|
+
- [ ] No hand-rolled `data-fluid-section-*` attributes — always go through the helper
|
|
689
|
+
- [ ] When passing through a component, attributes are forwarded as `attr: block.fluid_attributes`
|
|
690
|
+
|
|
691
|
+
**FairShare behavioral attributes (`data-fluid-*`)**
|
|
692
|
+
|
|
693
|
+
- [ ] Every attribute uses kebab-case `data-fluid-*` (no camelCase, no underscores, `data-` prefix required)
|
|
694
|
+
- [ ] `data-fluid-cart` is one of `"open"` / `"close"` / `"toggle"` (lowercase)
|
|
695
|
+
- [ ] Modifier attributes (`quantity`, `subscribe`, `subscription-plan-id`, `bundled-items`, `bundle-selections`, `open-cart-after-add`) are always paired with an add-action attribute
|
|
696
|
+
- [ ] `data-fluid-subscription-plan-id` carries a single integer (never comma-separated)
|
|
697
|
+
- [ ] JSON-valued attributes (`bundled-items`, `bundle-selections`) are valid JSON with numeric IDs and quantities
|
|
698
|
+
- [ ] Attributes live on `<button>` or `<a>` (not `<div>` without semantics)
|
|
699
|
+
- [ ] Exactly one `<script id="fluid-cdn-script">` with `data-fluid-shop` set is loaded by the layout
|
|
700
|
+
|
|
701
|
+
**Security & a11y**
|
|
702
|
+
|
|
703
|
+
- [ ] User content escaped (`| escape`) unless it's a HTML-shaped type (`richtext`/`html`/`html_textarea`)
|
|
704
|
+
- [ ] Images have `alt`; below-the-fold images have `loading="lazy"`
|
|
705
|
+
- [ ] Clickable elements use `<button>` (action) or `<a href>` (nav) — never `<div>`
|
|
706
|
+
|
|
707
|
+
If every box checks, the file is healthy. Each unchecked box is a finding — tag it and report it per [Review workflow](#review-workflow--how-to-actually-output-findings).
|