@bettercms-ai/mcp 0.73.0 → 0.73.2
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/index.js +46 -17
- package/dist/index.js.map +1 -1
- package/dist/skills/bettercms/SKILL.md +1 -1
- package/dist/skills/bettercms/references/animation.md +47 -2
- package/dist/skills/bettercms/references/editor-chrome.md +1 -1
- package/dist/skills/bettercms/references/finish-site.md +26 -4
- package/package.json +2 -2
|
@@ -131,7 +131,7 @@ resources: `bettercms://skill/bettercms` for this file, and each reference under
|
|
|
131
131
|
| The same for an article entry, navigation in the Global Layout, tabs holding columns | [worked-examples-advanced.md](references/worked-examples-advanced.md) | a collection entry with a body, site chrome, or nested blocks | example, blog post, document body, mega menu, tabs, columns |
|
|
132
132
|
| Componentize a derived site | [componentize.md](references/componentize.md) | `get_site_composition` shows pages made of fields only | componentize, sections, fields-only, get_componentize_plan, BcmsSections, --sections |
|
|
133
133
|
| Click-to-edit attributes, addresses, declared mode | [bindings.md](references/bindings.md) | `grep -rl "data-bcms-" src` finds files; a value the editor cannot select | data-bcms-field, click to edit, binding report, stega, declared |
|
|
134
|
-
| Animated sites: GSAP, ScrollTrigger, split text,
|
|
134
|
+
| Animated sites: GSAP, ScrollTrigger, split text, Motion, Lenis, Locomotive, three.js and WebGL, marquees, carousels, Lottie, hydrated islands, and what must stand down in the editor's canvas (`inEditor()`) | [animation.md](references/animation.md) | `package.json` lists `gsap`, `lenis`, `locomotive-scroll`, `framer-motion` or `motion`, `three`, `swiper` or a text splitter; a publish refused with `SPLIT_PATTERN_UNKNOWN` or `ISLAND_PROPS_UNMATCHED`; a `client-only-island` gate exception | animation, GSAP, ScrollTrigger, SplitText, Lenis, Framer Motion, marquee, Swiper, Lottie, island, client:only, data-bcms-split |
|
|
135
135
|
| Wiring, env, hosting modes, media host, snapshots | [frameworks.md](references/frameworks.md) | `package.json` lists an adapter; `bcms-content.json` exists | hosting, headless, env, codegen, types, revalidate, snapshot |
|
|
136
136
|
| Astro adapter | [astro.md](references/astro.md) | the repo has `astro.config.*` | astro.config, Astro, BcmsBlocks, getStaticPaths |
|
|
137
137
|
| Next.js adapter | [next.md](references/next.md) | the repo has `next.config.*` | next.config, Next.js, App Router, ISR, notFound, static export |
|
|
@@ -5,8 +5,9 @@ description: "Keeping GSAP, ScrollTrigger, SplitText, Framer Motion, Lenis, marq
|
|
|
5
5
|
|
|
6
6
|
# Animated sites: editable copy that keeps moving
|
|
7
7
|
|
|
8
|
-
Read this when `package.json` lists `gsap`, `lenis`, `framer-motion` or
|
|
9
|
-
`split-type`, `splitting`, `lottie-web` or
|
|
8
|
+
Read this when `package.json` lists `gsap`, `lenis`, `locomotive-scroll`, `framer-motion` or
|
|
9
|
+
`motion`, `swiper`, `split-type`, `splitting`, `lottie-web`, `three` or `@react-three/fiber`, or
|
|
10
|
+
when the built HTML carries
|
|
10
11
|
`astro-island`, split-text spans or a duplicated marquee track. Attribute grammar:
|
|
11
12
|
[bindings.md](bindings.md). Adapter wiring: [frameworks.md](frameworks.md).
|
|
12
13
|
|
|
@@ -150,6 +151,13 @@ window.addEventListener("message", (event) => {
|
|
|
150
151
|
and any panel of yours that must scroll by itself inside the page takes `data-lenis-prevent`.
|
|
151
152
|
Never bind the element Lenis is given as `wrapper` or `content`.
|
|
152
153
|
|
|
154
|
+
In the Visual Editor the canvas owns the scroll: it marks the page's `<body>` with
|
|
155
|
+
`data-lenis-prevent`, so the wheel there is the browser's own and has no easing. That is the
|
|
156
|
+
editor, not a fault in the site: Lenis still reports every scroll (ScrollTrigger and anything
|
|
157
|
+
else hung on `lenis.on("scroll")` runs), its own `scrollTo` still eases, and the published page
|
|
158
|
+
is untouched. Do not remove the attribute or "repair" the easing for the canvas. What other
|
|
159
|
+
scroll libraries need there is in section 12.
|
|
160
|
+
|
|
153
161
|
Check: after an edit that adds two lines to a paragraph, the page still scrolls to the footer.
|
|
154
162
|
|
|
155
163
|
## 7. Marquees and duplicated tracks
|
|
@@ -248,4 +256,41 @@ editable is what you pass in:
|
|
|
248
256
|
Give the canvas `role="img"` and an `aria-label` bound to a field, or an adjacent caption. A
|
|
249
257
|
scene with no text a visitor reads needs no field; record it under the `decorative` exception.
|
|
250
258
|
|
|
259
|
+
A canvas drawn over the page (a fixed full-screen scene behind or above the copy) does not block
|
|
260
|
+
the editor: a click looks through an element that is no field and no control to the field under
|
|
261
|
+
it. Keep the copy in the HTML above or below the canvas, never only inside the scene.
|
|
262
|
+
|
|
251
263
|
Check: change the bound string, publish, reload: the drawn text reads the new value.
|
|
264
|
+
|
|
265
|
+
## 12. In the editor's canvas
|
|
266
|
+
|
|
267
|
+
The Visual Editor shows the real page with its scripts running, and owns three things there: the
|
|
268
|
+
scroll (its canvas scrolls and the page's window follows), the pointer (a click selects a field)
|
|
269
|
+
and the place on the page (picking a field in the panel scrolls to it). Motion that reads the
|
|
270
|
+
window's scroll or the clock runs there as it does live. Only what takes one of those three away
|
|
271
|
+
has to stand down, and the site decides that with one question:
|
|
272
|
+
|
|
273
|
+
```js
|
|
274
|
+
import { inEditor } from "@bettercms-ai/sdk"; // 1.31+. Without the SDK: window.name === "bcms-editor"
|
|
275
|
+
|
|
276
|
+
const smooth = inEditor() ? null : new LocomotiveScroll({ el, smooth: true });
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
`inEditor()` is true from the page's first script and through every navigation inside the
|
|
280
|
+
canvas, and false on the published page. What each kind of page does there:
|
|
281
|
+
|
|
282
|
+
| The page uses | In the canvas | You write |
|
|
283
|
+
|---|---|---|
|
|
284
|
+
| Native scroll, CSS scroll-driven animations (`animation-timeline`), Motion `useScroll` / `whileInView`, ScrollTrigger pin and scrub | runs as live | nothing |
|
|
285
|
+
| Lenis, Locomotive Scroll 5 | runs; the wheel has no easing (section 6) | nothing |
|
|
286
|
+
| GSAP ScrollSmoother | runs; the window still scrolls, the content eases behind it | nothing |
|
|
287
|
+
| Locomotive Scroll 4, any script that locks the window and moves the content by a transform | the page works under its own wheel, but the canvas cannot scroll it and a field picked in the panel is not brought into view | `smooth: !inEditor()`, so the window scrolls |
|
|
288
|
+
| A wheel-driven slide deck (GSAP `Observer`, fullPage) | the same | do not create the observer when `inEditor()`; let the sections stack and scroll |
|
|
289
|
+
| A scene with its own scroll layer (react-three-fiber `ScrollControls`, a fixed `overflow: auto` layer) | runs; picking a field scrolls that layer | nothing |
|
|
290
|
+
| An intro or preloader that holds the page (`overflow: hidden` until it ends, a full-screen cover) | plays on every reload of the canvas, and covers the page while it does | skip it when `inEditor()` |
|
|
291
|
+
|
|
292
|
+
Never use `inEditor()` to change what a field renders, to hide a section or to stop a timeline:
|
|
293
|
+
the canvas must show what a visitor gets. It is for scroll takeover, intros and nothing else.
|
|
294
|
+
|
|
295
|
+
Check: open the page in the Visual Editor, scroll with the wheel, drag the canvas scrollbar to the
|
|
296
|
+
footer, then pick a field of the first section in the panel: the canvas returns to it.
|
|
@@ -30,7 +30,7 @@ Give a fieldset a `kind`:
|
|
|
30
30
|
|---|---|---|
|
|
31
31
|
| `group` | Fields folded under one header (`collapsed: true` starts it shut) | 12 rows inside one group |
|
|
32
32
|
| `row` | Short fields on one line | at most 3 fields |
|
|
33
|
-
| `tab` | A tab strip | at most 3 tabs, names of
|
|
33
|
+
| `tab` | A tab strip | at most 3 tabs, names of 40 characters or fewer, top-level fields only, tabs never nest |
|
|
34
34
|
|
|
35
35
|
A group joins a tab with `tab: "<tab id>"`. A fieldset with no `kind` stays flat. Props have no
|
|
36
36
|
fieldsets: the first prop of a group sends `ui.group: { id, label, kind }` and each later one
|
|
@@ -62,7 +62,7 @@ line), which is what a script waiting on the setup reads.
|
|
|
62
62
|
| `svg` | every inline SVG a visitor sees (a logo, an icon, an illustration) belongs to a field; a hidden symbol sheet is not counted | `routes.rows[].svg` |
|
|
63
63
|
| `number` | every element whose whole text is a number (a stat, a price, a rating) belongs to a field | `routes.rows[].number` |
|
|
64
64
|
| `attribute` | every `alt`, `aria-label`, `title` and `placeholder` with words in it belongs to a field | `routes.rows[].attribute` |
|
|
65
|
-
| `names` | no field of the page, and no component, has a name a person cannot read: a slug, a hash, a structural word (`Section 2`, `wrap`),
|
|
65
|
+
| `names` | no field of the page, and no component, has a name a person cannot read: a slug, a hash, a structural word (`Section 2`, `wrap`), a number beside the same name (`Icon 2`), a numbered run the import left (`Pricing text 1` … `Pricing text 12`), a section the import called by a sentence of its copy (`The Numbers After One Season items`), or a component name past four words | `blocking.rows` |
|
|
66
66
|
| `seo` | the page has a title of at most 70 characters and a description of at most 200, and a canonical where the build ships one for this site's origin; site-wide, every page has a share image, its own or the site's default | `blocking.rows` |
|
|
67
67
|
| `structuredData` | JSON-LD the build ships on the route is on the page or the entry | `blocking.rows` |
|
|
68
68
|
| `collections` | sibling routes that are one family (three under one parent, or two under `tags`, `category`, `author`) are a collection; reported on the family's listing route, built or not | `blocking.rows` |
|
|
@@ -99,7 +99,7 @@ The other rows name their fix the same way:
|
|
|
99
99
|
| `fixCode` | The fix |
|
|
100
100
|
|---|---|
|
|
101
101
|
| `bind-elements` | `get_conversion_brief { complete: true }` lists what no field owns on the route; bind each (an SVG as an image or icon field, a number as its own field, attribute copy with `data-bcms-props`) |
|
|
102
|
-
| `rename-fields` | `get_page`, then `
|
|
102
|
+
| `rename-fields` | `get_page`, read the section's source, then name every listed field of the page in ONE `update_content_model` call (`pageId`, `fieldLabel` and `fieldHelpText` keyed by field path, `ifMatch` from `get_page`'s `backingModel`), or `update_field` for a single one; `update_component` for a component (`name`, and `propLabel` / `propHelpText` for its props in one call). See "Names" below |
|
|
103
103
|
| `fill-seo` | `update_page` with `metaTitle` and `metaDescription` (`update_content_entry` with `meta` for an entry; `generate_seo_meta` drafts one from text you give it); a missing canonical is setup's to copy from the built head, so that row carries `editor_setup { force: true }`; the site-wide row wants the site's default share image, `update_seo` with `seoDefaults.ogImage` |
|
|
104
104
|
| `import-structured-data` | `editor_setup { force: true }` imports the build's JSON-LD; `update_seo` when it has to be written by hand |
|
|
105
105
|
| `model-collection` | `create_content_model` with the family's `urlPattern` (`/tags/:slug`), then its entries: one item template for the family ([content-modeling.md](content-modeling.md)) |
|
|
@@ -119,8 +119,9 @@ the map), `import-structured-data`, `fill-seo` from the built head and the page'
|
|
|
119
119
|
`rename-fields` for a field still carrying a derived name, `componentize` once the authoring
|
|
120
120
|
answer is `components`, and `enable-in-place-structure` on a project it creates. What is left
|
|
121
121
|
takes judgement and is yours: `bind-elements`, `model-collection`, `set-form-recipient` (ask the
|
|
122
|
-
user), `run-sections-conversion`, a name a person gave, a
|
|
123
|
-
|
|
122
|
+
user), `run-sections-conversion`, a name a person gave, a name only the site's code can give (a
|
|
123
|
+
numbered run, a section called by its copy), a title over its limit, a look named by a number, and
|
|
124
|
+
the authoring answer itself (the user's). `rebuild` is the user's.
|
|
124
125
|
|
|
125
126
|
Beside the gate, `data` lists what else is unfinished, each item by its `ruleId`: content
|
|
126
127
|
(`mcp-pages-draft`, `mcp-pages-missing-meta`, `mcp-model-empty`, `mcp-form-no-recipient`,
|
|
@@ -171,6 +172,27 @@ the same way. A term has a `body` only where its template has an article. Chrome
|
|
|
171
172
|
could not take (an icon with no Media asset) is template copy on item routes: one shared field
|
|
172
173
|
of the collection, as it is a page's own field on a page.
|
|
173
174
|
|
|
175
|
+
### Names
|
|
176
|
+
|
|
177
|
+
The import names a field by its section and its role there (`Hero headline`, `FAQ questions`).
|
|
178
|
+
Where the markup says no more it numbers them (`Pricing text 4`, `Hero image 2`), and a section
|
|
179
|
+
whose classes name nothing is called by its heading's words. The `names` row lists both until
|
|
180
|
+
each is renamed, and nothing excuses it: an editor reads these names all day.
|
|
181
|
+
|
|
182
|
+
Name from the site's code, the way a developer writes a schema. Open the section's source file
|
|
183
|
+
and use what it calls things (`plan.cents`, `billingFrequency`, `StatsBand.astro`):
|
|
184
|
+
|
|
185
|
+
| What | The name | Like | Never |
|
|
186
|
+
|---|---|---|---|
|
|
187
|
+
| A section (a component's `name`) | what it IS, one to four words, sentence case | `Announcement banner`, `Logo cloud`, `Feature tabs`, `Testimonial + stats`, `Call to action` | its heading's copy, `Section 4`, a file path |
|
|
188
|
+
| A field or prop | what it HOLDS there, one to three words | `Dismiss key`, `Starter plan price`, `Dashboard screenshot`, `Billing frequency` | its own copy, a role with a number (`Text 4`), the section's name alone |
|
|
189
|
+
| Help text | one sentence saying what it is for or where it shows, only where the name leaves a question | `Change this to show the banner again to visitors who dismissed an earlier one.` | the label or the value said again |
|
|
190
|
+
|
|
191
|
+
A numbered run that is one thing repeated (plans, features, logos) is a list, not twelve names:
|
|
192
|
+
fold it into one list field or one `table` prop whose rows are the cards
|
|
193
|
+
([componentize.md](componentize.md)), and name the list (`Plans`). A name you write is yours: a
|
|
194
|
+
later setup never renames it.
|
|
195
|
+
|
|
174
196
|
### Exceptions go on the record
|
|
175
197
|
|
|
176
198
|
A check you are leaving on purpose is recorded, one row per route and check, with
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bettercms-ai/mcp",
|
|
3
|
-
"version": "0.73.
|
|
3
|
+
"version": "0.73.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "BetterCMS MCP server — schema, content, forms + components authoring tools over the Management API, authed via OAuth device-code.",
|
|
6
6
|
"bin": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@bettercms-ai/device-auth": "^0.2.0",
|
|
35
|
-
"@bettercms-ai/sdk": "^1.
|
|
35
|
+
"@bettercms-ai/sdk": "^1.31.0",
|
|
36
36
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
37
37
|
"zod": "^4.3.6"
|
|
38
38
|
},
|