@fluid-app/fluid-cli-theme-dev 0.1.26 → 0.1.27
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/dist/skills/themes-review/SKILL.md +2 -0
- package/dist/skills/themes-review/references/blocks-vs-sections.md +1 -3
- package/dist/skills/themes-review/references/dynamism.md +2 -3
- package/dist/skills/themes-review/references/media-tag.md +67 -0
- package/dist/skills/themes-review/references/security-accessibility.md +9 -7
- package/dist/skills/themes-review/references/setting-types.md +2 -0
- package/package.json +4 -4
|
@@ -331,6 +331,7 @@ touches them. Read the matching file when you hit its topic:
|
|
|
331
331
|
- **[Editor attributes](references/editor-attributes.md)** — `section.fluid_attributes` / `block.fluid_attributes`.
|
|
332
332
|
- **[FairShare attributes](references/fairshare-attributes.md)** — `data-fluid-*` cart / add-to-cart behavioral attributes + the CDN script.
|
|
333
333
|
- **[Performance](references/performance.md)** — `asset_url` in loops, render hygiene.
|
|
334
|
+
- **[Media rendering](references/media-tag.md)** — the `media_tag` filter for responsive, format-negotiated images and video; when raw `<img>`/`<video>` is wrong.
|
|
334
335
|
- **[Security & accessibility](references/security-accessibility.md)** — escaping user content, alt text, semantic clickables.
|
|
335
336
|
- **[Dead code](references/dead-code.md)** — unused sections/components/assets, unhandled block types.
|
|
336
337
|
- **[CSS / JS hygiene](references/css-js-hygiene.md)** — co-located stylesheet deprecation, inline `<style>`/`<script>` thresholds, `defer`.
|
|
@@ -619,6 +620,7 @@ When reviewing any theme file, run through this in order. Anything unchecked is
|
|
|
619
620
|
- [ ] No user-visible literal text in markup (use `text`/`textarea`/`richtext`)
|
|
620
621
|
- [ ] No hardcoded colors in styles (use `color` / `color_background` or a global token)
|
|
621
622
|
- [ ] No hardcoded image URLs (use `image_picker` or upload to `assets/`)
|
|
623
|
+
- [ ] Setting/resource images and videos render through `| media_tag` (not hand-rolled `<img>`/`<video>`)
|
|
622
624
|
- [ ] No external CDN URLs for theme-owned assets
|
|
623
625
|
- [ ] No hardcoded `href` to fixed paths the company might want to change (use `url`)
|
|
624
626
|
|
|
@@ -103,9 +103,7 @@ Rendering:
|
|
|
103
103
|
<div class="features features--cols-{{ section.settings.columns }}">
|
|
104
104
|
{%- for block in section.blocks -%}
|
|
105
105
|
<article class="feature" {{ block.fluid_attributes }}>
|
|
106
|
-
{
|
|
107
|
-
<img src="{{ block.settings.icon }}" alt="{{ block.settings.title | escape }}" />
|
|
108
|
-
{%- endif -%}
|
|
106
|
+
{{ block.settings.icon | media_tag: alt: block.settings.title }}
|
|
109
107
|
<h3>{{ block.settings.title }}</h3>
|
|
110
108
|
<p>{{ block.settings.body }}</p>
|
|
111
109
|
</article>
|
|
@@ -54,9 +54,7 @@ Every one of those values — background, padding, heading text, body text, imag
|
|
|
54
54
|
<section style="background:{{ s.bg_color }};padding:{{ s.section_pad }};">
|
|
55
55
|
<h2 style="color:{{ s.text_color }};">{{ s.heading | default: 'Our Bestsellers' }}</h2>
|
|
56
56
|
<p>{{ s.body }}</p>
|
|
57
|
-
{
|
|
58
|
-
<img src="{{ s.banner_image }}" alt="{{ s.heading | escape }}" />
|
|
59
|
-
{%- endif -%}
|
|
57
|
+
{{ s.banner_image | media_tag: alt: s.heading }}
|
|
60
58
|
{%- if s.show_cta -%}
|
|
61
59
|
<a href="{{ s.cta_url }}" class="btn">{{ s.cta_label }}</a>
|
|
62
60
|
{%- endif -%}
|
|
@@ -70,6 +68,7 @@ Every one of those values — background, padding, heading text, body text, imag
|
|
|
70
68
|
| User-visible literal text in markup (heading, paragraph, button label) | `should` | Should be a `text` / `textarea` / `richtext` setting. |
|
|
71
69
|
| Hardcoded colors (`#0a0a0a`, `rgb(...)`, `red`) in style/attribute | `should` | Should be a `color` or `color_background` setting. |
|
|
72
70
|
| Hardcoded image URL (external CDN, hardcoded path) | `blocker` if external CDN, `should` if internal | External CDNs can break, get blocked, or change. Upload to `assets/` or expose as `image_picker`. |
|
|
71
|
+
| Setting/resource image rendered with a raw `<img>` instead of `\| media_tag` | `should` | Misses responsive `srcset` + format negotiation. See [Media rendering](media-tag.md). |
|
|
73
72
|
| Hardcoded `href` to a fixed page or `?sort=popular`-style URL | `should` | Use a `url` setting. |
|
|
74
73
|
| Hardcoded counts (`limit: 6`, `limit: 12`) in `for` loops | `nit` → `should` | Expose as a `range` setting if the company might want to change density. |
|
|
75
74
|
| Boolean branches with no user toggle (`{% if true %}`) | `should` | Either delete or expose as a `checkbox`. |
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Media rendering — `media_tag`
|
|
2
|
+
|
|
3
|
+
> Part of the `themes-review` skill. See [`../SKILL.md`](../SKILL.md) for the review workflow, severity ladder, and validator rules.
|
|
4
|
+
|
|
5
|
+
Render every setting- or resource-backed image and video with the `media_tag` filter. It turns an image or video into a complete, responsive HTML element — `srcset`, WebP/AVIF negotiation, `loading`, `decoding="async"`, and `alt` — so a hand-rolled `<img>` / `<video>` in a section is almost always the wrong call.
|
|
6
|
+
|
|
7
|
+
Docs: <https://docs.fluid.app/docs/themes/media-tag>
|
|
8
|
+
|
|
9
|
+
## Contents
|
|
10
|
+
|
|
11
|
+
- 1. The rule
|
|
12
|
+
- 2. Options
|
|
13
|
+
- 3. Video
|
|
14
|
+
- 4. What it does *not* replace
|
|
15
|
+
|
|
16
|
+
### 1. The rule
|
|
17
|
+
|
|
18
|
+
**`should`.** A raw `<img src="{{ ... }}">` or `<video>` that renders an `image` / `image_picker` / `video_picker` / `media_picker` setting, or a resource image (`product.image`, `collection.image`, …), should be `| media_tag`. Hand-rolled markup ships one fixed-size original — no responsive `srcset`, no format negotiation — which bloats the download and hurts LCP, and forces you to remember `loading` / `decoding` / `alt` every time.
|
|
19
|
+
|
|
20
|
+
```liquid
|
|
21
|
+
{%- comment -%} Bad: fixed original, manual (easy-to-forget) attributes {%- endcomment -%}
|
|
22
|
+
<img src="{{ section.settings.banner_image }}" alt="{{ section.settings.heading | escape }}" loading="lazy" decoding="async" />
|
|
23
|
+
|
|
24
|
+
{%- comment -%} Good: responsive, format-negotiated, accessible — one filter {%- endcomment -%}
|
|
25
|
+
{{ section.settings.banner_image | media_tag: sizes: '(max-width: 768px) 100vw, 1200px', alt: section.settings.heading }}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`media_tag` escapes its own output, so do **not** wrap the setting in `| escape` yourself. It renders nothing on blank input, so the `{%- if image != blank -%}` guard is optional — keep it only when surrounding markup must be suppressed too.
|
|
29
|
+
|
|
30
|
+
### 2. Options
|
|
31
|
+
|
|
32
|
+
Pass options as filter arguments. Anything not in this table becomes a plain HTML attribute (`class`, `id`, `data-*`).
|
|
33
|
+
|
|
34
|
+
| Option | Default | Purpose |
|
|
35
|
+
| --------- | ------------------- | -------------------------------------------------------------------------- |
|
|
36
|
+
| `sizes` | `100vw` | Browser sizing hint — set it to the real rendered width. |
|
|
37
|
+
| `widths` | `400,800,1200,1600` | `srcset` breakpoints. |
|
|
38
|
+
| `width` | — | Fixed display width; emits a 2× retina `srcset`. |
|
|
39
|
+
| `height` | — | Layout stability (avoids CLS). |
|
|
40
|
+
| `quality` | `80` | 1–100. |
|
|
41
|
+
| `format` | `auto` | WebP/AVIF negotiation. |
|
|
42
|
+
| `crop` | — | Focus point. |
|
|
43
|
+
| `loading` | `lazy` | Use `eager` for the hero / above-the-fold image — `lazy` there delays LCP. |
|
|
44
|
+
| `alt` | image's alt text | Set it for `*_picker` settings; `alt: ''` for decorative. |
|
|
45
|
+
|
|
46
|
+
```liquid
|
|
47
|
+
{%- comment -%} Fixed-width logo, retina srcset {%- endcomment -%}
|
|
48
|
+
{{ section.settings.logo | media_tag: width: 240, alt: 'Logo' }}
|
|
49
|
+
|
|
50
|
+
{%- comment -%} Hero — eager so it doesn't delay LCP {%- endcomment -%}
|
|
51
|
+
{{ section.settings.hero | media_tag: sizes: '100vw', loading: 'eager', alt: section.settings.heading }}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 3. Video
|
|
55
|
+
|
|
56
|
+
A video URL (`.mp4` / `.mov` / `.webm` / `.m4v`) or video object renders a `<video>`. Options: `autoplay`, `loop`, `muted`, `controls`, `poster`, `format` (default `mp4`), `quality`.
|
|
57
|
+
|
|
58
|
+
```liquid
|
|
59
|
+
{{ section.settings.bg_video | media_tag: autoplay: true, loop: true, muted: true }}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 4. What it does *not* replace
|
|
63
|
+
|
|
64
|
+
- **Static theme assets** (`'logo.svg' | asset_url`) are not ImageKit media — `media_tag` passes non-ImageKit URLs through as a plain tag, so `asset_url` + a plain `<img>` stays correct for files shipped in `assets/`.
|
|
65
|
+
- **`alt` judgement** is still yours — `media_tag` defaults `alt` from the image, but `*_picker` settings carry none, so pass one (or `alt: ''` for decorative). The alt rule in [security & accessibility](security-accessibility.md) still applies.
|
|
66
|
+
|
|
67
|
+
---
|
|
@@ -41,15 +41,17 @@
|
|
|
41
41
|
|
|
42
42
|
### 1. Missing `alt` on images
|
|
43
43
|
|
|
44
|
+
Render the image with `| media_tag` (see [Media rendering](media-tag.md)) and pass `alt`:
|
|
45
|
+
|
|
44
46
|
```liquid
|
|
45
|
-
{%- comment -%} Bad {%- endcomment -%}
|
|
46
|
-
|
|
47
|
+
{%- comment -%} Bad: image_picker carries no stored alt, so this ships alt-less {%- endcomment -%}
|
|
48
|
+
{{ section.settings.image | media_tag }}
|
|
47
49
|
|
|
48
50
|
{%- comment -%} Good {%- endcomment -%}
|
|
49
|
-
|
|
51
|
+
{{ section.settings.image | media_tag: alt: section.settings.heading }}
|
|
50
52
|
```
|
|
51
53
|
|
|
52
|
-
Decorative images: `alt
|
|
54
|
+
`media_tag` defaults `alt` from a resource image (`product.featured_image`), but `*_picker` settings carry none — pass one explicitly. Decorative images: `alt: ''`.
|
|
53
55
|
|
|
54
56
|
### 2. Heading hierarchy
|
|
55
57
|
|
|
@@ -61,10 +63,10 @@ A clickable `<div>` is `blocker`. Use `<button>` for actions, `<a href>` for nav
|
|
|
61
63
|
|
|
62
64
|
### 4. `decoding="async"` and `loading="lazy"`
|
|
63
65
|
|
|
66
|
+
`| media_tag` applies `decoding="async"` and `loading="lazy"` for you — don't hand-roll them. For hero/above-the-fold images pass `loading: 'eager'`, since lazy loading there delays LCP:
|
|
67
|
+
|
|
64
68
|
```liquid
|
|
65
|
-
|
|
69
|
+
{{ section.settings.hero | media_tag: loading: 'eager', alt: section.settings.heading }}
|
|
66
70
|
```
|
|
67
71
|
|
|
68
|
-
Don't set `loading="lazy"` on hero/above-the-fold images — it delays LCP.
|
|
69
|
-
|
|
70
72
|
---
|
|
@@ -41,6 +41,8 @@ These are the canonical setting types. **Any `type:` value not in this list is r
|
|
|
41
41
|
|
|
42
42
|
### Visual & media
|
|
43
43
|
|
|
44
|
+
Render `image` / `image_picker` / `video_picker` / `media_picker` values with the `media_tag` filter, not a hand-rolled `<img>`/`<video>` — see [Media rendering](media-tag.md).
|
|
45
|
+
|
|
44
46
|
| `type` | Notes |
|
|
45
47
|
| ------------------ | ----------------------------------------------------------------- |
|
|
46
48
|
| `color` | Single color picker. |
|
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.27",
|
|
4
4
|
"description": "Fluid CLI plugin for theme developer workflows — dev server, push, pull, init",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
"jest": "^29.7.0",
|
|
39
39
|
"tsdown": "^0.21.0",
|
|
40
40
|
"typescript": "^5",
|
|
41
|
-
"@fluid-app/api-client
|
|
41
|
+
"@fluid-app/themes-api-client": "0.1.0",
|
|
42
42
|
"@fluid-app/typescript-config": "0.0.0",
|
|
43
|
-
"@fluid-app/
|
|
44
|
-
"@fluid-app/
|
|
43
|
+
"@fluid-app/api-client-core": "0.1.0",
|
|
44
|
+
"@fluid-app/theme-schema": "0.1.0"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
|
47
47
|
"node": ">=18.0.0"
|