@bettercms-ai/mcp 0.66.6 → 0.67.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.
@@ -60,13 +60,16 @@ value over everything else inside it, so:
60
60
  and bind that. An `<li>` that renders nothing but the value is itself the leaf.
61
61
  - **A link or button label beside an icon.** In `<a href="…"><svg/> Sign up</a>`, put the label
62
62
  in its own `<span>` with the field, and the `href` on the anchor through `data-bcms-props`
63
- (`cta.href|url|href`). A binding on the anchor's text deletes the icon.
63
+ (`cta.href|url|href`). A binding on the anchor's text deletes the icon. A call to action is a
64
+ `button` field rendered with `BcmsButton` ([components.md](components.md)).
64
65
  - **SVG text is never bound; the icon is.** No field is derived from the text inside an `<svg>`,
65
66
  and no binding there is measured. Each inline icon IS an editable `image` field (`config.media:
66
67
  "icon"`, an svg value): an editor swaps it. Never remove one to tidy a form; group it.
67
- - **Icon-only links in the nav and footer** take their field name from the anchor's
68
- `aria-label`, then the SVG `<title>`. Give each one an `aria-label`, or every social link is
69
- named `link` and an editor cannot tell them apart.
68
+ - **Icon-only links in the nav and footer** take their field name from what a screen reader
69
+ reads: the anchor's `aria-label`, its `sr-only` text, a `title`, the SVG `<title>` or an `alt`
70
+ (never `<style>` or `<script>` text). Give each one an `aria-label` or `sr-only` label, or every
71
+ social link is named `link` and an editor cannot tell them apart. A row of them is a "Links" or
72
+ "Social links" list; each row keeps its own name in its values.
70
73
 
71
74
  ### The site's design is not yours to change
72
75
 
@@ -131,7 +134,9 @@ helpers rather than by hand.
131
134
  keep inside a bound element. A value with no element never reaches the page, and one that
132
135
  would corrupt its element is refused and named.
133
136
  - Read the report, not the screen. Clean = `coverage.pending` empty and the mode you expect. Each
134
- `unmatched` reason names a fix in the source. A `fallback` flag means the build carried no
137
+ `unmatched` reason names a fix in the source. A field the stamped build renders on no route is flagged `stale: true` on its schema — kept, hidden in the editor, cleared by the release that binds it again — and is not listed. A Global Layout Section is reported only on the
138
+ routes that render it (`layoutRoutes` lists each route's Sections): a docs page on its own layout
139
+ is not missing the nav, so never add one there. A `fallback` flag means the build carried no
135
140
  `bcms-brief` meta and was measured against the latest pin. Only a release creates the report;
136
141
  a content apply later updates its Layout rows.
137
142
 
@@ -168,9 +168,31 @@ A code-built site ships only what its templates draw, whatever the page's `block
168
168
  `upgrade-section-roots`. The fix is the codemod's `--upgrade-sections` mode, latest version, at
169
169
  the repo root (`--dry-run` first); give each root it reports as skipped (`OWN_STYLE`, `SPREAD`,
170
170
  `BLOCK_ID_ONLY`, …) its block style by hand, confirm with `--verify-styles`, push and release.
171
- - **A `button` field renders with `BcmsButton`** (`path`, `value`): one `<a>` carrying
172
- `data-variant` and `data-size`, so the site's CSS decides what each look looks like and the
173
- editor repaints it live.
171
+ - **A `button` field renders with `BcmsButton`** (`path`, `value`): one `<a data-bcms-button>`
172
+ carrying `data-variant` and `data-size`, so the site's CSS decides what each look looks like and
173
+ every look control (style, size, arrow, icon, new tab) applies on the canvas and on publish.
174
+ Without the SDK, write that same anchor: `data-bcms-button="<path>"`, the label in its own
175
+ `<span data-bcms-field="<path>.label">`, the href through `data-bcms-props`. The arrow is the
176
+ `svg.bcms-btn-arrow` it draws; never leave a hand-written arrow `<svg>` beside it, or turning
177
+ the arrow off leaves one behind. A CTA whose look is hard-coded tells the editor "Style lives
178
+ in the site's code".
179
+ - **Render a CTA as one button** (the fix a 409 `CODE_RENDERED_ROUTE` names: its `next` gives the
180
+ `route` and the pair's `keys`). On a route the site's app renders, the code reads the keys it
181
+ declares, so the swap is made in code, on a branch, never on the default one:
182
+ 1. Find the anchor. Search the repo for the keys; follow them through props and bindings tables
183
+ to the component that writes the `<a>` (a page passes them to a layout component, which
184
+ passes them to the button component).
185
+ 2. Render it with `BcmsButton` (`@bettercms-ai/astro/components/BcmsButton.astro`, or
186
+ `BcmsButton` from `@bettercms-ai/next`) under ONE key: the link key without `-href`. Pass
187
+ `value` as the entry's button when it holds one, else `{ label, href }` from the copy and
188
+ href the page renders today, so nothing changes until someone edits it. Keep the anchor's
189
+ classes on `class`.
190
+ 3. Remove the old pair's stamps and reads for that anchor (`data-bcms-field`, `data-bcms-props`,
191
+ the lookups of the two old keys). Leave other callers of a shared component working.
192
+ 4. Open a pull request. After it is merged and the site rebuilt, run `editor_setup`, then check
193
+ the page: the anchor carries `data-bcms-button="<key>"` and `get_binding_report` lists no
194
+ unmatched path for it. The button field is added by that build and starts from the pair's
195
+ label and link.
174
196
 
175
197
  ## 6. Preview source
176
198
 
@@ -94,7 +94,9 @@ sitemap), RSS (dated by `publishedAt`), `llms.txt` (one link per published item)
94
94
  `create_page` and model writes. A page that already holds a pair (`primaryCtaText` +
95
95
  `primaryCtaHref`) becomes one button with `update_field` `{ pageId, path: "primaryCtaText",
96
96
  mergeHref: "primaryCtaHref" }`: each entry's DRAFT gets `{ label, href }` and keeps the old values,
