@fluid-app/fluid-cli-theme-dev 0.1.20 → 0.1.21
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 +3 -3
- 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,160 @@
|
|
|
1
|
+
# Editor selector attributes — fluid_attributes
|
|
2
|
+
|
|
3
|
+
> Part of the `themes-review` skill. See [`../SKILL.md`](../SKILL.md) for the review workflow, severity ladder, and validator rules.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- What `fluid_attributes` emits
|
|
8
|
+
- The two drops
|
|
9
|
+
- Correct usage
|
|
10
|
+
- Findings to surface
|
|
11
|
+
- Examples from the reference theme
|
|
12
|
+
- Quick audit
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
Sections and blocks have a Liquid drop called `fluid_attributes` that emits the `data-fluid-section-*` attributes the visual editor uses to find, select, click-to-edit, and drag-to-reorder the element. **Without these attributes on the right element, the editor cannot interact with the content** — click handlers don't bind, inspector panels don't open, drag-and-drop is dead.
|
|
16
|
+
|
|
17
|
+
> **Note:** Despite the `data-fluid-*` prefix, these are _editor_ attributes — not the same as the runtime [FairShare behavioral attributes](#fairshare-behavioral-attributes--data-fluid-) below (which wire up cart, checkout, enrollment behavior). Keep them straight: `section.fluid_attributes` / `block.fluid_attributes` are Liquid drops the _theme engine_ emits in editor mode; FairShare attributes are _hand-written_ HTML attributes that the FairShare runtime SDK reads.
|
|
18
|
+
|
|
19
|
+
These attributes are only populated in editor preview mode; in production rendering, the drops emit an empty string. So a missing call has zero visible effect at runtime — the bug only surfaces in the editor. That's why reviewers must catch it.
|
|
20
|
+
|
|
21
|
+
### What `fluid_attributes` emits
|
|
22
|
+
|
|
23
|
+
In editor mode, `{{ block.fluid_attributes }}` expands to the full set of `data-fluid-*` attributes the editor needs to find and edit the block — block id, parent section type, section id, block type, and a serialized settings payload — rendered as one attribute string on the element:
|
|
24
|
+
|
|
25
|
+
```html
|
|
26
|
+
<div
|
|
27
|
+
data-fluid-section-block-id="abc123"
|
|
28
|
+
data-fluid-parent-section-type="hero"
|
|
29
|
+
data-fluid-section-id="45678"
|
|
30
|
+
data-fluid-section-block-type="heading"
|
|
31
|
+
data-fluid-block-attribute='{"color":"#000000",...}'
|
|
32
|
+
></div>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### The two drops
|
|
36
|
+
|
|
37
|
+
| Drop | Where it's available | Where to emit it |
|
|
38
|
+
| -------------------------------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------- |
|
|
39
|
+
| `{{ section.fluid_attributes }}` | Inside a section file (`sections/{name}/index.liquid`) | On the **root** wrapper element of the section |
|
|
40
|
+
| `{{ block.fluid_attributes }}` | Inside a `{% for block in section.blocks %}` loop, or when a block context is passed to a render | On the **root** wrapper element of _each_ block |
|
|
41
|
+
|
|
42
|
+
**These are the only two.** There is no `component.fluid_attributes`, no dropzone drop, no slot drop, no inline-editing helper. Components are pure partials — they don't have editor presence on their own; the section/block that _renders_ them carries the attributes.
|
|
43
|
+
|
|
44
|
+
### Correct usage
|
|
45
|
+
|
|
46
|
+
```liquid
|
|
47
|
+
{%- comment -%} Section root: section's attributes on the outer wrapper {%- endcomment -%}
|
|
48
|
+
<section class="featured-products" {{ section.fluid_attributes }}>
|
|
49
|
+
{%- for block in section.blocks -%}
|
|
50
|
+
{%- case block.type -%}
|
|
51
|
+
{%- when 'heading' -%}
|
|
52
|
+
<h2 class="featured-products__heading" {{ block.fluid_attributes }}>
|
|
53
|
+
{{ block.settings.text | escape }}
|
|
54
|
+
</h2>
|
|
55
|
+
|
|
56
|
+
{%- when 'product_card' -%}
|
|
57
|
+
<article class="featured-products__card" {{ block.fluid_attributes }}>
|
|
58
|
+
{% render 'product_card', product: block.settings.product %}
|
|
59
|
+
</article>
|
|
60
|
+
|
|
61
|
+
{%- when 'cta' -%}
|
|
62
|
+
{%- comment -%} Passing into a component? Forward the attrs through a named arg. {%- endcomment -%}
|
|
63
|
+
{% render 'button',
|
|
64
|
+
text: block.settings.label,
|
|
65
|
+
href: block.settings.link,
|
|
66
|
+
variant: block.settings.variant,
|
|
67
|
+
attr: block.fluid_attributes %}
|
|
68
|
+
{%- endcase -%}
|
|
69
|
+
{%- endfor -%}
|
|
70
|
+
</section>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
When a block's content is fully delegated to a component, the standard pattern is to forward the attributes as a named argument (`attr: block.fluid_attributes`) and have the component apply them on its root element. See `sections/hero_section/index.liquid` in the reference theme and the `button` component for examples.
|
|
74
|
+
|
|
75
|
+
### Findings to surface
|
|
76
|
+
|
|
77
|
+
| You see | Severity | Fix |
|
|
78
|
+
| ---------------------------------------------------------------------------------------------------------------------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
79
|
+
| Section file has no `{{ section.fluid_attributes }}` anywhere | `blocker` | Add it to the outer wrapper element of the section. Editor cannot select the section without it. |
|
|
80
|
+
| Section uses `{% for block in section.blocks %}` and the block's root element has no `{{ block.fluid_attributes }}` | `blocker` | Each block's outer element must carry its own `block.fluid_attributes`. Without it the editor can't click-to-edit individual blocks. |
|
|
81
|
+
| Typo: `{{ section.fluid_attribute }}` (singular) | `blocker` | The drop name is `fluid_attributes` (plural). The singular form renders empty. |
|
|
82
|
+
| Typo: `{{ block.fluid_attr }}` / `{{ section.fluidAttributes }}` / `{{ section.fluid_attribute_set }}` | `blocker` | Only `fluid_attributes` exists. |
|
|
83
|
+
| Attributes applied on a child element instead of the root | `blocker` | Move to the outermost element of the section/block. The editor walks ancestors looking for the closest match; placing it on a child means the editor picks up _that_ element as the section bounds, not the real one. |
|
|
84
|
+
| Same `{{ section.fluid_attributes }}` applied to multiple elements | `should` | Apply it once — to the root. Duplicates render the same `data-*` attributes twice in the DOM. |
|
|
85
|
+
| Hardcoded `data-fluid-*` attributes (`data-section-id="{{ section.id }}"`) instead of the helper | `blocker` | Replace with `{{ section.fluid_attributes }}` / `{{ block.fluid_attributes }}`. The helper emits a full attribute set; hand-rolling drops most of them. |
|
|
86
|
+
| Block-aware component (renders inside `{% for block %}`) has no `attr` parameter to receive `block.fluid_attributes` | `should` | Add an `attr` argument to the component's signature and apply it on the component's root element. |
|
|
87
|
+
| Conditional render without protecting against `nil`: `<div {{ section.fluid_attributes }}>` where `section` may be undefined | `should` (rarely `blocker`) | Wrap: `{% if section %}{{ section.fluid_attributes }}{% endif %}`. Same for `block`. Only relevant where the value can legitimately be nil. |
|
|
88
|
+
|
|
89
|
+
### Examples from the reference theme
|
|
90
|
+
|
|
91
|
+
Section root with attributes — `sections/main_product/index.liquid:49-51` in the reference fluid theme:
|
|
92
|
+
|
|
93
|
+
```liquid
|
|
94
|
+
<div
|
|
95
|
+
class="MainProductBlock MainProductBlock--breadcrumb MainProductBlock--{{ block.id }}"
|
|
96
|
+
{{ block.fluid_attributes }}
|
|
97
|
+
>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Block forwarding through to a component as `attr:` — `sections/hero_section/index.liquid:140-149` in the reference fluid theme:
|
|
101
|
+
|
|
102
|
+
```liquid
|
|
103
|
+
{%- if block.type == 'button' -%}
|
|
104
|
+
{%- render 'button',
|
|
105
|
+
text: block.settings.text,
|
|
106
|
+
href: block.settings.link,
|
|
107
|
+
variant: block.settings.variant | default: 'primary',
|
|
108
|
+
size: block.settings.size | default: 'md',
|
|
109
|
+
attr: block.fluid_attributes
|
|
110
|
+
-%}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Multiple blocks each carrying their own attrs — `sections/main_category/index.liquid:109,123,158` in the reference fluid theme:
|
|
114
|
+
|
|
115
|
+
```liquid
|
|
116
|
+
<div class="categories-page-header-copy" {% if header_block %}{{ header_block.fluid_attributes }}{% endif %}>
|
|
117
|
+
<h2>{{ page_title }}</h2>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
<div class="categories-page-view-all" {% if view_all_block %}{{ view_all_block.fluid_attributes }}{% endif %}>
|
|
121
|
+
{% render 'button', ... %}
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div
|
|
125
|
+
class="splide carousel"
|
|
126
|
+
data-fluid-resource-slider
|
|
127
|
+
{% if grid_block %}{{ grid_block.fluid_attributes }}{% endif %}
|
|
128
|
+
>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Quick audit
|
|
132
|
+
|
|
133
|
+
From the theme repo root:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Sections missing section.fluid_attributes entirely
|
|
137
|
+
for f in sections/*/index.liquid; do
|
|
138
|
+
grep -q 'section\.fluid_attributes' "$f" || echo "MISSING section.fluid_attributes $f"
|
|
139
|
+
done
|
|
140
|
+
|
|
141
|
+
# Sections that iterate blocks but never emit block.fluid_attributes
|
|
142
|
+
for f in sections/*/index.liquid; do
|
|
143
|
+
iterates=$(grep -q 'for\s\+block\s\+in\s\+section\.blocks' "$f" && echo yes)
|
|
144
|
+
emits=$(grep -q 'block\.fluid_attributes' "$f" && echo yes)
|
|
145
|
+
if [ "$iterates" = "yes" ] && [ "$emits" != "yes" ]; then
|
|
146
|
+
echo "MISSING block.fluid_attributes $f"
|
|
147
|
+
fi
|
|
148
|
+
done
|
|
149
|
+
|
|
150
|
+
# Common typos
|
|
151
|
+
grep -rE 'fluid_attribute[^s]|fluidAttributes|fluid_attribute_set' sections/ components/
|
|
152
|
+
|
|
153
|
+
# Hardcoded data-fluid-* attributes (likely should use the helper)
|
|
154
|
+
grep -rE 'data-fluid-section-id|data-fluid-section-block-id|data-fluid-parent-section-type' sections/ components/ \
|
|
155
|
+
| grep -v 'fluid_attributes'
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The last grep is the one to actually open a PR comment on — any hit is a hand-rolled subset of what the helper would emit.
|
|
159
|
+
|
|
160
|
+
---
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Worked examples — full section reviews
|
|
2
|
+
|
|
3
|
+
> Part of the `themes-review` skill. See [`../SKILL.md`](../SKILL.md) for the review workflow, severity ladder, and validator rules.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- Summary review output
|
|
8
|
+
- Inline comment (line 9)
|
|
9
|
+
- Inline comment (lines 5-8)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
**File:** `sections/featured_products/index.liquid`
|
|
13
|
+
|
|
14
|
+
```liquid
|
|
15
|
+
{% schema %}
|
|
16
|
+
{
|
|
17
|
+
"name": "Featured Products",
|
|
18
|
+
"settings": [
|
|
19
|
+
{ "type": "text", "id": "heading", "label": "Heading" },
|
|
20
|
+
{ "type": "product", "id": "p1", "label": "Product 1" },
|
|
21
|
+
{ "type": "product", "id": "p2", "label": "Product 2" },
|
|
22
|
+
{ "type": "product", "id": "p3", "label": "Product 3" },
|
|
23
|
+
{ "type": "product", "id": "p4", "label": "Product 4" },
|
|
24
|
+
{ "type": "text_area", "id": "desc", "label": "Description" },
|
|
25
|
+
{ "type": "image", "id": "bg", "label": "Background" }
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
{% endschema %}
|
|
29
|
+
|
|
30
|
+
<section>
|
|
31
|
+
<h2>{{ section.settings.heading }}</h2>
|
|
32
|
+
|
|
33
|
+
{% for product in products %}
|
|
34
|
+
<img src="{{ 'placeholder.png' | asset_url }}" />
|
|
35
|
+
<a href="{{ product.url }}">{{ product.title }}</a>
|
|
36
|
+
{% endfor %}
|
|
37
|
+
|
|
38
|
+
{% if section.settings.p1 != blank %}{% render 'product_card', product: section.settings.p1 %}{% endif %}
|
|
39
|
+
{% if section.settings.p2 != blank %}{% render 'product_card', product: section.settings.p2 %}{% endif %}
|
|
40
|
+
{% if section.settings.p3 != blank %}{% render 'product_card', product: section.settings.p3 %}{% endif %}
|
|
41
|
+
{% if section.settings.p4 != blank %}{% render 'product_card', product: section.settings.p4 %}{% endif %}
|
|
42
|
+
</section>
|
|
43
|
+
````
|
|
44
|
+
|
|
45
|
+
### Summary review output
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
## Theme review — featured_products.liquid
|
|
49
|
+
|
|
50
|
+
**8 findings** — 3 blocker, 4 should, 1 nit
|
|
51
|
+
|
|
52
|
+
Reproduce locally with `fluid theme lint --json`.
|
|
53
|
+
|
|
54
|
+
| Severity | Line | Finding |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| blocker | 9 | Invalid type `text_area` — use `textarea`. Validator rejects. |
|
|
57
|
+
| blocker | 16 | Missing `{{ section.fluid_attributes }}` on the section root — editor cannot select or click-to-edit this section. Replace `<section>` with `<section {{ section.fluid_attributes }}>`. |
|
|
58
|
+
| blocker | 18 | `{% for product in products %}` — `products` is undefined in this scope. It is not a global drop, and the schema declares no `products` setting. Loop silently iterates `nil`. Collapse to the `product_list` fix below (`section.settings.products`) or remove. |
|
|
59
|
+
| should | 10 | Type `image` is valid but `image_picker` is clearer for new code |
|
|
60
|
+
| should | 5-8 | Four `product` settings, same role — collapse to one `product_list` with `limit: 4` |
|
|
61
|
+
| should | 19 | `asset_url` resolved inside loop — hoist above |
|
|
62
|
+
| should | 16-21 | Missing whitespace control on `{% for %}` / `{% endfor %}` |
|
|
63
|
+
| nit | 17 | `<h2>{{ section.settings.heading }}</h2>` — add `| default: 'Featured'` |
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Inline comment (line 9)
|
|
67
|
+
|
|
68
|
+
````
|
|
69
|
+
[blocker] Invalid setting type `text_area`
|
|
70
|
+
|
|
71
|
+
The schema validator only accepts the canonical type list. `fluid theme lint --json`
|
|
72
|
+
fails on this. The correct type is `textarea`:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{ "type": "textarea", "id": "desc", "label": "Description" }
|
|
76
|
+
```
|
|
77
|
+
````
|
|
78
|
+
|
|
79
|
+
### Inline comment (lines 5-8)
|
|
80
|
+
|
|
81
|
+
````
|
|
82
|
+
|
|
83
|
+
[should] Four `product` settings — collapse to `product_list`
|
|
84
|
+
|
|
85
|
+
Four singular product pickers playing the same role. Replace with one list:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"type": "product_list",
|
|
90
|
+
"id": "products",
|
|
91
|
+
"label": "Products",
|
|
92
|
+
"limit": 4
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Render block becomes a single loop:
|
|
97
|
+
|
|
98
|
+
```liquid
|
|
99
|
+
{%- for product in section.settings.products -%}
|
|
100
|
+
{% render 'product_card', product: product %}
|
|
101
|
+
{%- endfor -%}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
This drops 4 conditionals + 4 settings into 1 + 1 and removes the per-slot upper
|
|
105
|
+
bound the manual setup encoded by accident. A `product_list` does **not** give
|
|
106
|
+
drag-and-drop reorder — that's a blocks feature; model items as blocks if the
|
|
107
|
+
merchant needs to reorder them.
|
|
108
|
+
|
|
109
|
+
````
|
|
110
|
+
|
|
111
|
+
---
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# FairShare behavioral attributes — data-fluid-*
|
|
2
|
+
|
|
3
|
+
> Part of the `themes-review` skill. See [`../SKILL.md`](../SKILL.md) for the review workflow, severity ladder, and validator rules.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- Naming convention
|
|
8
|
+
- Loading the runtime
|
|
9
|
+
- The attributes
|
|
10
|
+
- Correct usage examples
|
|
11
|
+
- Findings to surface
|
|
12
|
+
- Quick audit
|
|
13
|
+
- Checklist for FairShare-attributed elements
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
These are the **runtime behavioral attributes** consumed by the FairShare web-widget SDK (loaded from `https://assets.fluid.app/scripts/fluid-sdk/latest/web-widgets/index.js`). Theme authors hand-write them on buttons, links, and elements to wire up commerce behavior — add to cart, open cart drawer, add enrollment packs, etc.
|
|
17
|
+
|
|
18
|
+
**This is different from `section.fluid_attributes` above.** The editor attributes are emitted by Liquid drops in editor mode. These are static HTML attributes you write into the template that the runtime SDK scans for on `DOMContentLoaded`.
|
|
19
|
+
|
|
20
|
+
### Naming convention
|
|
21
|
+
|
|
22
|
+
All FairShare runtime attributes are `data-fluid-*` (kebab-case, `data-` prefix). The SDK is case-sensitive — `data-fluid-add-to-cart` works, `data-fluid-addToCart` and `data-fluid_add_to_cart` do not. The runtime auto-scans the DOM; no init call is required.
|
|
23
|
+
|
|
24
|
+
### Loading the runtime
|
|
25
|
+
|
|
26
|
+
```html
|
|
27
|
+
<script
|
|
28
|
+
id="fluid-cdn-script"
|
|
29
|
+
src="https://assets.fluid.app/scripts/fluid-sdk/latest/web-widgets/index.js"
|
|
30
|
+
data-fluid-shop="{{ shop.handle }}"
|
|
31
|
+
defer
|
|
32
|
+
></script>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
`data-fluid-shop` is required. Optional script-level attributes: `data-fluid-api-base-url`, `data-fluid-country` (2-letter ISO), `data-fluid-language` (2–3 letter), `data-fluid-rep-id`, `data-share-guid`, `data-debug`.
|
|
36
|
+
|
|
37
|
+
### The attributes
|
|
38
|
+
|
|
39
|
+
| Attribute | Value shape | Goes on | Notes |
|
|
40
|
+
| --------------------------------- | ----------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
41
|
+
| `data-fluid-cart` | `"open"` / `"close"` / `"toggle"` | button/link | Lowercase only. Strict equality check in the SDK. |
|
|
42
|
+
| `data-fluid-add-to-cart` | comma-separated variant IDs (integers) | button/link | Companion for `data-fluid-quantity`, `data-fluid-subscribe`, `data-fluid-subscription-plan-id`, `data-fluid-bundled-items`, `data-fluid-open-cart-after-add`. |
|
|
43
|
+
| `data-fluid-quantity` | positive integer | button/link **with** `data-fluid-add-to-cart` or `data-fluid-add-enrollment-pack` | Defaults to 1. |
|
|
44
|
+
| `data-fluid-subscribe` | `"true"` / `"false"` or comma-separated per item | button/link **with** `data-fluid-add-to-cart` | Per-item shape mirrors the variant ID list. |
|
|
45
|
+
| `data-fluid-subscription-plan-id` | **single** integer | button/link **with** `data-fluid-add-to-cart` | **Only one** — comma-separated values are not supported. |
|
|
46
|
+
| `data-fluid-bundled-items` | JSON string `[{"variant_id": int, "quantity": int}, ...]` | button/link **with** `data-fluid-add-to-cart` | Invalid JSON is logged and silently dropped. |
|
|
47
|
+
| `data-fluid-open-cart-after-add` | `"true"` / `"false"` | button/link **with** add-to-cart or enrollment-pack | Defaults to `"true"`. Set `"false"` to keep shopping. |
|
|
48
|
+
| `data-fluid-add-enrollment-pack` | enrollment pack ID (integer) | button/link | Can combine with `data-fluid-add-to-cart` for pack + items in one click. |
|
|
49
|
+
| `data-fluid-bundle-selections` | JSON string `[{"variant_id": int, "bundled_items": [...]}]` | button/link **with** `data-fluid-add-enrollment-pack` | For packs whose products take variant/bundle config. |
|
|
50
|
+
| `data-fluid-add-combined` | `"{enrollmentPackId}+{variantId1},{variantId2},..."` | button/link | Alternative to setting both `data-fluid-add-to-cart` and `data-fluid-add-enrollment-pack`. |
|
|
51
|
+
|
|
52
|
+
### Correct usage examples
|
|
53
|
+
|
|
54
|
+
```html
|
|
55
|
+
{%- comment -%} Open cart drawer {%- endcomment -%}
|
|
56
|
+
<button data-fluid-cart="open">Cart ({{ cart.item_count }})</button>
|
|
57
|
+
|
|
58
|
+
{%- comment -%} Add one item {%- endcomment -%}
|
|
59
|
+
<button data-fluid-add-to-cart="{{ product.first_variant.id }}">
|
|
60
|
+
Add to cart
|
|
61
|
+
</button>
|
|
62
|
+
|
|
63
|
+
{%- comment -%} Add with quantity + subscription {%- endcomment -%}
|
|
64
|
+
<button
|
|
65
|
+
data-fluid-add-to-cart="{{ variant.id }}"
|
|
66
|
+
data-fluid-quantity="2"
|
|
67
|
+
data-fluid-subscribe="true"
|
|
68
|
+
data-fluid-subscription-plan-id="{{ section.settings.default_plan_id }}"
|
|
69
|
+
>
|
|
70
|
+
Subscribe & save
|
|
71
|
+
</button>
|
|
72
|
+
|
|
73
|
+
{%- comment -%} Add a bundle product with its bundled items {%- endcomment -%}
|
|
74
|
+
<button
|
|
75
|
+
data-fluid-add-to-cart="{{ bundle_variant.id }}"
|
|
76
|
+
data-fluid-bundled-items='[
|
|
77
|
+
{%- for item in selected_items -%}
|
|
78
|
+
{ "variant_id": {{ item.variant_id }}, "quantity": {{ item.qty }} }{%- unless forloop.last -%},{%- endunless -%}
|
|
79
|
+
{%- endfor -%}
|
|
80
|
+
]'
|
|
81
|
+
data-fluid-open-cart-after-add="false"
|
|
82
|
+
>
|
|
83
|
+
Add bundle
|
|
84
|
+
</button>
|
|
85
|
+
|
|
86
|
+
{%- comment -%} Add an enrollment pack {%- endcomment -%}
|
|
87
|
+
<button data-fluid-add-enrollment-pack="{{ section.settings.starter_pack.id }}">
|
|
88
|
+
Join with starter pack
|
|
89
|
+
</button>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Findings to surface
|
|
93
|
+
|
|
94
|
+
The reviewer should flag these on any element that uses `data-fluid-*`:
|
|
95
|
+
|
|
96
|
+
#### Wrong attribute names (typos / wrong casing)
|
|
97
|
+
|
|
98
|
+
**`blocker`** — the SDK only recognizes the exact kebab-case forms listed above.
|
|
99
|
+
|
|
100
|
+
| You see | Severity | Fix |
|
|
101
|
+
| ----------------------------------------------------------- | --------- | ------------------------------------------------ |
|
|
102
|
+
| `data-fluid-addToCart="..."` | `blocker` | `data-fluid-add-to-cart="..."` |
|
|
103
|
+
| `data-fluid_add_to_cart="..."` (underscores) | `blocker` | Kebab-case only. |
|
|
104
|
+
| `datafluid-add-to-cart="..."` (missing hyphen after `data`) | `blocker` | Must be `data-fluid-*`. |
|
|
105
|
+
| `fluid-add-to-cart="..."` (missing `data-` prefix) | `blocker` | Add `data-`. |
|
|
106
|
+
| `data-fluid-cart="Open"` / `"OPEN"` | `blocker` | Lowercase: `"open"` / `"close"` / `"toggle"`. |
|
|
107
|
+
| `data-fluid-cart="show"` / `"display"` | `blocker` | Only `open` / `close` / `toggle` are recognized. |
|
|
108
|
+
|
|
109
|
+
#### Missing required companions / dangling modifiers
|
|
110
|
+
|
|
111
|
+
**`blocker`** — modifier attributes are inert without their owner.
|
|
112
|
+
|
|
113
|
+
| You see | Severity | Fix |
|
|
114
|
+
| -------------------------------------------------------------------------------------------------------------- | --------- | ------------------------------------------------------- |
|
|
115
|
+
| `data-fluid-quantity` on an element with neither `data-fluid-add-to-cart` nor `data-fluid-add-enrollment-pack` | `blocker` | Either pair it with one of the add-actions or remove. |
|
|
116
|
+
| `data-fluid-subscribe` without `data-fluid-add-to-cart` | `blocker` | Subscribe is meaningful only in an add-to-cart context. |
|
|
117
|
+
| `data-fluid-subscription-plan-id` without `data-fluid-add-to-cart` | `blocker` | Same. |
|
|
118
|
+
| `data-fluid-bundled-items` without `data-fluid-add-to-cart` | `blocker` | Bundled items modify the add-to-cart action. |
|
|
119
|
+
| `data-fluid-bundle-selections` without `data-fluid-add-enrollment-pack` | `blocker` | Only meaningful when adding a pack. |
|
|
120
|
+
| `data-fluid-open-cart-after-add` on an element with no add-action | `should` | Inert — remove or pair with an add-action. |
|
|
121
|
+
|
|
122
|
+
#### Wrong value shapes
|
|
123
|
+
|
|
124
|
+
| You see | Severity | Fix |
|
|
125
|
+
| ------------------------------------------------------------------------------------------ | --------- | ------------------------------------------------------------------------------------------------------------- |
|
|
126
|
+
| `data-fluid-subscription-plan-id="4,5"` (comma-separated) | `blocker` | Single integer only. If the items need different plans, that's not supported — drop down to single-item rows. |
|
|
127
|
+
| `data-fluid-quantity="2.5"` / `"0"` / `"-1"` | `blocker` | Positive integer. |
|
|
128
|
+
| `data-fluid-subscribe="yes"` / `"1"` / `"on"` | `blocker` | String `"true"` or `"false"` only. |
|
|
129
|
+
| `data-fluid-bundled-items='[{"variant_id":"39325", "quantity":1}]'` (variant_id as string) | `blocker` | `variant_id` and `quantity` must be JSON numbers. |
|
|
130
|
+
| `data-fluid-bundled-items='{...}'` (object, not array) | `blocker` | Must be a JSON array. |
|
|
131
|
+
| Single-quote-wrapped JSON without escaping inner double quotes | `blocker` | Use `'[...]'` outer + `"..."` inner per the examples. Mismatched quoting breaks parsing silently. |
|
|
132
|
+
| `data-fluid-country="USA"` (3-letter) on the script tag | `blocker` | 2-letter ISO. `"US"`, not `"USA"`. |
|
|
133
|
+
| `data-fluid-language="english"` on the script tag | `blocker` | 2–3 letter code: `"en"`, `"es"`, `"fil"`. |
|
|
134
|
+
|
|
135
|
+
#### Wrong element type / placement
|
|
136
|
+
|
|
137
|
+
| You see | Severity | Fix |
|
|
138
|
+
| ----------------------------------------------------------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
139
|
+
| `data-fluid-add-to-cart` on a `<div>` with no click semantics | `should` (`blocker` for accessibility-critical pages) | Use `<button type="button">` or `<a href="...">`. |
|
|
140
|
+
| `data-fluid-add-to-cart` on a parent that wraps multiple buttons | `blocker` | Move to the specific clickable child — the SDK click handler fires once per click, on the element with the attribute. |
|
|
141
|
+
| `data-fluid-shop` missing from the `<script id="fluid-cdn-script">` tag | `blocker` | Required for SDK init. Without it, none of the `data-fluid-*` attributes do anything. |
|
|
142
|
+
| Multiple `<script>` tags with `id="fluid-cdn-script"` | `blocker` | The SDK uses this ID for self-discovery; duplicates pick a random one. |
|
|
143
|
+
|
|
144
|
+
#### Liquid-side mistakes
|
|
145
|
+
|
|
146
|
+
| You see | Severity | Fix |
|
|
147
|
+
| ---------------------------------------------------------------------------------------------- | ---------------------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
148
|
+
| `data-fluid-add-to-cart="{{ variant }}"` (drop, not `.id`) | `blocker` | `{{ variant.id }}`. |
|
|
149
|
+
| `data-fluid-quantity="{{ section.settings.default_qty | default: 1 }}"` rendering to empty | `should` | Always provide a `default:` filter for settings that are required by the SDK to be integers. |
|
|
150
|
+
| `data-fluid-bundled-items` JSON built with un-escaped Liquid (commas/quotes from user content) | `blocker` | Render through ` | json` or hand-craft only with sanitized numeric IDs. User-controlled strings inside JSON attributes are an XSS path. |
|
|
151
|
+
|
|
152
|
+
### Quick audit
|
|
153
|
+
|
|
154
|
+
From the theme repo root:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# Wrong attribute prefixes
|
|
158
|
+
grep -rE 'datafluid-|data-fluid_|[^a-z-]fluid-add-to-cart|[^a-z-]fluid-cart=' --include='*.liquid' . 2>/dev/null
|
|
159
|
+
|
|
160
|
+
# Common camelCase typos
|
|
161
|
+
grep -rE 'data-fluid-(addToCart|openCartAfterAdd|subscriptionPlanId|bundledItems)' --include='*.liquid' . 2>/dev/null
|
|
162
|
+
|
|
163
|
+
# Modifier attributes without an owner action in the same element
|
|
164
|
+
# (heuristic — manual review still needed)
|
|
165
|
+
grep -rln 'data-fluid-quantity' --include='*.liquid' . 2>/dev/null | while read -r f; do
|
|
166
|
+
if ! grep -q 'data-fluid-add-to-cart\|data-fluid-add-enrollment-pack' "$f"; then
|
|
167
|
+
echo "POSSIBLE_ORPHAN $f"
|
|
168
|
+
fi
|
|
169
|
+
done
|
|
170
|
+
|
|
171
|
+
# Wrong cart action values
|
|
172
|
+
grep -rE 'data-fluid-cart="(?!(open|close|toggle)")' --include='*.liquid' . 2>/dev/null
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Checklist for FairShare-attributed elements
|
|
176
|
+
|
|
177
|
+
- [ ] Attribute name is exact kebab-case with `data-fluid-` prefix
|
|
178
|
+
- [ ] `data-fluid-cart` value is `open` / `close` / `toggle` (lowercase)
|
|
179
|
+
- [ ] Modifier attributes (`quantity`, `subscribe`, `subscription-plan-id`, `bundled-items`, `open-cart-after-add`) are paired with an add-action attribute
|
|
180
|
+
- [ ] `data-fluid-subscription-plan-id` carries a single integer
|
|
181
|
+
- [ ] `data-fluid-bundled-items` / `data-fluid-bundle-selections` are valid JSON arrays with numeric IDs and quantities
|
|
182
|
+
- [ ] Attributes live on a `<button>` or `<a>` (or have appropriate ARIA + click semantics)
|
|
183
|
+
- [ ] Script tag has `id="fluid-cdn-script"` and `data-fluid-shop` set, and there is exactly one
|
|
184
|
+
|
|
185
|
+
---
|