@bettercms-ai/mcp 0.83.0 → 0.83.1
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/README.md +86 -86
- package/SKILL.md +160 -160
- package/dist/index.js +35 -7
- package/dist/index.js.map +1 -1
- package/dist/kit-catalog.json +1509 -1509
- package/dist/skills/bettercms/SKILL.md +206 -206
- package/dist/skills/bettercms/references/accessibility.md +171 -171
- package/dist/skills/bettercms/references/animation.md +296 -296
- package/dist/skills/bettercms/references/astro.md +148 -148
- package/dist/skills/bettercms/references/bindings.md +366 -366
- package/dist/skills/bettercms/references/componentize.md +185 -185
- package/dist/skills/bettercms/references/components.md +415 -415
- package/dist/skills/bettercms/references/content-experiments.md +62 -62
- package/dist/skills/bettercms/references/content-modeling.md +382 -382
- package/dist/skills/bettercms/references/detection.md +465 -465
- package/dist/skills/bettercms/references/dock-input.md +121 -121
- package/dist/skills/bettercms/references/editor-chrome.md +92 -92
- package/dist/skills/bettercms/references/external-content.md +78 -78
- package/dist/skills/bettercms/references/finish-site.md +377 -377
- package/dist/skills/bettercms/references/forms-and-interactions.md +245 -245
- package/dist/skills/bettercms/references/frameworks.md +159 -159
- package/dist/skills/bettercms/references/getting-started.md +156 -156
- package/dist/skills/bettercms/references/integrations.md +364 -364
- package/dist/skills/bettercms/references/kit-catalog.md +3259 -3259
- package/dist/skills/bettercms/references/kit-scenarios.md +712 -712
- package/dist/skills/bettercms/references/localization.md +85 -85
- package/dist/skills/bettercms/references/media-and-brand.md +181 -181
- package/dist/skills/bettercms/references/next.md +189 -189
- package/dist/skills/bettercms/references/other-frameworks.md +85 -85
- package/dist/skills/bettercms/references/portable-text.md +213 -213
- package/dist/skills/bettercms/references/programmatic-seo.md +98 -98
- package/dist/skills/bettercms/references/publishing.md +289 -289
- package/dist/skills/bettercms/references/seo.md +357 -357
- package/dist/skills/bettercms/references/shopify-headless.md +52 -52
- package/dist/skills/bettercms/references/site-blueprints.md +895 -895
- package/dist/skills/bettercms/references/site-review.md +74 -74
- package/dist/skills/bettercms/references/worked-examples-advanced.md +509 -509
- package/dist/skills/bettercms/references/worked-examples.md +885 -885
- package/dist/skills/bettercms/references/write-limits.md +217 -217
- package/package.json +1 -1
|
@@ -1,85 +1,85 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Localization"
|
|
3
|
-
description: "Document-per-locale translations (locale, translationOf, translate_entry, delivery translations), URL-prefixed locale copies of imported builds, Layout locale variants, locale-unvarianted, hreflang, and what never to model."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Localization
|
|
7
|
-
|
|
8
|
-
**Outcome.** A multilingual site keeps every language rendering, each language's copy is
|
|
9
|
-
editable, and nothing invents a locale model the platform does not read.
|
|
10
|
-
|
|
11
|
-
Two things are called a "language" here, and they are separate:
|
|
12
|
-
|
|
13
|
-
- **A translation** is its own entry or page row, one per locale: `locale` is its BCP-47 tag (one
|
|
14
|
-
of the project's locales; null = the default language) and `translationOf` is the SOURCE row's
|
|
15
|
-
id. Each has its own slug, draft and publish state.
|
|
16
|
-
- **A locale copy of an imported build** is a URL prefix (`/fr`, `/pt-br`) proven by the page's own
|
|
17
|
-
`<html lang>`. It is how a converted site's languages arrive, before anyone models them.
|
|
18
|
-
|
|
19
|
-
## 1. What exists
|
|
20
|
-
|
|
21
|
-
| Piece | How it works |
|
|
22
|
-
|---|---|
|
|
23
|
-
| The project's locales | `localeConfig.locales`: the owner's list (project settings) plus every page language an import finds. A `locale` outside it is refused with 400; an empty list means a single-language project. Tags are stored canonical (`zh-CN` → `zh-cn`) |
|
|
24
|
-
| A page's language | `list_pages` / `get_page` return `language` (its `locale`, else the `<html lang>` its import declared; null = the default) and `path`. Set it with `create_page` / `update_page` `locale` |
|
|
25
|
-
| Address a page | By id or `path` (`/de/construction/safety`). A slug repeats across language folders, so a bare slug works only when one page has it; otherwise the tool refuses with each match's id, path and language. Never guess one |
|
|
26
|
-
| Translate an entry with AI | `translate_entry` { entryId, targetLocale, slug?, fields? } creates the translation as a DRAFT with `locale` and `translationOf` set (AI credits: 402/403 when out or not on the plan). Text and longtext are translated; rich text and documents are copied for you to translate; `meta` lists which. `409 locale_conflict`: that translation exists, so edit it instead |
|
|
27
|
-
| Create one by hand | `create_content_entry` or `create_page` with `locale` and `translationOf` = the source's id, never another translation's. One row per locale per source (a second is `409 locale_conflict`); `update_content_entry` / `update_page` change either, null clears |
|
|
28
|
-
| Find them | `list_content_entries` `where: { locale: 'fr' }`; default-language rows have a null `locale`, so they match `{ locale: { ne: 'fr' } }`, never `eq` |
|
|
29
|
-
| Deliver them | Every delivered entry carries `locale`, `translationOf` and `translations`: `[{ id, locale, slug }]` of its group's other PUBLISHED rows (drafts only when previewing). A delivered page carries `locale` and `translationOf` |
|
|
30
|
-
| Translate one string | `write_content` with `action: "translate"` and `targetLang` returns the text; apply it with `set_page_content` or `update_content_entry` |
|
|
31
|
-
| Locale copy of an imported page | A page whose route opens with a language prefix its own `<html lang>` confirms (`/fr` on a French page). The prefix is proven by the page's language, never by the segment's shape: `/de` is a locale only on a German page |
|
|
32
|
-
| Componentizing a locale copy | `get_componentize_plan` places `/fr` on its default-locale twin's components, band by band, when each pair holds the same prop types in the same order; a mismatched band stays its own section. `componentize_sections` `joinTwins` moves older copies onto their twin's components |
|
|
33
|
-
| Layout locale variants | A Global Layout Section can have a variant per locale (`navigation--fr`). A page renders the variant whose locale is its route's first segment, else the base |
|
|
34
|
-
| `locale-unvarianted` | A `get_binding_report` unmatched reason: the route resolved in a locale, the Section has no variant for it, and the base still renders there. Named, never written |
|
|
35
|
-
|
|
36
|
-
Import derives deeper locale routes (`/fr/about`) as pages in a URL folder (`fr`), each recording
|
|
37
|
-
the language its `<html lang>` declares, and the Content sidebar files them under Pages › <language>.
|
|
38
|
-
A locale prefix is never a collection, but a collection's localized posts (`/fr/blog/post-1`, its
|
|
39
|
-
`<html lang>` agreeing) are entries of that collection with `locale` set, `translationOf` the post
|
|
40
|
-
their hreflang alternate (else their slug) names, slug `<slug>-<locale>`, served at
|
|
41
|
-
`/<locale>/<collection>/<slug>`; the project's locales gain each language found. An edit to `/about` never reaches `/fr/about`; edit each page or entry.
|
|
42
|
-
|
|
43
|
-
## 2. Never model
|
|
44
|
-
|
|
45
|
-
- **No `language`, `locale` or `lang` field** on a model, page or component, and no
|
|
46
|
-
`translations` reference field between entries. The language is the native `locale`, the link
|
|
47
|
-
is the native `translationOf`, and delivery derives `translations` from them.
|
|
48
|
-
- **No hreflang, alternate-URL or canonical-per-language field.** SEO is native
|
|
49
|
-
([seo.md](seo.md)), and the native columns have no locale slot.
|
|
50
|
-
- **No per-language copy of a field** (`titleFr`, `titleEn`). It doubles every edit and leaves
|
|
51
|
-
the page's language implicit in a key name.
|
|
52
|
-
|
|
53
|
-
## 3. Where hreflang comes from
|
|
54
|
-
|
|
55
|
-
The frontend writes the `<head>` links. For each entry, render `<link rel="alternate" hreflang="…">`
|
|
56
|
-
for itself and for every row in its delivered `translations` (the locale is the tag, the slug
|
|
57
|
-
builds the URL on the site's own route pattern), plus `x-default` for the default-language row.
|
|
58
|
-
Hosted `<head>` output emits none.
|
|
59
|
-
|
|
60
|
-
The hosted sitemap does emit them. Every listed page or entry in a translation group (the source
|
|
61
|
-
plus its `translationOf` rows) carries `<xhtml:link rel="alternate" hreflang>` for each listed
|
|
62
|
-
sibling, itself included, plus `x-default` for the source. A row with no `locale` takes the
|
|
63
|
-
project's `localeConfig.defaultLanguage`; with neither, it gets no link of its own. Unpublished
|
|
64
|
-
and noindex siblings are not listed, so they get no link either. Check the live `sitemap.xml`.
|
|
65
|
-
|
|
66
|
-
## 4. Workflow
|
|
67
|
-
|
|
68
|
-
For a site modelled in the CMS:
|
|
69
|
-
|
|
70
|
-
1. Confirm the owner has set the project's locales; you cannot.
|
|
71
|
-
2. Per source entry, `translate_entry` into each locale (or create the row by hand), then
|
|
72
|
-
translate the copied rich text with `write_content` and write it with `update_content_entry`.
|
|
73
|
-
3. Review each draft with the user, publish it (`publish_entries` takes many), and check the
|
|
74
|
-
source's delivered `translations` lists it.
|
|
75
|
-
4. Emit hreflang from `translations` in the frontend (§3) and fetch each language's URL.
|
|
76
|
-
|
|
77
|
-
For a multilingual import:
|
|
78
|
-
|
|
79
|
-
1. List the prefixes the build serves and confirm each page's `<html lang>` matches its prefix.
|
|
80
|
-
2. Componentize the default language first; the plan then places each locale copy on its twin.
|
|
81
|
-
3. Read `get_binding_report` on each prefixed route: `locale-unvarianted` rows are the Layout
|
|
82
|
-
Sections still showing the default language there. `update_layout` takes no locale, so name
|
|
83
|
-
those Sections to the user rather than writing translated copy onto the base.
|
|
84
|
-
4. Translate with `write_content`, apply, publish, and fetch each prefixed URL.
|
|
85
|
-
5. Report the routes that stay build-only (the editor setup's `pages` note names any) with their count.
|
|
1
|
+
---
|
|
2
|
+
title: "Localization"
|
|
3
|
+
description: "Document-per-locale translations (locale, translationOf, translate_entry, delivery translations), URL-prefixed locale copies of imported builds, Layout locale variants, locale-unvarianted, hreflang, and what never to model."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Localization
|
|
7
|
+
|
|
8
|
+
**Outcome.** A multilingual site keeps every language rendering, each language's copy is
|
|
9
|
+
editable, and nothing invents a locale model the platform does not read.
|
|
10
|
+
|
|
11
|
+
Two things are called a "language" here, and they are separate:
|
|
12
|
+
|
|
13
|
+
- **A translation** is its own entry or page row, one per locale: `locale` is its BCP-47 tag (one
|
|
14
|
+
of the project's locales; null = the default language) and `translationOf` is the SOURCE row's
|
|
15
|
+
id. Each has its own slug, draft and publish state.
|
|
16
|
+
- **A locale copy of an imported build** is a URL prefix (`/fr`, `/pt-br`) proven by the page's own
|
|
17
|
+
`<html lang>`. It is how a converted site's languages arrive, before anyone models them.
|
|
18
|
+
|
|
19
|
+
## 1. What exists
|
|
20
|
+
|
|
21
|
+
| Piece | How it works |
|
|
22
|
+
|---|---|
|
|
23
|
+
| The project's locales | `localeConfig.locales`: the owner's list (project settings) plus every page language an import finds. A `locale` outside it is refused with 400; an empty list means a single-language project. Tags are stored canonical (`zh-CN` → `zh-cn`) |
|
|
24
|
+
| A page's language | `list_pages` / `get_page` return `language` (its `locale`, else the `<html lang>` its import declared; null = the default) and `path`. Set it with `create_page` / `update_page` `locale` |
|
|
25
|
+
| Address a page | By id or `path` (`/de/construction/safety`). A slug repeats across language folders, so a bare slug works only when one page has it; otherwise the tool refuses with each match's id, path and language. Never guess one |
|
|
26
|
+
| Translate an entry with AI | `translate_entry` { entryId, targetLocale, slug?, fields? } creates the translation as a DRAFT with `locale` and `translationOf` set (AI credits: 402/403 when out or not on the plan). Text and longtext are translated; rich text and documents are copied for you to translate; `meta` lists which. `409 locale_conflict`: that translation exists, so edit it instead |
|
|
27
|
+
| Create one by hand | `create_content_entry` or `create_page` with `locale` and `translationOf` = the source's id, never another translation's. One row per locale per source (a second is `409 locale_conflict`); `update_content_entry` / `update_page` change either, null clears |
|
|
28
|
+
| Find them | `list_content_entries` `where: { locale: 'fr' }`; default-language rows have a null `locale`, so they match `{ locale: { ne: 'fr' } }`, never `eq` |
|
|
29
|
+
| Deliver them | Every delivered entry carries `locale`, `translationOf` and `translations`: `[{ id, locale, slug }]` of its group's other PUBLISHED rows (drafts only when previewing). A delivered page carries `locale` and `translationOf` |
|
|
30
|
+
| Translate one string | `write_content` with `action: "translate"` and `targetLang` returns the text; apply it with `set_page_content` or `update_content_entry` |
|
|
31
|
+
| Locale copy of an imported page | A page whose route opens with a language prefix its own `<html lang>` confirms (`/fr` on a French page). The prefix is proven by the page's language, never by the segment's shape: `/de` is a locale only on a German page |
|
|
32
|
+
| Componentizing a locale copy | `get_componentize_plan` places `/fr` on its default-locale twin's components, band by band, when each pair holds the same prop types in the same order; a mismatched band stays its own section. `componentize_sections` `joinTwins` moves older copies onto their twin's components |
|
|
33
|
+
| Layout locale variants | A Global Layout Section can have a variant per locale (`navigation--fr`). A page renders the variant whose locale is its route's first segment, else the base |
|
|
34
|
+
| `locale-unvarianted` | A `get_binding_report` unmatched reason: the route resolved in a locale, the Section has no variant for it, and the base still renders there. Named, never written |
|
|
35
|
+
|
|
36
|
+
Import derives deeper locale routes (`/fr/about`) as pages in a URL folder (`fr`), each recording
|
|
37
|
+
the language its `<html lang>` declares, and the Content sidebar files them under Pages › <language>.
|
|
38
|
+
A locale prefix is never a collection, but a collection's localized posts (`/fr/blog/post-1`, its
|
|
39
|
+
`<html lang>` agreeing) are entries of that collection with `locale` set, `translationOf` the post
|
|
40
|
+
their hreflang alternate (else their slug) names, slug `<slug>-<locale>`, served at
|
|
41
|
+
`/<locale>/<collection>/<slug>`; the project's locales gain each language found. An edit to `/about` never reaches `/fr/about`; edit each page or entry.
|
|
42
|
+
|
|
43
|
+
## 2. Never model
|
|
44
|
+
|
|
45
|
+
- **No `language`, `locale` or `lang` field** on a model, page or component, and no
|
|
46
|
+
`translations` reference field between entries. The language is the native `locale`, the link
|
|
47
|
+
is the native `translationOf`, and delivery derives `translations` from them.
|
|
48
|
+
- **No hreflang, alternate-URL or canonical-per-language field.** SEO is native
|
|
49
|
+
([seo.md](seo.md)), and the native columns have no locale slot.
|
|
50
|
+
- **No per-language copy of a field** (`titleFr`, `titleEn`). It doubles every edit and leaves
|
|
51
|
+
the page's language implicit in a key name.
|
|
52
|
+
|
|
53
|
+
## 3. Where hreflang comes from
|
|
54
|
+
|
|
55
|
+
The frontend writes the `<head>` links. For each entry, render `<link rel="alternate" hreflang="…">`
|
|
56
|
+
for itself and for every row in its delivered `translations` (the locale is the tag, the slug
|
|
57
|
+
builds the URL on the site's own route pattern), plus `x-default` for the default-language row.
|
|
58
|
+
Hosted `<head>` output emits none.
|
|
59
|
+
|
|
60
|
+
The hosted sitemap does emit them. Every listed page or entry in a translation group (the source
|
|
61
|
+
plus its `translationOf` rows) carries `<xhtml:link rel="alternate" hreflang>` for each listed
|
|
62
|
+
sibling, itself included, plus `x-default` for the source. A row with no `locale` takes the
|
|
63
|
+
project's `localeConfig.defaultLanguage`; with neither, it gets no link of its own. Unpublished
|
|
64
|
+
and noindex siblings are not listed, so they get no link either. Check the live `sitemap.xml`.
|
|
65
|
+
|
|
66
|
+
## 4. Workflow
|
|
67
|
+
|
|
68
|
+
For a site modelled in the CMS:
|
|
69
|
+
|
|
70
|
+
1. Confirm the owner has set the project's locales; you cannot.
|
|
71
|
+
2. Per source entry, `translate_entry` into each locale (or create the row by hand), then
|
|
72
|
+
translate the copied rich text with `write_content` and write it with `update_content_entry`.
|
|
73
|
+
3. Review each draft with the user, publish it (`publish_entries` takes many), and check the
|
|
74
|
+
source's delivered `translations` lists it.
|
|
75
|
+
4. Emit hreflang from `translations` in the frontend (§3) and fetch each language's URL.
|
|
76
|
+
|
|
77
|
+
For a multilingual import:
|
|
78
|
+
|
|
79
|
+
1. List the prefixes the build serves and confirm each page's `<html lang>` matches its prefix.
|
|
80
|
+
2. Componentize the default language first; the plan then places each locale copy on its twin.
|
|
81
|
+
3. Read `get_binding_report` on each prefixed route: `locale-unvarianted` rows are the Layout
|
|
82
|
+
Sections still showing the default language there. `update_layout` takes no locale, so name
|
|
83
|
+
those Sections to the user rather than writing translated copy onto the base.
|
|
84
|
+
4. Translate with `write_content`, apply, publish, and fetch each prefixed URL.
|
|
85
|
+
5. Report the routes that stay build-only (the editor setup's `pages` note names any) with their count.
|
|
@@ -1,181 +1,181 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Brand kit and media"
|
|
3
|
-
description: "Brand tokens, fonts, logo kit, image sizing and focal point, SVG and video rules, and the media host."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Brand kit, section styles and media
|
|
7
|
-
|
|
8
|
-
**Outcome.** An imported site keeps its appearance, and editors change the intended brand and
|
|
9
|
-
media controls through one coherent system instead of scattered CSS values.
|
|
10
|
-
|
|
11
|
-
## Audit sources and the rendered design
|
|
12
|
-
|
|
13
|
-
Inspect CSS variables, the Tailwind or theme config, design-token files, `@font-face`, layout
|
|
14
|
-
components, media directories, responsive assets and computed styles on representative routes.
|
|
15
|
-
Compare desktop and mobile screenshots. Record where each proposed token came from and how often
|
|
16
|
-
it is used. One colour on one button is not a brand identity.
|
|
17
|
-
|
|
18
|
-
Build a token inventory: role, source value, proposed BetterCMS value, affected components,
|
|
19
|
-
confidence, approval status. Cover colours, type families, weights and scale, spacing, container
|
|
20
|
-
widths, radius, borders, elevation, motion and breakpoints. Group colours into semantic roles
|
|
21
|
-
(primary, accent, background, surface, text, muted, status colours). Keep raw source values as
|
|
22
|
-
evidence, never as every component's editable prop.
|
|
23
|
-
|
|
24
|
-
## What the brand kit is, and who writes it
|
|
25
|
-
|
|
26
|
-
- `get_project` returns `brandKit`: colours (`primary`, `accent`, `background`, `surface`,
|
|
27
|
-
`text`, `muted`), `typography` (`headingFont`, `bodyFont`), `radius` (`sharp`/`rounded`/`pill`),
|
|
28
|
-
and optional `typeScale`, `spacing`, `elevation`, `motion` and gradients. A project that never
|
|
29
|
-
saved one still returns the default kit.
|
|
30
|
-
- **Colours and typography are edited in the dashboard**; no MCP tool writes them. Prepare a
|
|
31
|
-
precise proposal and map the source CSS to it; never say you changed them.
|
|
32
|
-
- **The logo mark and favicon** are written with `set_brand_assets`, by the id of a media asset
|
|
33
|
-
of this project (upload it first). A slot a person chose is refused unless you pass
|
|
34
|
-
`replace: true`; ask before passing it.
|
|
35
|
-
- **Gradients** are written with `set_brand_graphics`: a kind, an optional angle and 2-8 stops
|
|
36
|
-
that name kit colour keys, never a hex or a CSS string.
|
|
37
|
-
- Every brand write is a version a person can restore in the dashboard.
|
|
38
|
-
- The hosted renderer emits the kit as `--bcms-*` and `--brand-*` CSS custom properties, which
|
|
39
|
-
reach hosted pages only. A self-hosted site owns its tokens: bind its own presentation
|
|
40
|
-
variables to the approved kit, and declare its presentation in `bcms-presentation.json` at the
|
|
41
|
-
root of the deployed artifact.
|
|
42
|
-
- `update_project` `presentation` themes the platform's own renderer with the same closed knobs
|
|
43
|
-
as a release's `bcms-presentation.json`. It does not restyle a deployed build.
|
|
44
|
-
|
|
45
|
-
## Section styles
|
|
46
|
-
|
|
47
|
-
Section style props are closed tokens, not free CSS: space is `none|xs|sm|md|lg|xl`, colour is
|
|
48
|
-
`none|white|black|muted|mutedBg|primary|accent`. Never put a hex value from the brand into block
|
|
49
|
-
props: a token follows a brand change, a copied hex does not. Style inspector properties:
|
|
50
|
-
`CM-27`. Expose a curated choice (for example
|
|
51
|
-
`Surface: Light / Dark / Brand`); never ask an editor for a hex, a class name or a font path.
|
|
52
|
-
Say whether a change is brand-wide or one instance, and test that a token change keeps contrast
|
|
53
|
-
and layout intact in every variant. Offer only controls the renderer actually applies; a missing
|
|
54
|
-
style binding is a code or platform task, not a dashboard promise.
|
|
55
|
-
|
|
56
|
-
## Typography and fonts
|
|
57
|
-
|
|
58
|
-
Inventory family stacks, weights, styles, variable axes, local files, remote providers and
|
|
59
|
-
where each is used. Match heading and body roles to the rendered code; do not swap a display
|
|
60
|
-
face for the body face because it appears less often. Keep fallbacks and the loading strategy.
|
|
61
|
-
Self-host a font file only when its licence allows it; otherwise keep the provider reference and
|
|
62
|
-
list the dependency. Record licence evidence or "licence unknown".
|
|
63
|
-
|
|
64
|
-
Keep semantic HTML apart from visual size: an H2 may use a display token without becoming an
|
|
65
|
-
H1. Expose typography variants only where the design allows a real choice.
|
|
66
|
-
|
|
67
|
-
## Logos, images, icons and video
|
|
68
|
-
|
|
69
|
-
- **Logo kit.** Roles: primary, reverse, dark, compact mark, wordmark, favicon, where the source
|
|
70
|
-
proves them; mark the rest unclassified. Keep vector originals and transparency. The navigation
|
|
71
|
-
logo is a stored image on the layout; replace it there ([components.md](components.md) §4),
|
|
72
|
-
not in each page.
|
|
73
|
-
- **Images.** A stored image value is an object (`url`, `altText`, asset id), never a bare
|
|
74
|
-
string; read it as one. Alt text, decorative state and caption belong to the usage. Keep
|
|
75
|
-
`srcset`/`sizes`, poster, lazy/preload intent and art direction (a publish keeps them too:
|
|
76
|
-
[bindings.md](bindings.md) Images). Decorative images render an
|
|
77
|
-
empty `alt`; informative ones need text that fits the context. `update_media` with
|
|
78
|
-
`decorative: true` marks a library asset decorative, so the audit stops asking for its alt text.
|
|
79
|
-
- **Request the size you render.** Mint resize URLs with `@bettercms-ai/image-url`
|
|
80
|
-
(`urlFor = imageUrlBuilder({ baseUrl: mediaUrl })`, then `urlFor(asset).size(800, 450).url()`;
|
|
81
|
-
also `width`, `format`, `quality`, `fit`, `dpr` 1–3) and a
|
|
82
|
-
`srcset` of the widths the layout actually uses. Why: the original upload is often several
|
|
83
|
-
thousand pixels wide, and an unresized `url` ships all of it.
|
|
84
|
-
- **Framing lives on the value, never in the model.** An asset carries one focal point (0–1
|
|
85
|
-
fractions), set with `update_media` `focalX`/`focalY` (null resets to the centre) or in the
|
|
86
|
-
dashboard; a URL with both dimensions and `fit` cover crops around it automatically. One
|
|
87
|
-
PLACEMENT can differ: the image value itself may carry `focal: { x, y }` and
|
|
88
|
-
`crop: { top, right, bottom, left }` (0–1, cut from each edge; the crop must leave a visible
|
|
89
|
-
window). The value's focal wins over the asset's; absent keys fall back to the asset. Draw a
|
|
90
|
-
value with `imageValueUrl(value, { w, h })` and `imageValueObjectPosition(value)` from
|
|
91
|
-
`@bettercms-ai/image-url`, never `.url` alone, or the crop is lost. The dashboard's asset Crop
|
|
92
|
-
rewrites the stored bytes for every usage. Never add focal or crop FIELDS to a model: they
|
|
93
|
-
belong on the image value.
|
|
94
|
-
- **Image block width.** An image block's `props.width` is pixels, and every lane caps it at
|
|
95
|
-
the column (`max-width:100%`, height auto). Leave it unset to fill the column.
|
|
96
|
-
- **Rich-text images.** A `builtin:image` block takes `align` (`full` | `center` | `left` |
|
|
97
|
-
`right`) and `size` (`small` | `medium` | `large` | `full`) in its fields. Both render on every
|
|
98
|
-
lane, with inline widths, so no site CSS is needed.
|
|
99
|
-
- **Image facts ride on the delivered value.** Delivery adds `width`, `height`, `thumbHash` (a
|
|
100
|
-
ThumbHash placeholder) and `focalX`/`focalY` to each image value, read at request time and
|
|
101
|
-
omitted while unknown (object values only: a bare URL string stays a string). Reserve the box from `width`/`height`, decode `thumbHash` for a blur-up,
|
|
102
|
-
and fall back to a tiny variant (`width(24)`) when it is absent.
|
|
103
|
-
- **Generate or edit an image.** `generate_image` { prompt, width?, height?, altText } adds an AI
|
|
104
|
-
image to the Media Library; `transform_image` { mediaId, prompt } saves an edited copy as a NEW
|
|
105
|
-
asset and keeps the original. Each costs 5 AI credits (free with the workspace's own Together
|
|
106
|
-
key). Always set `altText`, and use them only where the user wants generated imagery: never to
|
|
107
|
-
stand in for a real product, person or place.
|
|
108
|
-
- **SVG.** Every SVG upload is sanitised on the way in, and SVGs are served as vectors (no
|
|
109
|
-
raster variants). Keep safe icons as SVG and expose icon choice, size and colour token.
|
|
110
|
-
Functional icons need an accessible label or adjacent text. Editing SVG paths is not a CMS
|
|
111
|
-
feature; the text inside an SVG is not a bindable field ([bindings.md](bindings.md) §2).
|
|
112
|
-
- **Video.** Inventory format, host, poster, captions, autoplay/mute/loop and cost. Do not copy
|
|
113
|
-
every video into the media library by default; keep a streaming provider reference where the
|
|
114
|
-
site depends on one.
|
|
115
|
-
|
|
116
|
-
## Where images hide
|
|
117
|
-
|
|
118
|
-
Setup makes every picture an image field backed by a library asset, in code, with no step for
|
|
119
|
-
you: the release adopts each media file the build ships (alt text from the markup, its size, and
|
|
120
|
-
a default focal point you can move with `update_media`), derives the fields, and links each
|
|
121
|
-
value to its asset. Check it, never redo it: the binding report's `elementCoverage.image` is
|
|
122
|
-
`editable / total`, and `imageGaps` names what is not a field, misses first.
|
|
123
|
-
|
|
124
|
-
| Code | The picture | What to do |
|
|
125
|
-
|---|---|---|
|
|
126
|
-
| `IMG_DATA_URI`, `IMG_NO_SOURCE` | An `<img>` inlined as `data:`, or filled in by a script | Ship it as a file with a real `src` |
|
|
127
|
-
| `BG_PSEUDO_ELEMENT` | A background on `::before` / `::after` | Move it onto the element, or render an `<img>` |
|
|
128
|
-
| `BG_ON_TEXT` | A background on an element that is itself a text field, or on a section whose copy sits directly in it. A section that keeps its copy in one wrapper is never listed: the picture is the section's field and the copy the wrapper's | Wrap the text, so the box and the words are two elements |
|
|
129
|
-
| `SVG_TOO_LARGE` | An inline `<svg>` past 64 KB | Ship it as an `.svg` file in an `<img>` |
|
|
130
|
-
| `SVG_SPRITE` | A `<use>` naming a symbol the page does not hold, or a sprite FILE the build does not ship (another host's, a relative path) | Inline the symbol in the page, serve the sprite from the site root, or ship the icon as its own `<svg>` |
|
|
131
|
-
| `IMAGE_NOT_ASSET` | A header or footer picture the Media library holds no asset for | Nothing lost: the rest of the region is the Layout's and this picture stays a field of each page. Setup tries it again on the next build |
|
|
132
|
-
| `SVG_NOT_ICON`, `SVG_DECORATIVE` | An `<svg>` holding a script, an HTML island or a remote image; an `aria-hidden` glyph in the nav or footer, or a nameless one on an older import | Decoration: leave it, or give a logo `role="img"` and an `aria-label` |
|
|
133
|
-
| `BG_SPRITE`, `BG_TINY`, `BG_PATTERN`, `BG_DATA_URI`, `BG_STATE` | A sprite, an icon-sized box, a tile, an inlined texture, a hover image | Decoration: leave it |
|
|
134
|
-
|
|
135
|
-
The first four rows count against the image coverage; the rest do not. A gap you cannot
|
|
136
|
-
close is reported with its code, never described as done.
|
|
137
|
-
|
|
138
|
-
An icon a sprite file draws (`<use href="/sprite.svg#shield">`, a same-site `.svg` the build
|
|
139
|
-
ships) is an image field like any inline icon. An icon a FONT draws (`<i class="ph ph-tag">`) is
|
|
140
|
-
a text field holding the class, written to the `class` attribute alone: change the class to
|
|
141
|
-
change the glyph.
|
|
142
|
-
|
|
143
|
-
`elementGaps` is the same record for everything that is not a picture (`rows` carry a `sample`
|
|
144
|
-
where an image row has a `src`). `EMBED_NOT_VIDEO` (an `<iframe>` that is no video provider's),
|
|
145
|
-
`AUDIO_SOURCE` and `ICON_FONT` (a font icon inside a paragraph that is one rich-text value) are
|
|
146
|
-
shown and editable nowhere, and count against coverage. The rest are declined on purpose and
|
|
147
|
-
never count: `SKIP_LINK`, `FRAMEWORK_UI` (a docs framework's pager, table of contents or
|
|
148
|
-
search), `FRAGMENT_LINK` (an in-page `#` destination under those rules), `ORDINAL_MARKER` (a
|
|
149
|
-
step number the template counts out, on a project imported before step numbers were fields),
|
|
150
|
-
`TEMPLATE_TEXT`, `NOSCRIPT_TEXT` and `HEAD_META_UNMAPPED` (a `<meta>` no native SEO field holds,
|
|
151
|
-
by name).
|
|
152
|
-
|
|
153
|
-
## Media host
|
|
154
|
-
|
|
155
|
-
Use `mediaBaseUrl` from `get_project`: media is served from the CDN origin or the API origin of
|
|
156
|
-
the backend that owns the asset ids. `set_media_delivery` is the owner's decision, asked every
|
|
157
|
-
time. If a stored URL 404s, find the owning asset and report it; never re-save content to "move"
|
|
158
|
-
assets. How templates and resize URLs use it: [frameworks.md](frameworks.md) Media host.
|
|
159
|
-
|
|
160
|
-
## Presentation-mode UX
|
|
161
|
-
|
|
162
|
-
Show only meaningful controls, and state the scope before a change:
|
|
163
|
-
|
|
164
|
-
```text
|
|
165
|
-
Brand [sitewide, reviewed]
|
|
166
|
-
Logos · Colors · Typography · Spacing / Radius / Motion
|
|
167
|
-
|
|
168
|
-
Selected hero image [this instance]
|
|
169
|
-
Asset · Alt text / Decorative · Caption
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
Keep provenance and licence status visible to developers, away from everyday editing.
|
|
173
|
-
|
|
174
|
-
## Acceptance
|
|
175
|
-
|
|
176
|
-
- Every local asset resolves from the project's media store; external dependencies are listed.
|
|
177
|
-
- Logo roles and fonts carry evidence and licence status.
|
|
178
|
-
- Colour, type and spacing changes go through tokens and keep contrast and responsive layout.
|
|
179
|
-
- Editing a usage's alt text or caption changes the right rendered image.
|
|
180
|
-
- Icons work in every intended theme and state.
|
|
181
|
-
- Baseline and changed screenshots are compared; material differences are explained.
|
|
1
|
+
---
|
|
2
|
+
title: "Brand kit and media"
|
|
3
|
+
description: "Brand tokens, fonts, logo kit, image sizing and focal point, SVG and video rules, and the media host."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Brand kit, section styles and media
|
|
7
|
+
|
|
8
|
+
**Outcome.** An imported site keeps its appearance, and editors change the intended brand and
|
|
9
|
+
media controls through one coherent system instead of scattered CSS values.
|
|
10
|
+
|
|
11
|
+
## Audit sources and the rendered design
|
|
12
|
+
|
|
13
|
+
Inspect CSS variables, the Tailwind or theme config, design-token files, `@font-face`, layout
|
|
14
|
+
components, media directories, responsive assets and computed styles on representative routes.
|
|
15
|
+
Compare desktop and mobile screenshots. Record where each proposed token came from and how often
|
|
16
|
+
it is used. One colour on one button is not a brand identity.
|
|
17
|
+
|
|
18
|
+
Build a token inventory: role, source value, proposed BetterCMS value, affected components,
|
|
19
|
+
confidence, approval status. Cover colours, type families, weights and scale, spacing, container
|
|
20
|
+
widths, radius, borders, elevation, motion and breakpoints. Group colours into semantic roles
|
|
21
|
+
(primary, accent, background, surface, text, muted, status colours). Keep raw source values as
|
|
22
|
+
evidence, never as every component's editable prop.
|
|
23
|
+
|
|
24
|
+
## What the brand kit is, and who writes it
|
|
25
|
+
|
|
26
|
+
- `get_project` returns `brandKit`: colours (`primary`, `accent`, `background`, `surface`,
|
|
27
|
+
`text`, `muted`), `typography` (`headingFont`, `bodyFont`), `radius` (`sharp`/`rounded`/`pill`),
|
|
28
|
+
and optional `typeScale`, `spacing`, `elevation`, `motion` and gradients. A project that never
|
|
29
|
+
saved one still returns the default kit.
|
|
30
|
+
- **Colours and typography are edited in the dashboard**; no MCP tool writes them. Prepare a
|
|
31
|
+
precise proposal and map the source CSS to it; never say you changed them.
|
|
32
|
+
- **The logo mark and favicon** are written with `set_brand_assets`, by the id of a media asset
|
|
33
|
+
of this project (upload it first). A slot a person chose is refused unless you pass
|
|
34
|
+
`replace: true`; ask before passing it.
|
|
35
|
+
- **Gradients** are written with `set_brand_graphics`: a kind, an optional angle and 2-8 stops
|
|
36
|
+
that name kit colour keys, never a hex or a CSS string.
|
|
37
|
+
- Every brand write is a version a person can restore in the dashboard.
|
|
38
|
+
- The hosted renderer emits the kit as `--bcms-*` and `--brand-*` CSS custom properties, which
|
|
39
|
+
reach hosted pages only. A self-hosted site owns its tokens: bind its own presentation
|
|
40
|
+
variables to the approved kit, and declare its presentation in `bcms-presentation.json` at the
|
|
41
|
+
root of the deployed artifact.
|
|
42
|
+
- `update_project` `presentation` themes the platform's own renderer with the same closed knobs
|
|
43
|
+
as a release's `bcms-presentation.json`. It does not restyle a deployed build.
|
|
44
|
+
|
|
45
|
+
## Section styles
|
|
46
|
+
|
|
47
|
+
Section style props are closed tokens, not free CSS: space is `none|xs|sm|md|lg|xl`, colour is
|
|
48
|
+
`none|white|black|muted|mutedBg|primary|accent`. Never put a hex value from the brand into block
|
|
49
|
+
props: a token follows a brand change, a copied hex does not. Style inspector properties:
|
|
50
|
+
`CM-27`. Expose a curated choice (for example
|
|
51
|
+
`Surface: Light / Dark / Brand`); never ask an editor for a hex, a class name or a font path.
|
|
52
|
+
Say whether a change is brand-wide or one instance, and test that a token change keeps contrast
|
|
53
|
+
and layout intact in every variant. Offer only controls the renderer actually applies; a missing
|
|
54
|
+
style binding is a code or platform task, not a dashboard promise.
|
|
55
|
+
|
|
56
|
+
## Typography and fonts
|
|
57
|
+
|
|
58
|
+
Inventory family stacks, weights, styles, variable axes, local files, remote providers and
|
|
59
|
+
where each is used. Match heading and body roles to the rendered code; do not swap a display
|
|
60
|
+
face for the body face because it appears less often. Keep fallbacks and the loading strategy.
|
|
61
|
+
Self-host a font file only when its licence allows it; otherwise keep the provider reference and
|
|
62
|
+
list the dependency. Record licence evidence or "licence unknown".
|
|
63
|
+
|
|
64
|
+
Keep semantic HTML apart from visual size: an H2 may use a display token without becoming an
|
|
65
|
+
H1. Expose typography variants only where the design allows a real choice.
|
|
66
|
+
|
|
67
|
+
## Logos, images, icons and video
|
|
68
|
+
|
|
69
|
+
- **Logo kit.** Roles: primary, reverse, dark, compact mark, wordmark, favicon, where the source
|
|
70
|
+
proves them; mark the rest unclassified. Keep vector originals and transparency. The navigation
|
|
71
|
+
logo is a stored image on the layout; replace it there ([components.md](components.md) §4),
|
|
72
|
+
not in each page.
|
|
73
|
+
- **Images.** A stored image value is an object (`url`, `altText`, asset id), never a bare
|
|
74
|
+
string; read it as one. Alt text, decorative state and caption belong to the usage. Keep
|
|
75
|
+
`srcset`/`sizes`, poster, lazy/preload intent and art direction (a publish keeps them too:
|
|
76
|
+
[bindings.md](bindings.md) Images). Decorative images render an
|
|
77
|
+
empty `alt`; informative ones need text that fits the context. `update_media` with
|
|
78
|
+
`decorative: true` marks a library asset decorative, so the audit stops asking for its alt text.
|
|
79
|
+
- **Request the size you render.** Mint resize URLs with `@bettercms-ai/image-url`
|
|
80
|
+
(`urlFor = imageUrlBuilder({ baseUrl: mediaUrl })`, then `urlFor(asset).size(800, 450).url()`;
|
|
81
|
+
also `width`, `format`, `quality`, `fit`, `dpr` 1–3) and a
|
|
82
|
+
`srcset` of the widths the layout actually uses. Why: the original upload is often several
|
|
83
|
+
thousand pixels wide, and an unresized `url` ships all of it.
|
|
84
|
+
- **Framing lives on the value, never in the model.** An asset carries one focal point (0–1
|
|
85
|
+
fractions), set with `update_media` `focalX`/`focalY` (null resets to the centre) or in the
|
|
86
|
+
dashboard; a URL with both dimensions and `fit` cover crops around it automatically. One
|
|
87
|
+
PLACEMENT can differ: the image value itself may carry `focal: { x, y }` and
|
|
88
|
+
`crop: { top, right, bottom, left }` (0–1, cut from each edge; the crop must leave a visible
|
|
89
|
+
window). The value's focal wins over the asset's; absent keys fall back to the asset. Draw a
|
|
90
|
+
value with `imageValueUrl(value, { w, h })` and `imageValueObjectPosition(value)` from
|
|
91
|
+
`@bettercms-ai/image-url`, never `.url` alone, or the crop is lost. The dashboard's asset Crop
|
|
92
|
+
rewrites the stored bytes for every usage. Never add focal or crop FIELDS to a model: they
|
|
93
|
+
belong on the image value.
|
|
94
|
+
- **Image block width.** An image block's `props.width` is pixels, and every lane caps it at
|
|
95
|
+
the column (`max-width:100%`, height auto). Leave it unset to fill the column.
|
|
96
|
+
- **Rich-text images.** A `builtin:image` block takes `align` (`full` | `center` | `left` |
|
|
97
|
+
`right`) and `size` (`small` | `medium` | `large` | `full`) in its fields. Both render on every
|
|
98
|
+
lane, with inline widths, so no site CSS is needed.
|
|
99
|
+
- **Image facts ride on the delivered value.** Delivery adds `width`, `height`, `thumbHash` (a
|
|
100
|
+
ThumbHash placeholder) and `focalX`/`focalY` to each image value, read at request time and
|
|
101
|
+
omitted while unknown (object values only: a bare URL string stays a string). Reserve the box from `width`/`height`, decode `thumbHash` for a blur-up,
|
|
102
|
+
and fall back to a tiny variant (`width(24)`) when it is absent.
|
|
103
|
+
- **Generate or edit an image.** `generate_image` { prompt, width?, height?, altText } adds an AI
|
|
104
|
+
image to the Media Library; `transform_image` { mediaId, prompt } saves an edited copy as a NEW
|
|
105
|
+
asset and keeps the original. Each costs 5 AI credits (free with the workspace's own Together
|
|
106
|
+
key). Always set `altText`, and use them only where the user wants generated imagery: never to
|
|
107
|
+
stand in for a real product, person or place.
|
|
108
|
+
- **SVG.** Every SVG upload is sanitised on the way in, and SVGs are served as vectors (no
|
|
109
|
+
raster variants). Keep safe icons as SVG and expose icon choice, size and colour token.
|
|
110
|
+
Functional icons need an accessible label or adjacent text. Editing SVG paths is not a CMS
|
|
111
|
+
feature; the text inside an SVG is not a bindable field ([bindings.md](bindings.md) §2).
|
|
112
|
+
- **Video.** Inventory format, host, poster, captions, autoplay/mute/loop and cost. Do not copy
|
|
113
|
+
every video into the media library by default; keep a streaming provider reference where the
|
|
114
|
+
site depends on one.
|
|
115
|
+
|
|
116
|
+
## Where images hide
|
|
117
|
+
|
|
118
|
+
Setup makes every picture an image field backed by a library asset, in code, with no step for
|
|
119
|
+
you: the release adopts each media file the build ships (alt text from the markup, its size, and
|
|
120
|
+
a default focal point you can move with `update_media`), derives the fields, and links each
|
|
121
|
+
value to its asset. Check it, never redo it: the binding report's `elementCoverage.image` is
|
|
122
|
+
`editable / total`, and `imageGaps` names what is not a field, misses first.
|
|
123
|
+
|
|
124
|
+
| Code | The picture | What to do |
|
|
125
|
+
|---|---|---|
|
|
126
|
+
| `IMG_DATA_URI`, `IMG_NO_SOURCE` | An `<img>` inlined as `data:`, or filled in by a script | Ship it as a file with a real `src` |
|
|
127
|
+
| `BG_PSEUDO_ELEMENT` | A background on `::before` / `::after` | Move it onto the element, or render an `<img>` |
|
|
128
|
+
| `BG_ON_TEXT` | A background on an element that is itself a text field, or on a section whose copy sits directly in it. A section that keeps its copy in one wrapper is never listed: the picture is the section's field and the copy the wrapper's | Wrap the text, so the box and the words are two elements |
|
|
129
|
+
| `SVG_TOO_LARGE` | An inline `<svg>` past 64 KB | Ship it as an `.svg` file in an `<img>` |
|
|
130
|
+
| `SVG_SPRITE` | A `<use>` naming a symbol the page does not hold, or a sprite FILE the build does not ship (another host's, a relative path) | Inline the symbol in the page, serve the sprite from the site root, or ship the icon as its own `<svg>` |
|
|
131
|
+
| `IMAGE_NOT_ASSET` | A header or footer picture the Media library holds no asset for | Nothing lost: the rest of the region is the Layout's and this picture stays a field of each page. Setup tries it again on the next build |
|
|
132
|
+
| `SVG_NOT_ICON`, `SVG_DECORATIVE` | An `<svg>` holding a script, an HTML island or a remote image; an `aria-hidden` glyph in the nav or footer, or a nameless one on an older import | Decoration: leave it, or give a logo `role="img"` and an `aria-label` |
|
|
133
|
+
| `BG_SPRITE`, `BG_TINY`, `BG_PATTERN`, `BG_DATA_URI`, `BG_STATE` | A sprite, an icon-sized box, a tile, an inlined texture, a hover image | Decoration: leave it |
|
|
134
|
+
|
|
135
|
+
The first four rows count against the image coverage; the rest do not. A gap you cannot
|
|
136
|
+
close is reported with its code, never described as done.
|
|
137
|
+
|
|
138
|
+
An icon a sprite file draws (`<use href="/sprite.svg#shield">`, a same-site `.svg` the build
|
|
139
|
+
ships) is an image field like any inline icon. An icon a FONT draws (`<i class="ph ph-tag">`) is
|
|
140
|
+
a text field holding the class, written to the `class` attribute alone: change the class to
|
|
141
|
+
change the glyph.
|
|
142
|
+
|
|
143
|
+
`elementGaps` is the same record for everything that is not a picture (`rows` carry a `sample`
|
|
144
|
+
where an image row has a `src`). `EMBED_NOT_VIDEO` (an `<iframe>` that is no video provider's),
|
|
145
|
+
`AUDIO_SOURCE` and `ICON_FONT` (a font icon inside a paragraph that is one rich-text value) are
|
|
146
|
+
shown and editable nowhere, and count against coverage. The rest are declined on purpose and
|
|
147
|
+
never count: `SKIP_LINK`, `FRAMEWORK_UI` (a docs framework's pager, table of contents or
|
|
148
|
+
search), `FRAGMENT_LINK` (an in-page `#` destination under those rules), `ORDINAL_MARKER` (a
|
|
149
|
+
step number the template counts out, on a project imported before step numbers were fields),
|
|
150
|
+
`TEMPLATE_TEXT`, `NOSCRIPT_TEXT` and `HEAD_META_UNMAPPED` (a `<meta>` no native SEO field holds,
|
|
151
|
+
by name).
|
|
152
|
+
|
|
153
|
+
## Media host
|
|
154
|
+
|
|
155
|
+
Use `mediaBaseUrl` from `get_project`: media is served from the CDN origin or the API origin of
|
|
156
|
+
the backend that owns the asset ids. `set_media_delivery` is the owner's decision, asked every
|
|
157
|
+
time. If a stored URL 404s, find the owning asset and report it; never re-save content to "move"
|
|
158
|
+
assets. How templates and resize URLs use it: [frameworks.md](frameworks.md) Media host.
|
|
159
|
+
|
|
160
|
+
## Presentation-mode UX
|
|
161
|
+
|
|
162
|
+
Show only meaningful controls, and state the scope before a change:
|
|
163
|
+
|
|
164
|
+
```text
|
|
165
|
+
Brand [sitewide, reviewed]
|
|
166
|
+
Logos · Colors · Typography · Spacing / Radius / Motion
|
|
167
|
+
|
|
168
|
+
Selected hero image [this instance]
|
|
169
|
+
Asset · Alt text / Decorative · Caption
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Keep provenance and licence status visible to developers, away from everyday editing.
|
|
173
|
+
|
|
174
|
+
## Acceptance
|
|
175
|
+
|
|
176
|
+
- Every local asset resolves from the project's media store; external dependencies are listed.
|
|
177
|
+
- Logo roles and fonts carry evidence and licence status.
|
|
178
|
+
- Colour, type and spacing changes go through tokens and keep contrast and responsive layout.
|
|
179
|
+
- Editing a usage's alt text or caption changes the right rendered image.
|
|
180
|
+
- Icons work in every intended theme and state.
|
|
181
|
+
- Baseline and changed screenshots are compared; material differences are explained.
|