@bettercms-ai/mcp 0.72.0 → 0.72.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 +55 -13
- package/dist/index.js.map +1 -1
- package/dist/skills/bettercms/references/astro.md +27 -0
- package/dist/skills/bettercms/references/componentize.md +8 -0
- package/dist/skills/bettercms/references/components.md +17 -1
- package/dist/skills/bettercms/references/forms-and-interactions.md +22 -0
- package/dist/skills/bettercms/references/next.md +28 -0
- package/dist/skills/bettercms/references/portable-text.md +11 -5
- package/dist/skills/bettercms/references/worked-examples-advanced.md +1 -1
- package/package.json +1 -1
|
@@ -88,6 +88,33 @@ import BcmsSections from "@bettercms-ai/astro/components/BcmsSections.astro";
|
|
|
88
88
|
too until the list names one of the page's slots; from then on it is a removed section. Write
|
|
89
89
|
a slot bare only when the page already places that bind (`get_page`), otherwise `code:`.
|
|
90
90
|
- No snapshot (a local build, the API down): every slot renders in source order, untouched.
|
|
91
|
+
|
|
92
|
+
**A page that only draws a view.** Where the page file is a shell (`<HomeView locale="en" />`,
|
|
93
|
+
alone or inside its layout) the sections are written in the view, and `--sections` wraps them
|
|
94
|
+
there: it follows the import (relative, or a `paths` alias the repository's own tsconfig or
|
|
95
|
+
jsconfig declares, index files included) up to three components down, to the first file that
|
|
96
|
+
writes two or more sections or writes its run inside a layout. One view often serves several
|
|
97
|
+
pages (`/` and `/fr`), so the view cannot name the page. Each page hands it down, and the view
|
|
98
|
+
reads it:
|
|
99
|
+
|
|
100
|
+
```astro
|
|
101
|
+
<!-- src/pages/fr/index.astro -->
|
|
102
|
+
<HomeView locale="fr" bcmsPage="fr" />
|
|
103
|
+
|
|
104
|
+
<!-- src/views/HomeView.astro: `bcmsPage?: string` joins its Props -->
|
|
105
|
+
<MainLayout>
|
|
106
|
+
<BcmsSections page={Astro.props.bcmsPage ?? ""}>
|
|
107
|
+
<Fragment slot="hero"><HeroSection title={copy.hero.title} /></Fragment>
|
|
108
|
+
…
|
|
109
|
+
</BcmsSections>
|
|
110
|
+
</MainLayout>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Each page then follows its own CMS page's list through the one set of slots. A route that hands
|
|
114
|
+
the view no page (the CMS has none for it yet) renders the source order. A slot is bare only
|
|
115
|
+
when every page drawing the view places that bind. A dynamic route's view (`blog/[id].astro` →
|
|
116
|
+
`BlogPostView`) is wrapped the same way with `template`. Write it by hand in this shape when the
|
|
117
|
+
codemod refuses the view ([componentize.md](componentize.md) §5).
|
|
91
118
|
- The list is the published snapshot's, or the draft's when the Visual Editor frames a
|
|
92
119
|
server-rendered page (`prerender = false`). `blocks={…}` gives it yourself.
|
|
93
120
|
|
|
@@ -140,6 +140,11 @@ npx @bettercms-ai/convert --sections --root .
|
|
|
140
140
|
npx @bettercms-ai/convert --verify-styles --root . # must report no style edits
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
+
A page file that only draws a view component (`<HomeView locale="en" />`) is wrapped in the
|
|
144
|
+
view's file, where its sections are written; the dry run names that file per page, and the page
|
|
145
|
+
itself only gains the prop that tells the view which page it is ([astro.md](astro.md),
|
|
146
|
+
[next.md](next.md)). A lone component that is itself one section is wrapped in the page.
|
|
147
|
+
|
|
143
148
|
A page it leaves alone keeps rendering as before, and is listed with one of these:
|
|
144
149
|
|
|
145
150
|
| Reason | The page | What you do |
|
|
@@ -154,6 +159,9 @@ A page it leaves alone keeps rendering as before, and is listed with one of thes
|
|
|
154
159
|
| `DIALECT_UNSUPPORTED` | Markdown, MDX or HTML | Nothing: the body is one `document` field |
|
|
155
160
|
| `NAME_TAKEN` | Already uses the name `BcmsSections` for something else | Rename that, run again |
|
|
156
161
|
| `SDK_REQUIRED` | The repo's SDK predates the component | Add `--add-sdk`, install, run again |
|
|
162
|
+
| `VIEW_IMPORT_UNRESOLVED` | Draws one component, and its import names no file: an alias no tsconfig or jsconfig of the repository declares (one inherited through `extends` is not read) | Declare the alias in the config's own `compilerOptions.paths`, or import the view by a relative path; run again |
|
|
163
|
+
| `VIEW_DEPTH_EXCEEDED` | Its sections are more than three components below the page | Wrap by hand in the file that writes them, and pass the page down ([astro.md](astro.md), [next.md](next.md)) |
|
|
164
|
+
| `VIEW_SHARED_INCOMPATIBLE` | Draws a view another page draws too, and the CMS names the view's sections differently on the two (or one is a page and the other a collection's item template, in Astro) | Give each page a view of its own, or make the two CMS pages place the same binds, then run again |
|
|
157
165
|
|
|
158
166
|
The by-hand markup is in [astro.md](astro.md) and [next.md](next.md), and it follows the rules of
|
|
159
167
|
[animation.md](animation.md) §10: slot tags around sections you do not edit, no new element. On
|
|
@@ -32,6 +32,13 @@ defaultValue? }`.
|
|
|
32
32
|
- **A `button` prop is one unit.** Its value is `{ label, href, variant?, size?, arrow?, icon?,
|
|
33
33
|
newTab? }` and its `target.path` is `props` on a `button` block. For a new component never
|
|
34
34
|
declare a label prop plus a url prop: the two drift apart on the first edit.
|
|
35
|
+
- **A button the site's own script handles has no href.** Its value is `{ label, action: "code",
|
|
36
|
+
variant?, size?, arrow?, icon? }`: a `<button type="button">` that opens a dialog, accepts
|
|
37
|
+
cookies or starts a tour. The label and the look are edited; the click stays the site's.
|
|
38
|
+
`href`, `newTab` and `rel` beside `action: "code"` are refused, and a value with neither an
|
|
39
|
+
href nor the action is refused as before. Setup derives these as button fields on a new
|
|
40
|
+
import. A control of the page itself is never one: a menu or theme toggle, a carousel arrow, a
|
|
41
|
+
dialog's close, an accordion or tab trigger, a copy button, a pagination step.
|
|
35
42
|
- `target.blockId` + `target.path` must point at a real block and property inside the
|
|
36
43
|
component's own `blockJson`. Why: rendering copies each prop's value to that target; a target
|
|
37
44
|
that does not exist renders the default and the edit goes nowhere.
|
|
@@ -227,7 +234,10 @@ adding or spacing one section is the recipe in [publishing.md](publishing.md).
|
|
|
227
234
|
`BLOCK_ID_ONLY`, …) its block style by hand, confirm with `--verify-styles`, push and release.
|
|
228
235
|
- **A `button` field renders with `BcmsButton`** (`path`, `value`): one `<a data-bcms-button>`
|
|
229
236
|
carrying `data-variant` and `data-size`, so every look control (style, size, arrow, icon, new
|
|
230
|
-
tab) applies on the canvas and on publish.
|
|
237
|
+
tab) applies on the canvas and on publish. A value with `action: "code"` renders
|
|
238
|
+
`<button type="button" data-bcms-button>` with the same children and no href: attach your
|
|
239
|
+
handler to it as before. A publish writes its label and look and never an href, a target or a
|
|
240
|
+
rel, and never turns it into an anchor.
|
|
231
241
|
- **A button's looks are the ones the site already has.** Each release measures the built pages
|
|
232
242
|
and keeps the project's Button family equal to the looks found there. Style offers those looks
|
|
233
243
|
and no others, and a publish paints a pick with the site's own classes. Never write CSS for a
|
|
@@ -251,6 +261,12 @@ adding or spacing one section is the recipe in [publishing.md](publishing.md).
|
|
|
251
261
|
| Ghost | No fill and no border, padded like a button or filled on hover. |
|
|
252
262
|
| Text Link | A plain link the site styles as a call to action. Menu and footer links and links in copy are not looks. |
|
|
253
263
|
|
|
264
|
+
What counts as a button is one rule for the family and for the fields: what the site names one
|
|
265
|
+
(`btn`, `button`, `cta`, `role="button"`), or what its stylesheet paints as one (a fill or a
|
|
266
|
+
border of its own). Two looks paint nothing at rest and still count: a ghost, by its own padded
|
|
267
|
+
box, and a text link drawn as a row that underlines. In a menu those two count only beside a
|
|
268
|
+
painted button.
|
|
269
|
+
|
|
254
270
|
A light and a dark rendition, a hover or disabled state, a size, and one paint spelled in two
|
|
255
271
|
sets of classes are all ONE look. Two looks left with one role are told apart in words from
|
|
256
272
|
their paint ("Outline on dark", "Pill Outline"). A role once given stays: a look added later
|
|
@@ -191,6 +191,28 @@ form is named for what it is for: its `aria-label`, its one legend, a heading in
|
|
|
191
191
|
own band, else its purpose ("Newsletter signup", "Contact"). Rename it with `update_form` if the
|
|
192
192
|
site calls it something else.
|
|
193
193
|
|
|
194
|
+
**An imported form's words are edited in the form, and written into the built page.** On a new
|
|
195
|
+
import, setup binds each part of the built `<form>` to its form row: field labels, placeholders,
|
|
196
|
+
help lines, select and radio options, and the submit button's text. Change one with `update_form`
|
|
197
|
+
(`fields[].label`, `placeholder`, `helpText`, `options`, or `submitLabel`) and the next content
|
|
198
|
+
apply writes it into every built page that draws the form, with no rebuild. The built form
|
|
199
|
+
carries the stamps:
|
|
200
|
+
|
|
201
|
+
| Stamp | On | Says |
|
|
202
|
+
|---|---|---|
|
|
203
|
+
| `data-bcms-form-bound="<formId>"` | the `<form>` | Its parts are this form's. Never `data-bcms-form`: that one means the form posts to BetterCMS. |
|
|
204
|
+
| `data-bcms-form-part` | a part | `label`, `help`, `option` or `submit`, as `BcmsForm` names them. |
|
|
205
|
+
| `data-bcms-form-field="<key>"` | a part or a control | The field it belongs to. |
|
|
206
|
+
| `data-bcms-form-value="form:<formId>:<path>"` | a part shown as text | The address written: `fields.<key>.label`, `fields.<key>.helpText`, `fields.<key>.options.<i>`, `submitLabel`. A placeholder is a `data-bcms-props` entry (`form:<formId>:fields.<key>.placeholder`). |
|
|
207
|
+
|
|
208
|
+
Only words are written. The lane adds and removes no element and never touches `name`, `id`,
|
|
209
|
+
`for`, `action`, `value`, a validation attribute or a hidden input, so the site's own script and
|
|
210
|
+
endpoint keep working. An option added to the row has no element until the site's code is
|
|
211
|
+
rebuilt, and is reported as not on the page. A fieldset's `<legend>` and an
|
|
212
|
+
`<input type="submit">` stay the code's. A form setup ignored (sign-in, basket) and a form
|
|
213
|
+
already wired with `data-bcms-form` take no stamp. The submit button belongs to the form: its
|
|
214
|
+
text is `submitLabel`, never a page field.
|
|
215
|
+
|
|
194
216
|
### 4. Wiring a self-hosted site
|
|
195
217
|
|
|
196
218
|
- **Next**: `getForm('Name')` (or an id) reads the form from `bcms-content.json`; render
|
|
@@ -103,6 +103,34 @@ A page with its own data function passes it in, and the list joins the props it
|
|
|
103
103
|
`export const getStaticProps = sectionsStaticProps("home", pageStaticProps)`, or
|
|
104
104
|
`sectionsServerSideProps` for `getServerSideProps`. `--sections` writes this for you.
|
|
105
105
|
|
|
106
|
+
A page that only returns a view (`return <HomeView locale="en" />`) has its sections in the
|
|
107
|
+
view's file, and `--sections` wraps them there: it follows the import (relative, a `paths` alias
|
|
108
|
+
of the repository's tsconfig or jsconfig, an index file or a barrel's re-export) up to three
|
|
109
|
+
components down. One view often serves several pages (`/` and `/fr`), so the page reads its own
|
|
110
|
+
list and hands it over, and the view takes it as an optional prop:
|
|
111
|
+
|
|
112
|
+
```tsx
|
|
113
|
+
// app/fr/page.tsx
|
|
114
|
+
export default function Page() {
|
|
115
|
+
return <HomeView locale="fr" bcmsSections={sectionsFor("fr")} />;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// views/home-view.tsx: a server or a "use client" component, it only names the type
|
|
119
|
+
export default function HomeView({ bcmsSections, locale }: Props & { bcmsSections?: SectionsData }) {
|
|
120
|
+
return (
|
|
121
|
+
<main>
|
|
122
|
+
<BcmsSections {...(bcmsSections ?? { page: "" })}>
|
|
123
|
+
<BcmsSection bind="hero"><Hero locale={locale} /></BcmsSection>
|
|
124
|
+
…
|
|
125
|
+
</BcmsSections>
|
|
126
|
+
</main>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
A render that was handed no list (a route the CMS has no page for) keeps the source order. In the
|
|
132
|
+
Pages Router the page passes on the prop it got: `<HomeView bcmsSections={bcmsSections} />`.
|
|
133
|
+
|
|
106
134
|
- A bind is the family of the section's first bound field (`features-title` → `features`): the
|
|
107
135
|
`bind` on the page's `component` block. A block bound to a section renders it at the block's
|
|
108
136
|
position; a section added in the editor (no bind) renders through `BcmsBlocks`.
|
|
@@ -76,7 +76,7 @@ names the field accepts.
|
|
|
76
76
|
| decorators (span `marks`) | `strong`, `em`, `underline`, `strike-through`, `code`, `highlight` | `b`, `i`, `u`, `s`, `code`, `mark` |
|
|
77
77
|
| annotations (`markDefs`) | `link` {href, target, className}; `styled` {tag, className, attrs} | `link`/`internalLink`; always |
|
|
78
78
|
| `listItem` | `bullet`, `number`, `check` | `ul`, `ol`, `checklist` |
|
|
79
|
-
| block objects | `bcmsDivider
|
|
79
|
+
| block objects | `bcmsDivider`, `pullQuote` {quote, name, role?, link?}; `keyTakeaways` {title?, points}; `bcmsCode` {code, language}; `bcmsBlock` {schemaKey, schemaLabel, fields}; `table` {rows} | `p`; `ul`; `pre`; `image`/`embed`/`columns`; `table` |
|
|
80
80
|
| inline objects | `bcmsMention` {label} | always |
|
|
81
81
|
|
|
82
82
|
- **Keys**: every block, span, markDef, row and cell has `_key` matching `^[A-Za-z0-9-]{1,32}$`,
|
|
@@ -85,16 +85,22 @@ names the field accepts.
|
|
|
85
85
|
block. Every markDef is referenced by at least one span.
|
|
86
86
|
- **Lists are flat**: a list item is a normal block with `listItem` and `level` (1 = top). Never
|
|
87
87
|
nest blocks inside a list container. A `check` item carries `checked: true|false`.
|
|
88
|
+
- **Pull quote**: `{_type:"pullQuote", _key, quote, name, role?, link?}`; `quote` and `name` are
|
|
89
|
+
strings, `link` is a URL (or a link object with `href`) the name links to. A `pullQuote` without
|
|
90
|
+
string `quote` and `name` is refused on write. Renders `<figure data-rt="pullquote">`.
|
|
91
|
+
- **Key takeaways**: `{_type:"keyTakeaways", _key, title?, points}`; `points` is an array of plain
|
|
92
|
+
strings, one per bullet. More than 8 points or a title over 40 characters is refused on write,
|
|
93
|
+
by code and path. Renders `<aside data-rt="takeaways">`.
|
|
88
94
|
- **Tables**: a cell holds blocks in `content`, not a string. `header: true` marks a `<th>` cell;
|
|
89
95
|
`head: true` puts a row in `<thead>`.
|
|
90
96
|
- **`styled`** carries a site's own inline markup; `tag` is one of `span`, `small`, `sup`, `sub`,
|
|
91
97
|
`kbd`, `abbr`, `time`, or a decorator tag (`strong`, `em`, `b`, `i`, `u`, `s`, `mark`, `code`, …)
|
|
92
98
|
that carries a class. A `link` keeps its class as `className`.
|
|
93
99
|
- **Top-level `_type`** is one of `block`, `bcmsBlock`, `table`, `bcmsCode`, `bcmsDivider`,
|
|
94
|
-
`bcmsMention`. A name outside that list is refused on write, by
|
|
95
|
-
block would disappear from the live site. The refusal is **top level
|
|
96
|
-
passes and its text renders unmarked; a foreign inline child passes
|
|
97
|
-
those yourself.
|
|
100
|
+
`bcmsMention`, `pullQuote`, `keyTakeaways`. A name outside that list is refused on write, by
|
|
101
|
+
name and `_key`, because the block would disappear from the live site. The refusal is **top level
|
|
102
|
+
only**: a foreign mark passes and its text renders unmarked; a foreign inline child passes
|
|
103
|
+
and renders nothing. Check those yourself.
|
|
98
104
|
|
|
99
105
|
A paragraph with a bold word and a link:
|
|
100
106
|
|
|
@@ -501,7 +501,7 @@ in the tabs above. Rules and refusal codes: [components.md](components.md).
|
|
|
501
501
|
|
|
502
502
|
Not available today: a tab icon (a tab has a label only) and a
|
|
503
503
|
"first open tab" setting. Per-column widths: columns are equal, and `gap` is a whole number of
|
|
504
|
-
pixels, not a named size. Per-breakpoint sizes are not available; `style.hideOn` (`["mobile"]`, `["tablet","desktop"]
|
|
504
|
+
pixels, not a named size. Per-breakpoint sizes are not available; `style.hideOn` (`["mobile"]`, `["tablet","desktop"]`) hides a block at those breakpoints, under 768, 768-1023 and 1024 and up, as `display:none` that keeps it in the HTML; all three hides it on the page, and no published page, Markdown twin or search index carries it.
|
|
505
505
|
|
|
506
506
|
## Checks
|
|
507
507
|
|
package/package.json
CHANGED