@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.
@@ -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`; `bcmsCode` {code, language}; `bcmsBlock` {schemaKey, schemaLabel, fields}; `table` {rows} | `p`; `pre`; `image`/`embed`/`columns`; `table` |
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 name and `_key`, because the
95
- block would disappear from the live site. The refusal is **top level only**: a foreign mark
96
- passes and its text renders unmarked; a foreign inline child passes and renders nothing. Check
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"]`; never all three) hides a block at up to two breakpoints, under 768, 768-1023 and 1024 and up, as `display:none` that keeps it in the HTML.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bettercms-ai/mcp",
3
- "version": "0.72.0",
3
+ "version": "0.72.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": {