97
- so code still reading them keeps rendering; the live copy changes on publish. Inside a component
97
+ so code still reading them keeps rendering; the live copy changes on publish. On a route the
98
+ site's app renders at request time the merge is refused (409 `CODE_RENDERED_ROUTE`): change the
99
+ code to draw the CTA with `BcmsButton`, and the next build adds the button field. Inside a component
98
100
  a CTA is a `button` block whose `props.text` and `props.href` two props target.
99
101
  - Never model SEO columns ([seo.md](seo.md)), Form questions (they live on the Form document) or
100
102
  Global Layout values as content fields.
@@ -83,8 +83,11 @@ bettercms/inputs/<name>.tsx
83
83
 
84
84
  - `<name>` is the `ui.input.name`: lowercase letters, digits and dashes, 1 to 48 characters,
85
85
  starting with a letter (`button-look`, `brand-swatch`).
86
- - The default export is the component. The file also calls `mountDockInput(Component)`, which
87
- speaks the protocol (section 6) so the component only deals with props.
86
+ - The default export is the component, and the file does nothing else. The build wraps it in
87
+ `mountDockInput`, which speaks the protocol (section 6), so the component only deals with props.
88
+ Never call `mountDockInput` in the file: the input would mount twice.
89
+ - The site needs `react`, `react-dom` and `@bettercms-ai/dock-input` in its `devDependencies`
90
+ (plus `@bettercms-ai/kit` for kit components). The build bundles them into the input.
88
91
  - Keep it small and self-contained: kit components and the field's own data, no app imports.
89
92
 
90
93
  ## 6. Protocol summary
@@ -68,7 +68,8 @@ and `get_componentize_plan` proposes nothing more.
68
68
  0. A build brings in only SEO and meta, media, the brand and the models `bcms-content.json`
69
69
  declares. Pages, collections, forms, bindings and components come from `editor_setup`: run it
70
70
  and poll `editor_setup { check: true }` until `ready` before anything below. Run it again
71
- whenever `get_deploy_status` reports `editorSetup` `stale`. It also fills missing meta from the
71
+ whenever `get_deploy_status` reports `editorSetup` `stale`: a re-run flags the auto fields the
72
+ build no longer renders `stale` (hidden in the editor, never deleted) and never one a person edited, so never delete those by hand. It also fills missing meta from the
72
73
  page copy, merges identical components and switches to declared bindings when the build proves
73
74
  them; the receipt stays yours (it is the codemod's account of your repo).
74
75
  1. Explore the repo (the "Read when" column of SKILL.md's reference table names the signals) and
@@ -20,7 +20,9 @@ Two things are called a "language" here, and they are separate:
20
20
 
21
21
  | Piece | How it works |
22
22
  |---|---|
23
- | The project's locales | `localeConfig.locales`, set by the owner in the dashboard (project settings). A `locale` outside it is refused with 400; an empty list means a single-language project |
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 |
24
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 |
25
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 |
26
28
  | Find them | `list_content_entries` `where: { locale: 'fr' }`; default-language rows have a null `locale`, so they match `{ locale: { ne: 'fr' } }`, never `eq` |
@@ -35,8 +37,8 @@ Import derives deeper locale routes (`/fr/about`) as pages in a URL folder (`fr`
35
37
  the language its `<html lang>` declares, and the Content sidebar files them under Pages › <language>.
36
38
  A locale prefix is never a collection, but a collection's localized posts (`/fr/blog/post-1`, its
37
39
  `<html lang>` agreeing) are entries of that collection with `locale` set, `translationOf` the post
38
- their hreflang alternate (else their slug) names, slug `<slug>-<locale>`; the project's locales gain
39
- each language found. An edit to `/about` never reaches `/fr/about`; edit each page or entry.
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.
40
42
 
41
43
  ## 2. Never model
42
44
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bettercms-ai/mcp",
3
- "version": "0.66.6",
3
+ "version": "0.67.1",
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": {