@fluid-app/fluid-cli-theme-dev 0.1.30 → 0.1.31
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.
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: themes-cart-feedback
|
|
3
|
+
description: |
|
|
4
|
+
Wire up shopper feedback for cart mutations in a Fluid Liquid theme, using the FairShare
|
|
5
|
+
web-widget SDK. Two surfaces: (1) the built-in feedback — a toast that confirms or reports
|
|
6
|
+
a cart add/update/remove and add-to-cart buttons that spin while the request is in flight,
|
|
7
|
+
enabled with `FairShareSDK.configureCartFeedback(...)` or `data-fluid-toast` /
|
|
8
|
+
`data-fluid-button-loading` script attributes; (2) the cart operation events —
|
|
9
|
+
`CART_OPERATION_SUCCESS` and `CART_OPERATION_ERROR` window events that fire on every cart
|
|
10
|
+
mutation regardless of toast config, so custom UI can react. Use when building or reviewing
|
|
11
|
+
anything that adds to cart or reacts to a cart change: custom product cards, shop / collection
|
|
12
|
+
grids, cart drawers and cart sections, mini-cart badges, "added to cart" confirmations, and
|
|
13
|
+
enrollment-pack join flows. Companion to `themes-review`'s FairShare `data-fluid-*` attributes
|
|
14
|
+
(those trigger the mutation; this skill handles the result). Edits are local Liquid/JS/CSS;
|
|
15
|
+
never run `fluid theme push` without explicit approval.
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Cart Feedback & Cart Operation Events
|
|
19
|
+
|
|
20
|
+
When a shopper adds, updates, or removes a cart item on a Fluid theme, the FairShare web-widget SDK can surface the result two ways:
|
|
21
|
+
|
|
22
|
+
1. **Built-in feedback** — a **toast** confirms success or reports a failure, and add-to-cart **buttons** show a spinner while the request is in flight. Opt in once; zero custom JS. This is the right default for most themes.
|
|
23
|
+
2. **Cart operation events** — the SDK dispatches `CART_OPERATION_SUCCESS` and `CART_OPERATION_ERROR` on `window` for **every** mutation, whether or not the toast is enabled. Listen to these when you're building custom commerce UI that has to react — update a mini-cart badge, re-render a cart drawer, flash an inline confirmation on the specific product card, or route an enrollment flow forward.
|
|
24
|
+
|
|
25
|
+
This skill is the **result** half of theme commerce. The **trigger** half — the declarative `data-fluid-add-to-cart`, `data-fluid-add-enrollment-pack`, `data-fluid-cart` attributes and the CDN `<script>` — lives in the `themes-review` skill's [FairShare attributes reference](../themes-review/references/fairshare-attributes.md). Read that first if the theme doesn't yet load the SDK or wire up add-to-cart buttons.
|
|
26
|
+
|
|
27
|
+
## Which path do I need?
|
|
28
|
+
|
|
29
|
+
| You want to… | Use |
|
|
30
|
+
| ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- |
|
|
31
|
+
| Confirm every add/update/remove with a standard toast, and spin add-to-cart buttons | **Built-in feedback** ([Part 1](#part-1--built-in-feedback)) |
|
|
32
|
+
| Localize or restyle that toast, or move it to a corner | **Built-in feedback** + config |
|
|
33
|
+
| Update a custom cart-count badge / mini-cart when the cart changes | **Events** ([Part 2](#part-2--cart-operation-events)) |
|
|
34
|
+
| Flash an inline "Added ✓" on the exact product card the shopper clicked | **Events**, matched by `variantIds` |
|
|
35
|
+
| Re-render a hand-built cart drawer or cart section on update/remove | **Events**, filtered by `category` |
|
|
36
|
+
| Advance an enrollment-pack join flow after the pack lands (or show its error) | **Events**, keyed on `enrollmentPackId` |
|
|
37
|
+
| Report a failure that the SDK would otherwise swallow (`addCartItems`, `decrementCartItem`) | **Events** — `CART_OPERATION_ERROR` (a `try/catch` won't see it) |
|
|
38
|
+
|
|
39
|
+
Most themes want the built-in feedback for the common case **and** a small event listener or two for their custom UI. The two coexist — enabling the toast does not stop the events from firing.
|
|
40
|
+
|
|
41
|
+
## Prerequisite — the SDK must be loaded
|
|
42
|
+
|
|
43
|
+
Everything here depends on exactly one `<script id="fluid-cdn-script">` with `data-fluid-shop` set, loaded by `layouts/theme.liquid`:
|
|
44
|
+
|
|
45
|
+
```html
|
|
46
|
+
<script
|
|
47
|
+
id="fluid-cdn-script"
|
|
48
|
+
src="https://assets.fluid.app/scripts/fluid-sdk/latest/web-widgets/index.js"
|
|
49
|
+
data-fluid-shop="{{ shop.handle }}"
|
|
50
|
+
defer
|
|
51
|
+
></script>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Without it, `window.FairShareSDK` is undefined and no events fire. See the [FairShare attributes reference](../themes-review/references/fairshare-attributes.md) for the full script-tag contract. **Always guard SDK access with `window.FairShareSDK?.`** — with `defer`, the theme's own inline scripts can run before the SDK finishes initializing.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
# Part 1 — Built-in feedback
|
|
59
|
+
|
|
60
|
+
The built-in toast + button-loading covers the 80% case with no custom JavaScript. Turn it on once, localize the strings, optionally restyle, and you're done.
|
|
61
|
+
|
|
62
|
+
## Enable it
|
|
63
|
+
|
|
64
|
+
Two ways, and they merge (**precedence: `configureCartFeedback()` call > script-tag attributes > defaults**).
|
|
65
|
+
|
|
66
|
+
### Runtime (recommended — lets you localize)
|
|
67
|
+
|
|
68
|
+
Put this in `layouts/theme.liquid` (or a section that renders on every commerce page), inside a `DOMContentLoaded` listener so the SDK is ready and Liquid can inject translated strings:
|
|
69
|
+
|
|
70
|
+
```liquid
|
|
71
|
+
<script>
|
|
72
|
+
window.addEventListener("DOMContentLoaded", () => {
|
|
73
|
+
window.FairShareSDK?.configureCartFeedback({
|
|
74
|
+
toast: true,
|
|
75
|
+
buttonLoading: true,
|
|
76
|
+
position: "bottom-right",
|
|
77
|
+
messages: {
|
|
78
|
+
add: {{ 'cart.added' | t | json }},
|
|
79
|
+
update: {{ 'cart.updated' | t | json }},
|
|
80
|
+
remove: {{ 'cart.removed' | t | json }},
|
|
81
|
+
error: {{ 'cart.error' | t | json }},
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
</script>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
- The optional-chaining `?.` is required — the `defer`'d SDK may not have attached `configureCartFeedback` yet on a slow load; the guard makes a no-op instead of a `TypeError`.
|
|
89
|
+
- `{{ '…' | t | json }}` runs the locale lookup **and** JSON-escapes the result, so quotes/newlines/apostrophes in a translation can't break the script. Never hand-quote a `{{ '…' | t }}` string. See [i18n](#localize-the-messages).
|
|
90
|
+
|
|
91
|
+
### Script-tag attributes (no localization, quickest)
|
|
92
|
+
|
|
93
|
+
Add attributes to the same `<script id="fluid-cdn-script">` tag:
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<script
|
|
97
|
+
id="fluid-cdn-script"
|
|
98
|
+
src="https://assets.fluid.app/scripts/fluid-sdk/latest/web-widgets/index.js"
|
|
99
|
+
data-fluid-shop="{{ shop.handle }}"
|
|
100
|
+
data-fluid-toast="true"
|
|
101
|
+
data-fluid-button-loading="true"
|
|
102
|
+
data-fluid-toast-position="bottom-right"
|
|
103
|
+
defer
|
|
104
|
+
></script>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Use this only when the theme doesn't need translated strings. Anything you can pass as a `data-fluid-toast-*` attribute you can also pass to `configureCartFeedback()`; the full attribute catalog is in [references/cart-operation-events.md](references/cart-operation-events.md).
|
|
108
|
+
|
|
109
|
+
## The knobs
|
|
110
|
+
|
|
111
|
+
| Key (`configureCartFeedback`) | Type | Default | Notes |
|
|
112
|
+
| ----------------------------- | --------- | ---------------- | ---------------------------------------------------------------------------------- |
|
|
113
|
+
| `toast` | boolean | `false` | Master switch for the toast. |
|
|
114
|
+
| `buttonLoading` | boolean | `false` | Auto-spinner on `data-fluid-add-to-cart` / `data-fluid-add-enrollment-pack` buttons. |
|
|
115
|
+
| `position` | string | `bottom-center` | `bottom-center` \| `bottom-left` \| `bottom-right` \| `top-center` \| `top-left` \| `top-right`. |
|
|
116
|
+
| `duration` | number | `4000` | Auto-dismiss delay in ms. |
|
|
117
|
+
| `class` | string | — | Extra CSS class on the toast, for styling. |
|
|
118
|
+
| `icon` | boolean | `true` | Show the variant icon. |
|
|
119
|
+
| `successIcon` / `errorIcon` / `closeIcon` | string | built-ins | Emoji, text, or SVG markup. |
|
|
120
|
+
| `messages` | object | English defaults | `{ add, update, remove, error }` string overrides. |
|
|
121
|
+
|
|
122
|
+
Default messages: `add` → "Added to cart", `update` → "Cart updated", `remove` → "Removed from cart", `error` → "Something went wrong. Please try again."
|
|
123
|
+
|
|
124
|
+
**Success toasts are suppressed while the cart drawer is open** (the drawer already shows the change); **error toasts always display**.
|
|
125
|
+
|
|
126
|
+
## Localize the messages
|
|
127
|
+
|
|
128
|
+
Drive every string through the theme's `locales/*.json` with the `t` filter, and always chain `| json`:
|
|
129
|
+
|
|
130
|
+
```liquid
|
|
131
|
+
messages: {
|
|
132
|
+
add: {{ 'cart.added' | t | json }},
|
|
133
|
+
update: {{ 'cart.updated' | t | json }},
|
|
134
|
+
remove: {{ 'cart.removed' | t | json }},
|
|
135
|
+
error: {{ 'cart.error' | t | json }},
|
|
136
|
+
},
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`| json` is not optional — it's what makes a French `"Ajouté à votre panier"` or an apostrophe-bearing string safe to drop into JavaScript. Add the four keys to `locales/en.json` (and every other locale the theme ships).
|
|
140
|
+
|
|
141
|
+
## Button loading
|
|
142
|
+
|
|
143
|
+
Two ways to spin a button while its cart request is in flight.
|
|
144
|
+
|
|
145
|
+
### Declarative — for `data-fluid-*` buttons
|
|
146
|
+
|
|
147
|
+
With `buttonLoading: true` (or `data-fluid-button-loading="true"`), any button carrying `data-fluid-add-to-cart` or `data-fluid-add-enrollment-pack` spins automatically. Customize the label with `data-fluid-loading-text`:
|
|
148
|
+
|
|
149
|
+
```liquid
|
|
150
|
+
<button
|
|
151
|
+
type="button"
|
|
152
|
+
data-fluid-add-to-cart="{{ product.first_variant.id }}"
|
|
153
|
+
data-fluid-loading-text="{{ 'product.adding' | t }}"
|
|
154
|
+
>
|
|
155
|
+
{{ 'product.add_to_cart' | t }}
|
|
156
|
+
</button>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Programmatic — for buttons that call the SDK directly
|
|
160
|
+
|
|
161
|
+
When you drive the mutation from your own JS (not via a `data-fluid-*` attribute), wrap the call so the spinner clears no matter how it resolves:
|
|
162
|
+
|
|
163
|
+
```html
|
|
164
|
+
<button id="custom-add" type="button">Add to cart</button>
|
|
165
|
+
<script>
|
|
166
|
+
document.getElementById("custom-add")?.addEventListener("click", (e) => {
|
|
167
|
+
window.FairShareSDK?.withButtonLoading(e.currentTarget, () =>
|
|
168
|
+
window.FairShareSDK?.addCartItems(11111, { quantity: 1 }),
|
|
169
|
+
);
|
|
170
|
+
});
|
|
171
|
+
</script>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
- `withButtonLoading(el, fn)` — runs `fn`, shows the spinner, and clears it in a `finally` (so an error still stops the spin). Prefer this.
|
|
175
|
+
- `setButtonLoading(el, on)` — manual toggle; idempotent, sets `aria-busy`. Use only when you can't wrap the call in a single function (then clear it in your own `finally`).
|
|
176
|
+
|
|
177
|
+
The spinner inherits the button's text color, so it fits any button style with no extra CSS.
|
|
178
|
+
|
|
179
|
+
## Style the toast
|
|
180
|
+
|
|
181
|
+
The toast renders in **light DOM** with `id="fluid-toast"`, so plain CSS in an `assets/*.css` reaches it. Target state with the data attributes it carries — `data-variant` (`success` / `error`), `data-position`, `data-state` (`open` / `closed`) — plus any `class` you passed:
|
|
182
|
+
|
|
183
|
+
```css
|
|
184
|
+
#fluid-toast {
|
|
185
|
+
border-radius: var(--border_radius, 8px);
|
|
186
|
+
font-family: var(--font_family);
|
|
187
|
+
box-shadow: 0 8px 24px rgb(0 0 0 / 0.15);
|
|
188
|
+
}
|
|
189
|
+
#fluid-toast[data-variant="success"] {
|
|
190
|
+
background: var(--primary_color, #0f9d58);
|
|
191
|
+
color: #fff;
|
|
192
|
+
}
|
|
193
|
+
#fluid-toast[data-variant="error"] {
|
|
194
|
+
background: #d93025;
|
|
195
|
+
color: #fff;
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Reuse the theme's own CSS variables (`var(--primary_color)`, `var(--border_radius)`) so the toast matches the theme instead of hardcoding brand colors.
|
|
200
|
+
|
|
201
|
+
## Suppress one toast without killing its event
|
|
202
|
+
|
|
203
|
+
Pass `{ toast: false }` to a single mutation to skip its toast while the event still fires (handy when your custom UI already confirms that specific action):
|
|
204
|
+
|
|
205
|
+
```javascript
|
|
206
|
+
await window.FairShareSDK?.addCartItems(11111, { quantity: 1, toast: false });
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
# Part 2 — Cart operation events
|
|
212
|
+
|
|
213
|
+
For anything the built-in toast can't express — a live cart-count badge, a hand-built cart drawer, a per-card confirmation, an enrollment flow — listen to the two window events. **They fire on every mutation regardless of toast config.**
|
|
214
|
+
|
|
215
|
+
## The two events
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
window.addEventListener("CART_OPERATION_SUCCESS", (e) => {
|
|
219
|
+
// e.detail.operation, e.detail.category, e.detail.cart, …
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
window.addEventListener("CART_OPERATION_ERROR", (e) => {
|
|
223
|
+
// e.detail.operation, e.detail.category, e.detail.message, …
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
The event names are **exact, uppercase, underscored** — `CART_OPERATION_SUCCESS` / `CART_OPERATION_ERROR`. `cart_operation_success`, `CartOperationSuccess`, or any other casing silently never fires.
|
|
228
|
+
|
|
229
|
+
## The `detail` payload
|
|
230
|
+
|
|
231
|
+
Both events carry a `CustomEvent.detail`. The fields you'll actually use:
|
|
232
|
+
|
|
233
|
+
| Field | On | Use |
|
|
234
|
+
| ------------------ | ------------- | --------------------------------------------------------------------- |
|
|
235
|
+
| `operation` | both | The mutation name, e.g. `"addCartItems"`. |
|
|
236
|
+
| `category` | both | `"add"` \| `"update"` \| `"remove"` — the coarse bucket to switch on. |
|
|
237
|
+
| `cart` | success only | The resulting cart object. |
|
|
238
|
+
| `message` | error only | Best-effort human-readable error text — surface it or fall back to your own copy. |
|
|
239
|
+
| `variantIds` | when known | `number[]` of affected variants — match a success back to a card. |
|
|
240
|
+
| `enrollmentPackId` | when known | The pack id, on `addEnrollmentPack`. |
|
|
241
|
+
| `itemId` | when known | Cart item id for item-scoped ops. |
|
|
242
|
+
|
|
243
|
+
Full field list (plus `toast`, `timestamp`, `error`) is in [references/cart-operation-events.md](references/cart-operation-events.md).
|
|
244
|
+
|
|
245
|
+
> **Reading the cart count:** rather than guessing the shape of `e.detail.cart`, the SDK exposes `window.FairShareSDK.getCartItemCount()` — call it from the success handler for a reliable count. Use `e.detail.cart` only for fields you've confirmed exist.
|
|
246
|
+
|
|
247
|
+
## Attach the listener once
|
|
248
|
+
|
|
249
|
+
Register the listener a single time, at the theme/layout level — **not** inside a section that can render more than once, or per product card. Duplicate listeners double-fire.
|
|
250
|
+
|
|
251
|
+
```html
|
|
252
|
+
{%- comment -%} In layouts/theme.liquid or a once-per-page section {%- endcomment -%}
|
|
253
|
+
<script>
|
|
254
|
+
window.addEventListener("CART_OPERATION_SUCCESS", (e) => {
|
|
255
|
+
const badge = document.getElementById("fluid-cart-count");
|
|
256
|
+
if (badge) badge.textContent = String(window.FairShareSDK?.getCartItemCount() ?? "0");
|
|
257
|
+
});
|
|
258
|
+
</script>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
If you must attach from a section that may appear multiple times, guard against re-registering:
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
if (!window.__themeCartListenerAttached) {
|
|
265
|
+
window.__themeCartListenerAttached = true;
|
|
266
|
+
window.addEventListener("CART_OPERATION_SUCCESS", handleCartChange);
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Error handling — the swallow-vs-reject rule
|
|
271
|
+
|
|
272
|
+
**This is why the error event matters.** Some mutations reject their promise on failure (you can `try/catch`); two **swallow** the failure and resolve `undefined` for backward compatibility, so a `try/catch` sees nothing:
|
|
273
|
+
|
|
274
|
+
| Mutation | On failure | Can `try/catch` see it? |
|
|
275
|
+
| ----------------------- | --------------------------------- | ----------------------- |
|
|
276
|
+
| `addCartItems` | resolves `undefined` (swallowed) | **No** — use the event |
|
|
277
|
+
| `decrementCartItem` | resolves `undefined` (swallowed) | **No** — use the event |
|
|
278
|
+
| `addEnrollmentPack` | rethrows | Yes |
|
|
279
|
+
| `updateCartItems` | rethrows | Yes |
|
|
280
|
+
| `updateCartItemVariant` | rethrows | Yes |
|
|
281
|
+
| `removeCartItemById` | rethrows | Yes |
|
|
282
|
+
|
|
283
|
+
`CART_OPERATION_ERROR` fires for **all six**. So the durable rule: **surface failures from the event, not from a `try/catch`.** A `catch` block after `addCartItems` is dead code.
|
|
284
|
+
|
|
285
|
+
```javascript
|
|
286
|
+
window.addEventListener("CART_OPERATION_ERROR", (e) => {
|
|
287
|
+
showBanner(e.detail.message); // fires even for the swallowing mutations
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Worked examples
|
|
292
|
+
|
|
293
|
+
### A. Live cart-count badge (product cards, shop grids, navbar)
|
|
294
|
+
|
|
295
|
+
The single most common need. One listener updates the count wherever the shopper adds from — a product card, a collection grid, a quick-add. Put the badge markup where you want it and the listener once in the layout:
|
|
296
|
+
|
|
297
|
+
```html
|
|
298
|
+
<span id="fluid-cart-count">{{ cart.item_count }}</span>
|
|
299
|
+
|
|
300
|
+
<script>
|
|
301
|
+
window.addEventListener("CART_OPERATION_SUCCESS", () => {
|
|
302
|
+
const badge = document.getElementById("fluid-cart-count");
|
|
303
|
+
if (badge) badge.textContent = String(window.FairShareSDK?.getCartItemCount() ?? "0");
|
|
304
|
+
});
|
|
305
|
+
</script>
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### B. Inline "Added ✓" on the exact card clicked (`variantIds` match)
|
|
309
|
+
|
|
310
|
+
When a grid of product cards shares one listener, use `variantIds` to light up only the card the shopper acted on. Reveal a **separate** confirmation element rather than overwriting the button text — the button's own label is managed by `withButtonLoading`, so leave it alone:
|
|
311
|
+
|
|
312
|
+
```html
|
|
313
|
+
{%- comment -%} Each card carries its variant id; a hidden confirmation sits alongside the button {%- endcomment -%}
|
|
314
|
+
<div class="product-card" data-variant-id="{{ product.first_variant.id }}">
|
|
315
|
+
<button
|
|
316
|
+
type="button"
|
|
317
|
+
onclick="window.FairShareSDK?.withButtonLoading(this, () => window.FairShareSDK.addCartItems({{ product.first_variant.id }}, { quantity: 1 }))"
|
|
318
|
+
>
|
|
319
|
+
{{ 'product.add_to_cart' | t }}
|
|
320
|
+
</button>
|
|
321
|
+
<span class="added-flag" hidden>{{ 'product.added' | t }} ✓</span>
|
|
322
|
+
</div>
|
|
323
|
+
|
|
324
|
+
<script>
|
|
325
|
+
window.addEventListener("CART_OPERATION_SUCCESS", (e) => {
|
|
326
|
+
for (const id of e.detail.variantIds ?? []) {
|
|
327
|
+
const card = document.querySelector(`.product-card[data-variant-id="${id}"]`);
|
|
328
|
+
card?.querySelector(".added-flag")?.removeAttribute("hidden");
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
</script>
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### C. Re-render a custom cart drawer / cart section (`category` filter)
|
|
335
|
+
|
|
336
|
+
A hand-built cart section refetches its own markup whenever the cart changes. Every success is an add / update / remove, so all three want a refresh — don't gate on `category` unless you're treating one differently (e.g. animating a removal):
|
|
337
|
+
|
|
338
|
+
```javascript
|
|
339
|
+
window.addEventListener("CART_OPERATION_SUCCESS", (e) => {
|
|
340
|
+
// e.detail.category is "add" | "update" | "remove" — switch on it only to
|
|
341
|
+
// vary the treatment; any of them changes what the cart section should show.
|
|
342
|
+
refreshCartSection(); // your fetch/re-render of the cart section
|
|
343
|
+
});
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Because success toasts self-suppress while the drawer is open, a re-rendering drawer and the toast don't fight.
|
|
347
|
+
|
|
348
|
+
### D. Enrollment-pack join flow (`enrollmentPackId`)
|
|
349
|
+
|
|
350
|
+
An enrollment CTA adds the pack, spins while it's in flight, then advances on success or shows the error:
|
|
351
|
+
|
|
352
|
+
```html
|
|
353
|
+
<button
|
|
354
|
+
type="button"
|
|
355
|
+
data-fluid-add-enrollment-pack="{{ section.settings.starter_pack.id }}"
|
|
356
|
+
data-fluid-loading-text="{{ 'enroll.joining' | t }}"
|
|
357
|
+
>
|
|
358
|
+
{{ 'enroll.join_with_pack' | t }}
|
|
359
|
+
</button>
|
|
360
|
+
|
|
361
|
+
<script>
|
|
362
|
+
window.addEventListener("CART_OPERATION_SUCCESS", (e) => {
|
|
363
|
+
if (e.detail.operation === "addEnrollmentPack") {
|
|
364
|
+
window.location.href = "/checkout"; // or reveal the next step
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
window.addEventListener("CART_OPERATION_ERROR", (e) => {
|
|
368
|
+
if (e.detail.operation === "addEnrollmentPack") {
|
|
369
|
+
showEnrollError(e.detail.message);
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
</script>
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Combine `data-fluid-button-loading` (declarative spinner) with the events (routing) — the attribute handles the in-flight state, the event handles the outcome.
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## Anti-patterns
|
|
380
|
+
|
|
381
|
+
- **Rolling your own `fetch` to the cart API.** You lose the events, the toast, button loading, BFF-mode handling, and locale settings. Always go through `FairShareSDK.addCartItems` / `addEnrollmentPack` / `updateCartItems` / `removeCartItemById`, or the declarative `data-fluid-*` attributes.
|
|
382
|
+
- **Wrong event-name casing.** Only `CART_OPERATION_SUCCESS` / `CART_OPERATION_ERROR` fire. `cart_operation_success`, `CartOperationSuccess`, `cartOperationSuccess` bind a listener that never runs.
|
|
383
|
+
- **`try/catch` after `addCartItems` / `decrementCartItem`.** They swallow failures and resolve `undefined`; the `catch` is dead code. Listen for `CART_OPERATION_ERROR`.
|
|
384
|
+
- **Registering the listener per section / per card.** Sections re-render; you'll double- or triple-fire. Attach once at layout level, or guard with a `window.__…Attached` flag.
|
|
385
|
+
- **Calling `configureCartFeedback` / the SDK without `?.` or before `DOMContentLoaded`.** The `defer`'d SDK may not be ready; guard every access with `window.FairShareSDK?.`.
|
|
386
|
+
- **Hardcoding toast strings.** Use `messages: { add: {{ 'cart.added' | t | json }} }`. Hand-quoting `{{ '…' | t }}` breaks on apostrophes and defeats localization.
|
|
387
|
+
- **Reimplementing a button spinner.** Use `data-fluid-button-loading` or `withButtonLoading` — they set `aria-busy` and inherit the button color for free.
|
|
388
|
+
- **Guessing `e.detail.cart` field names.** Use `getCartItemCount()` for the count; only read cart fields you've verified in a running theme.
|
|
389
|
+
|
|
390
|
+
## Quick audit
|
|
391
|
+
|
|
392
|
+
From the theme repo root:
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
# Wrong event-name casing (should be exact CART_OPERATION_SUCCESS / _ERROR)
|
|
396
|
+
grep -rnE "addEventListener\((['\"])(cart_operation|CartOperation|cartOperation)" --include='*.liquid' --include='*.js' . 2>/dev/null
|
|
397
|
+
|
|
398
|
+
# Dead try/catch around a swallowing mutation
|
|
399
|
+
grep -rnE -B2 -A4 'addCartItems|decrementCartItem' --include='*.liquid' --include='*.js' . 2>/dev/null | grep -i 'catch'
|
|
400
|
+
|
|
401
|
+
# SDK access without an optional-chaining guard
|
|
402
|
+
grep -rnE 'window\.FairShareSDK\.' --include='*.liquid' --include='*.js' . 2>/dev/null
|
|
403
|
+
|
|
404
|
+
# Hardcoded toast messages (missing | t | json)
|
|
405
|
+
grep -rnE 'messages:[[:space:]]*\{' --include='*.liquid' . 2>/dev/null
|
|
406
|
+
|
|
407
|
+
# Hand-rolled cart fetches that bypass the SDK
|
|
408
|
+
grep -rnE "fetch\(['\"][^'\"]*cart" --include='*.liquid' --include='*.js' . 2>/dev/null
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Each hit is a candidate finding — confirm in context before flagging.
|
|
412
|
+
|
|
413
|
+
## Checklist
|
|
414
|
+
|
|
415
|
+
**SDK & setup**
|
|
416
|
+
|
|
417
|
+
- [ ] Exactly one `<script id="fluid-cdn-script">` with `data-fluid-shop` set (see `themes-review` FairShare reference)
|
|
418
|
+
- [ ] Every `FairShareSDK` access is guarded with `?.` and runs on/after `DOMContentLoaded`
|
|
419
|
+
|
|
420
|
+
**Built-in feedback (Part 1)**
|
|
421
|
+
|
|
422
|
+
- [ ] `configureCartFeedback({ toast, buttonLoading, … })` called once, in a `DOMContentLoaded` listener
|
|
423
|
+
- [ ] `messages` sourced from `locales/*.json` via `{{ 'key' | t | json }}` (never hand-quoted)
|
|
424
|
+
- [ ] The four message keys (`cart.added` / `cart.updated` / `cart.removed` / `cart.error`) exist in every locale file
|
|
425
|
+
- [ ] Toast styled through `#fluid-toast` + `data-variant`, reusing theme CSS variables (not hardcoded brand colors)
|
|
426
|
+
- [ ] Button spinners use `data-fluid-button-loading` or `withButtonLoading` (no hand-rolled spinner)
|
|
427
|
+
|
|
428
|
+
**Events (Part 2)**
|
|
429
|
+
|
|
430
|
+
- [ ] Event names are exactly `CART_OPERATION_SUCCESS` / `CART_OPERATION_ERROR`
|
|
431
|
+
- [ ] Listener attached once (layout level or guarded), never per-section / per-card
|
|
432
|
+
- [ ] Failures surfaced from `CART_OPERATION_ERROR`, not a `try/catch` (mandatory for `addCartItems` / `decrementCartItem`)
|
|
433
|
+
- [ ] Per-card / per-item reactions matched via `variantIds` / `itemId`; enrollment via `enrollmentPackId`
|
|
434
|
+
- [ ] Cart count read via `getCartItemCount()`, not a guessed `e.detail.cart` field
|
|
435
|
+
|
|
436
|
+
**Correctness**
|
|
437
|
+
|
|
438
|
+
- [ ] Cart mutations go through the SDK (or `data-fluid-*`), never a hand-rolled `fetch`
|
|
439
|
+
- [ ] Verified in a running theme with `fluid theme dev` (lint does not check runtime JS — see below)
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## Boundaries — local edits, lint, never push
|
|
444
|
+
|
|
445
|
+
- Everything here is a **local file edit** — Liquid, JS, and CSS in the theme repo. This skill never runs `fluid theme push`, never calls the API, and never writes to the live theme **without explicit approval**.
|
|
446
|
+
- Run `fluid theme lint --json` after schema-adjacent edits and parse the JSON, as with any theme change. **But note:** the linter validates `{% schema %}` and section references only — it does **not** execute or check the JavaScript, the event wiring, or the toast config. The real check for this skill is **`fluid theme dev`**: open the storefront, add/update/remove an item, and watch the toast, the spinner, the badge, and (via the console) the events actually fire.
|
|
447
|
+
- When reviewing a PR rather than authoring, follow the `themes-review` workflow — inline `file:line` findings with a `blocker` / `should` / `nit` severity, and don't push to the author's branch.
|
|
448
|
+
|
|
449
|
+
## Reference
|
|
450
|
+
|
|
451
|
+
- **[references/cart-operation-events.md](references/cart-operation-events.md)** — the complete catalogs: every mutation's success/failure behavior, the full `detail` field list, the full `configureCartFeedback` config keys, every `data-fluid-toast-*` script attribute, and the toast styling hooks. Read it when you need an exhaustive lookup rather than the common-case guidance above.
|
|
452
|
+
- **[themes-review FairShare attributes](../themes-review/references/fairshare-attributes.md)** — the declarative `data-fluid-*` attributes that *trigger* cart mutations, and the CDN script contract. The trigger half of this skill's result half.
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Cart operation events & feedback — full reference
|
|
2
|
+
|
|
3
|
+
> Part of the `themes-cart-feedback` skill. See [`../SKILL.md`](../SKILL.md) for the authoring workflow, decision table, and worked examples. This file is the exhaustive lookup: mutation behavior, the complete `detail` payload, every config key and script attribute, and the toast styling hooks.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- The two events
|
|
8
|
+
- Mutations that emit events (success/failure behavior)
|
|
9
|
+
- Event `detail` — complete field list
|
|
10
|
+
- `configureCartFeedback()` — complete config keys
|
|
11
|
+
- Script-tag `data-fluid-*` attributes — complete list
|
|
12
|
+
- Precedence & merging
|
|
13
|
+
- Toast styling hooks
|
|
14
|
+
- Button-loading APIs
|
|
15
|
+
- Per-call toast suppression
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## The two events
|
|
20
|
+
|
|
21
|
+
The SDK dispatches two uniform `window` `CustomEvent`s for **all** cart mutations:
|
|
22
|
+
|
|
23
|
+
- **`CART_OPERATION_SUCCESS`** — fires when a mutation completes successfully.
|
|
24
|
+
- **`CART_OPERATION_ERROR`** — fires when a mutation fails.
|
|
25
|
+
|
|
26
|
+
Both fire **regardless of toast configuration** — enabling or disabling the built-in toast has no effect on the events. Names are exact, uppercase, underscore-separated; any other casing binds a listener that never runs.
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
window.addEventListener("CART_OPERATION_SUCCESS", (e) => {
|
|
30
|
+
console.log(e.detail.operation, "succeeded", e.detail.cart);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
window.addEventListener("CART_OPERATION_ERROR", (e) => {
|
|
34
|
+
console.log(e.detail.operation, "failed:", e.detail.message);
|
|
35
|
+
});
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Mutations that emit events
|
|
39
|
+
|
|
40
|
+
Every cart mutation dispatches one of the two events. The **failure column matters**: two mutations swallow the failure (resolve `undefined`) for backward compatibility, so a `try/catch` around them sees nothing — the error surfaces **only** through `CART_OPERATION_ERROR`. The other four rethrow, so you can `try/catch` *or* use the event.
|
|
41
|
+
|
|
42
|
+
| Function | Category | On success | On failure |
|
|
43
|
+
| ----------------------- | -------- | ------------------------ | ----------------------------------------------------- |
|
|
44
|
+
| `addCartItems` | add | `CART_OPERATION_SUCCESS` | `CART_OPERATION_ERROR` — **resolves `undefined`** (swallowed) |
|
|
45
|
+
| `addEnrollmentPack` | add | `CART_OPERATION_SUCCESS` | `CART_OPERATION_ERROR` — **rethrows** |
|
|
46
|
+
| `updateCartItems` | update | `CART_OPERATION_SUCCESS` | `CART_OPERATION_ERROR` — **rethrows** |
|
|
47
|
+
| `decrementCartItem` | update | `CART_OPERATION_SUCCESS` | `CART_OPERATION_ERROR` — **resolves `undefined`** (swallowed) |
|
|
48
|
+
| `updateCartItemVariant` | update | `CART_OPERATION_SUCCESS` | `CART_OPERATION_ERROR` — **rethrows** |
|
|
49
|
+
| `removeCartItemById` | remove | `CART_OPERATION_SUCCESS` | `CART_OPERATION_ERROR` — **rethrows** |
|
|
50
|
+
|
|
51
|
+
**Rule:** to report a cart failure to the shopper, listen for `CART_OPERATION_ERROR`. It covers all six functions; a `try/catch` covers only the four that rethrow.
|
|
52
|
+
|
|
53
|
+
## Event `detail` — complete field list
|
|
54
|
+
|
|
55
|
+
Both events expose an identical `detail` object structure. Fields marked "when known" are present only when the SDK has that value for the operation.
|
|
56
|
+
|
|
57
|
+
| Field | Type | Presence | Purpose |
|
|
58
|
+
| ------------------ | ------------------------------- | ------------ | -------------------------------------------------- |
|
|
59
|
+
| `operation` | `string` | always | Function name, e.g. `"addCartItems"`. |
|
|
60
|
+
| `category` | `"add" \| "update" \| "remove"` | always | Coarse operation bucket. |
|
|
61
|
+
| `toast` | `boolean` | always | `false` if the call passed `{ toast: false }`. |
|
|
62
|
+
| `timestamp` | `string` | always | ISO 8601 timestamp. |
|
|
63
|
+
| `cart` | `Cart` | success only | The resulting cart object. |
|
|
64
|
+
| `error` | `unknown` | error only | The raw thrown value. |
|
|
65
|
+
| `message` | `string` | error only | Best-effort human-readable error text. |
|
|
66
|
+
| `variantIds` | `number[]` | when known | Variant ids affected. |
|
|
67
|
+
| `itemId` | `number` | when known | Cart item id (item-scoped ops). |
|
|
68
|
+
| `enrollmentPackId` | `number` | when known | Enrollment pack id (`addEnrollmentPack`). |
|
|
69
|
+
|
|
70
|
+
> Prefer `window.FairShareSDK.getCartItemCount()` over reading a count out of `e.detail.cart` — the count getter is a stable, documented API, whereas the `cart` object's shape is not enumerated here. Read `e.detail.cart` fields only after confirming them in a running theme.
|
|
71
|
+
|
|
72
|
+
## `configureCartFeedback()` — complete config keys
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
window.addEventListener("DOMContentLoaded", () => {
|
|
76
|
+
window.FairShareSDK?.configureCartFeedback({
|
|
77
|
+
toast: true,
|
|
78
|
+
buttonLoading: true,
|
|
79
|
+
position: "bottom-right",
|
|
80
|
+
class: "theme-toast",
|
|
81
|
+
duration: 5000,
|
|
82
|
+
icon: true,
|
|
83
|
+
successIcon: "✓",
|
|
84
|
+
errorIcon: "⚠",
|
|
85
|
+
closeIcon: "✕",
|
|
86
|
+
messages: {
|
|
87
|
+
add: "Added! 🎉",
|
|
88
|
+
update: "Cart updated",
|
|
89
|
+
remove: "Removed from cart",
|
|
90
|
+
error: "Couldn't add that — try again.",
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
All keys optional.
|
|
97
|
+
|
|
98
|
+
| Key | Type | Default | Notes |
|
|
99
|
+
| --------------- | --------- | ---------------- | -------------------------------------------------------------------------------------------------- |
|
|
100
|
+
| `toast` | boolean | `false` | Enable the built-in toast. |
|
|
101
|
+
| `buttonLoading` | boolean | `false` | Auto-spinner on `data-fluid-add-to-cart` / `data-fluid-add-enrollment-pack` buttons. |
|
|
102
|
+
| `position` | string | `bottom-center` | `bottom-center` \| `bottom-left` \| `bottom-right` \| `top-center` \| `top-left` \| `top-right`. |
|
|
103
|
+
| `class` | string | — | Extra CSS class on the toast element. |
|
|
104
|
+
| `duration` | number | `4000` | Auto-dismiss delay (ms). |
|
|
105
|
+
| `icon` | boolean | `true` | Show the variant icon. |
|
|
106
|
+
| `successIcon` | string | built-in | Emoji, text, or SVG markup for the success variant. |
|
|
107
|
+
| `errorIcon` | string | built-in | Emoji, text, or SVG markup for the error variant. |
|
|
108
|
+
| `closeIcon` | string | `✕` | Close-button glyph. |
|
|
109
|
+
| `messages` | object | English defaults | `{ add, update, remove, error }`. Default: "Added to cart" / "Cart updated" / "Removed from cart" / "Something went wrong. Please try again." |
|
|
110
|
+
|
|
111
|
+
## Script-tag `data-fluid-*` attributes — complete list
|
|
112
|
+
|
|
113
|
+
Set on the `<script id="fluid-cdn-script">` tag. Use these when no localization is needed; otherwise prefer `configureCartFeedback()` so Liquid can inject translated strings.
|
|
114
|
+
|
|
115
|
+
| Attribute | Maps to | Notes |
|
|
116
|
+
| -------------------------------- | -------------------------- | -------------------------------------------------------- |
|
|
117
|
+
| `data-fluid-toast` | `toast` | `"true"` to enable. |
|
|
118
|
+
| `data-fluid-button-loading` | `buttonLoading` | `"true"` to enable. |
|
|
119
|
+
| `data-fluid-toast-class` | `class` | Custom class name. |
|
|
120
|
+
| `data-fluid-toast-position` | `position` | Same six values as `position` (default `bottom-center`). |
|
|
121
|
+
| `data-fluid-toast-duration` | `duration` | Milliseconds (default `4000`). |
|
|
122
|
+
| `data-fluid-toast-icon` | `icon` | `"false"` to hide the icon. |
|
|
123
|
+
| `data-fluid-toast-success-icon` | `successIcon` | Custom success icon. |
|
|
124
|
+
| `data-fluid-toast-error-icon` | `errorIcon` | Custom error icon. |
|
|
125
|
+
| `data-fluid-toast-close-icon` | `closeIcon` | Custom close glyph (default `✕`). |
|
|
126
|
+
| `data-fluid-toast-msg-add` | `messages.add` | Default "Added to cart". |
|
|
127
|
+
| `data-fluid-toast-msg-update` | `messages.update` | Default "Cart updated". |
|
|
128
|
+
| `data-fluid-toast-msg-remove` | `messages.remove` | Default "Removed from cart". |
|
|
129
|
+
| `data-fluid-toast-msg-error` | `messages.error` | Default "Something went wrong. Please try again." |
|
|
130
|
+
|
|
131
|
+
Example:
|
|
132
|
+
|
|
133
|
+
```html
|
|
134
|
+
<script
|
|
135
|
+
id="fluid-cdn-script"
|
|
136
|
+
src="https://assets.fluid.app/scripts/fluid-sdk/latest/web-widgets/index.js"
|
|
137
|
+
data-fluid-shop="your-shop-id"
|
|
138
|
+
data-fluid-toast="true"
|
|
139
|
+
data-fluid-toast-position="bottom-right"
|
|
140
|
+
data-fluid-toast-duration="4000"
|
|
141
|
+
data-fluid-toast-msg-add="Added! 🎉"
|
|
142
|
+
defer
|
|
143
|
+
></script>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Precedence & merging
|
|
147
|
+
|
|
148
|
+
When both are present, they merge per key: **`configureCartFeedback()` call > `data-fluid-*` attributes > defaults.** So you can set the base config on the script tag and override just the localized strings at runtime.
|
|
149
|
+
|
|
150
|
+
## Toast styling hooks
|
|
151
|
+
|
|
152
|
+
The toast renders in **light DOM** (no shadow root), so plain theme CSS reaches it. It carries:
|
|
153
|
+
|
|
154
|
+
- `id="fluid-toast"` — the always-present selector hook.
|
|
155
|
+
- any `class` you passed via `class:` / `data-fluid-toast-class`.
|
|
156
|
+
- `data-variant` — `"success"` or `"error"`.
|
|
157
|
+
- `data-position` — the configured position value.
|
|
158
|
+
- `data-state` — `"open"` or `"closed"`.
|
|
159
|
+
|
|
160
|
+
```css
|
|
161
|
+
#fluid-toast {
|
|
162
|
+
border-radius: var(--border_radius, 8px);
|
|
163
|
+
font-family: var(--font_family);
|
|
164
|
+
box-shadow: 0 8px 24px rgb(0 0 0 / 0.15);
|
|
165
|
+
}
|
|
166
|
+
#fluid-toast[data-variant="success"] { background: var(--primary_color, #0f9d58); color: #fff; }
|
|
167
|
+
#fluid-toast[data-variant="error"] { background: #d93025; color: #fff; }
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Behavior:** success toasts are suppressed while the cart drawer is open (the drawer already reflects the change); error toasts always display.
|
|
171
|
+
|
|
172
|
+
## Button-loading APIs
|
|
173
|
+
|
|
174
|
+
| API | Behavior |
|
|
175
|
+
| -------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
176
|
+
| `data-fluid-button-loading="true"` (script attr) / `buttonLoading: true` (config) | Global opt-in. Buttons with `data-fluid-add-to-cart` or `data-fluid-add-enrollment-pack` auto-spin. |
|
|
177
|
+
| `data-fluid-loading-text` | Per-button label shown while loading. |
|
|
178
|
+
| `withButtonLoading(el, fn)`| Runs `fn` with the spinner shown; clears it in a `finally`. **Preferred** for custom mutations. |
|
|
179
|
+
| `setButtonLoading(el, on)` | Manual toggle; idempotent; sets `aria-busy`. Clear it yourself in a `finally`. |
|
|
180
|
+
|
|
181
|
+
The spinner inherits the button's text color, so it needs no per-theme CSS.
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
// Automated cleanup (preferred)
|
|
185
|
+
button.addEventListener("click", (e) => {
|
|
186
|
+
window.FairShareSDK?.withButtonLoading(e.currentTarget, () =>
|
|
187
|
+
window.FairShareSDK.addCartItems(11111, { quantity: 1 }),
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Manual control
|
|
192
|
+
button.addEventListener("click", async (e) => {
|
|
193
|
+
const btn = e.currentTarget;
|
|
194
|
+
window.FairShareSDK?.setButtonLoading(btn, true);
|
|
195
|
+
try {
|
|
196
|
+
await window.FairShareSDK?.addCartItems(11111, { quantity: 1 });
|
|
197
|
+
} finally {
|
|
198
|
+
window.FairShareSDK?.setButtonLoading(btn, false);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Per-call toast suppression
|
|
204
|
+
|
|
205
|
+
Pass `{ toast: false }` to a single mutation to suppress just that toast. The event **still fires** (with `detail.toast === false`); only the toast is skipped.
|
|
206
|
+
|
|
207
|
+
```javascript
|
|
208
|
+
await window.FairShareSDK?.addCartItems(11111, { quantity: 1, toast: false });
|
|
209
|
+
```
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
|
|
16
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
17
|
|
|
18
|
+
> These attributes **trigger** cart mutations. The **result** — confirming the change with a toast, spinning the button, or reacting in custom UI via the `CART_OPERATION_SUCCESS` / `CART_OPERATION_ERROR` events — is the `themes-cart-feedback` skill (installed as a sibling: [`../../themes-cart-feedback/SKILL.md`](../../themes-cart-feedback/SKILL.md), when present). Reach for it when reviewing product cards, shop grids, cart sections, or enrollment flows that add to cart.
|
|
19
|
+
|
|
18
20
|
**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
21
|
|
|
20
22
|
### Naming convention
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluid-app/fluid-cli-theme-dev",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"description": "Fluid CLI plugin for theme developer workflows — dev server, push, pull, init",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"jest": "^29.7.0",
|
|
39
39
|
"tsdown": "^0.21.0",
|
|
40
40
|
"typescript": "^5",
|
|
41
|
-
"@fluid-app/theme-schema": "0.1.0",
|
|
42
|
-
"@fluid-app/themes-api-client": "0.1.0",
|
|
43
41
|
"@fluid-app/typescript-config": "0.0.0",
|
|
42
|
+
"@fluid-app/themes-api-client": "0.1.0",
|
|
43
|
+
"@fluid-app/theme-schema": "0.1.0",
|
|
44
44
|
"@fluid-app/api-client-core": "0.1.0"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|