@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.
Files changed (40) hide show
  1. package/README.md +86 -86
  2. package/SKILL.md +160 -160
  3. package/dist/index.js +35 -7
  4. package/dist/index.js.map +1 -1
  5. package/dist/kit-catalog.json +1509 -1509
  6. package/dist/skills/bettercms/SKILL.md +206 -206
  7. package/dist/skills/bettercms/references/accessibility.md +171 -171
  8. package/dist/skills/bettercms/references/animation.md +296 -296
  9. package/dist/skills/bettercms/references/astro.md +148 -148
  10. package/dist/skills/bettercms/references/bindings.md +366 -366
  11. package/dist/skills/bettercms/references/componentize.md +185 -185
  12. package/dist/skills/bettercms/references/components.md +415 -415
  13. package/dist/skills/bettercms/references/content-experiments.md +62 -62
  14. package/dist/skills/bettercms/references/content-modeling.md +382 -382
  15. package/dist/skills/bettercms/references/detection.md +465 -465
  16. package/dist/skills/bettercms/references/dock-input.md +121 -121
  17. package/dist/skills/bettercms/references/editor-chrome.md +92 -92
  18. package/dist/skills/bettercms/references/external-content.md +78 -78
  19. package/dist/skills/bettercms/references/finish-site.md +377 -377
  20. package/dist/skills/bettercms/references/forms-and-interactions.md +245 -245
  21. package/dist/skills/bettercms/references/frameworks.md +159 -159
  22. package/dist/skills/bettercms/references/getting-started.md +156 -156
  23. package/dist/skills/bettercms/references/integrations.md +364 -364
  24. package/dist/skills/bettercms/references/kit-catalog.md +3259 -3259
  25. package/dist/skills/bettercms/references/kit-scenarios.md +712 -712
  26. package/dist/skills/bettercms/references/localization.md +85 -85
  27. package/dist/skills/bettercms/references/media-and-brand.md +181 -181
  28. package/dist/skills/bettercms/references/next.md +189 -189
  29. package/dist/skills/bettercms/references/other-frameworks.md +85 -85
  30. package/dist/skills/bettercms/references/portable-text.md +213 -213
  31. package/dist/skills/bettercms/references/programmatic-seo.md +98 -98
  32. package/dist/skills/bettercms/references/publishing.md +289 -289
  33. package/dist/skills/bettercms/references/seo.md +357 -357
  34. package/dist/skills/bettercms/references/shopify-headless.md +52 -52
  35. package/dist/skills/bettercms/references/site-blueprints.md +895 -895
  36. package/dist/skills/bettercms/references/site-review.md +74 -74
  37. package/dist/skills/bettercms/references/worked-examples-advanced.md +509 -509
  38. package/dist/skills/bettercms/references/worked-examples.md +885 -885
  39. package/dist/skills/bettercms/references/write-limits.md +217 -217
  40. package/package.json +1 -1
@@ -1,185 +1,185 @@
1
- ---
2
- title: "Componentizing a site"
3
- description: "Turn a derived, fields-only site into sections backed by components: reading the plan, confirming it, applying it, and the repository half with its receipt."
4
- ---
5
-
6
- # Componentizing a site, and deciding what each band becomes
7
-
8
- Read with [components.md](components.md); § numbers below are that file's. Doctrine: §10 of
9
- `bettercms://playbook/schema`.
10
-
11
- ## 1. Classify every band first
12
-
13
- Look at the source component, the rendered band, its CSS and its props on every route before
14
- deciding. Matching copy or a shared class name does not make two bands one component.
15
-
16
- | Band | Becomes |
17
- |---|---|
18
- | Navigation, footer, a truly sitewide bar | Chrome: a component in the Global Layout (§4) |
19
- | Same structure and the same editor fields on several pages | One component; layout differences are variants of one `sectionType` |
20
- | Different content contract (other fields, other order) | Its own family, even when it looks alike |
21
- | Genuinely unique | Still a section: a one-off `section` block or a single-use component |
22
- | Card, button, badge, quote inside a section | Part of that section: a `group`/`table` prop, or a nested component |
23
-
24
- Inside a section, pick the smallest shape that holds the children:
25
-
26
- - **Small identical items** (FAQ rows, feature bullets, logos): a `group` or `table` prop.
27
- - **One replaceable child with its own layout or variants** (a hero's media treatment): a `slot`
28
- prop. It holds one instance, and never sits inside a `group` or `table` row.
29
- - **Several different children**: the section's ordered children when the set is fixed; a `slot`
30
- prop with `config.multiple` when the editor picks and orders them.
31
-
32
- Never let a component contain itself, directly or through a child.
33
-
34
- ## 2. The componentize lane (a site whose pages `editor_setup` derived)
35
-
36
- 1. `get_componentize_plan { summary: true }` first (no values; the full plan is hundreds of KB,
37
- and `paged: true` reads it 10 pages at a time). It creates nothing and is computed live from
38
- the PUBLISHED pages. Each field group is one section, and every page gets its OWN components
39
- (`home-hero`, `about-hero`); a locale copy reuses its default page's. A collection template
40
- (`pattern: "{slug}"`) is planned once and every prop reads the entry being rendered: only its
41
- prose body and lists of object rows are sections. Read `pending` for what it will not offer:
42
- `NO_GROUP_ROOT` (keys are still the derive lane's own, such as `h1-welcome`: rename them into
43
- families first), `NOT_A_SECTION` (a lone scalar or page metadata; `detail: ENTRY_METADATA` is
44
- a template's frontmatter: author, images, tags and dates stay entry fields; `detail: DECLINED`
45
- is a group the user declined) and `IMAGE_AS_TEXT` (a text field holding an image path; it plans
46
- once the field is an image). A nav, footer or
47
- modal group is `NOT_A_SECTION` with `detail: LAYOUT_CHROME`: chrome is the Layout on every
48
- site, never a page band. Author the real sections among those with `create_component` and
49
- `set_page_content` instead. The other codes need no decision: `ALREADY_COMPONENTIZED` (a
50
- placement exists), `EMPTY_GROUP` (no field holds a value), `NESTED_REPEATER` (a repeater three
51
- deep; two levels become a nested `table` sub-field) and `PAGE_NOT_EMPTY` (the page holds
52
- blocks this lane does not own, and it will not overwrite them).
53
- 2. Show the user the sections, how many components it creates and which pages it rewrites.
54
- Confirm before writing. A group the user says is not a section: `componentize_sections
55
- { digest, sections: [groupKey], pageIds, decline: true }` (`decline: false` undoes it). The
56
- plan is done when it proposes nothing that was not declined.
57
- 3. `componentize_sections` with the plan's `digest` and `dryRun: true`. Read
58
- `components.wouldDuplicate` (§8). A 409 `stale-plan` means the site changed: read the plan
59
- again and confirm again. Then run it for real.
60
- 4. What it writes: each component as a DRAFT, and each page's draft blocks as an ordered list of
61
- instances. By default each instance carries `props.bind` and the words stay in the page's
62
- field group, so bindings and the coverage meter do not move. `copy: "instance"` is the
63
- page-builder model: each placement takes its group's CURRENT draft values into its own
64
- `props.overrides` (a repeater becomes one `table` prop holding every row), records the page
65
- path each came from in `props.source`, and drops `props.bind`. The old fields are kept and
66
- marked `origin: "componentized"`; nothing is deleted. Running it twice is safe: an existing placement comes back as
67
- `ALREADY_COMPONENTIZED`.
68
- 5. `publish_component` for each, then publish the pages. An unpublished component renders
69
- nothing on the live site.
70
- 6. The repository half. With no checkout, `componentize_sections { digest, codemod: true }` runs
71
- the section-order codemod on the repo's own Actions (`skipped` means there is no repo it may
72
- commit to). In a checkout, run the codemod with `--componentize` and a `--receipt` file, commit
73
- and push the files it wrote, and hand that receipt verbatim to `submit_componentize_receipt`. It records
74
- each extracted section as the source of the component with the same slug, so no
75
- `set_component_source` per component is needed. Deploy. A page whose bands could not be
76
- lifted is wrapped where it stands instead (§5).
77
- 7. Output validation, from step 3 of §6.
78
- 8. Editor chrome, last and not optional: the components this lane made have bare props. Load
79
- [editor-chrome.md](editor-chrome.md) and run step 8 of the repair order in
80
- [finish-site.md](finish-site.md) for each one; `extract_component` ends the same way.
81
-
82
- ## 3. Names and groups
83
-
84
- The import's kit entry (`kit: suggested <id> <score>`, the `hint` of its row in `receipt.gate.kit`) is only a suggestion: nothing is recorded, and nothing waits on it. The props already carry the ui that suggestion gives. For each component, read `bettercms://kit/entry/{id}`, check its `htmlSignals` against the section's own HTML and its pitfalls against the props, then confirm it or pick the right one from its category in `bettercms://kit` and name it with `update_component { kitEntry }`. Setting or changing `kitEntry` puts that entry's ui over the derived ui: where the entry specifies a tab, group, control or preview key for a prop, the entry wins; what it says nothing about stays as derived; ui you or a person set is kept. It also carries the entry's rules (showIf, limits, patterns, options, required) onto props that have none of their own. A component whose props still lack some gets a `gate.kit` row with `drift` listing each one as `path.key=value`: add them with `update_component { props }`, or record `kit-drift-accepted` with a note. The `no-kit-match` exemption needs a note.
85
-
86
- Name a family by what it is (`Hero`, `CTA`, `Testimonials`) and each variant by how it looks
87
- (`Hero / Split`, `CTA / Centered`). Never put a page name in a reusable definition; which pages
88
- use it is a usage question (§8). The componentize lane files a section under its page's slug
89
- (`home-hero`) but keeps the name the section's own (`Hero`). Its components go into the Component
90
- Group you pass as `group` (a folder of the dashboard's Components tab), else into one named for
91
- the section family: nav, header, footer or menu into Layout, an unnamed section into Sections,
92
- any other into the family's own name (Hero, FAQ). Keep related fields together in the
93
- order an editor reads them:
94
-
95
- ```text
96
- Hero / Split
97
- Heading · Description
98
- Primary button: Label · Link · Style
99
- Secondary button: Label · Link · Style
100
- Image: Asset · Alt text
101
- ```
102
-
103
- Expose variants and style tokens, never raw class names, CSS strings or HTML.
104
-
105
- **Name props from the code, not the copy.** An import labels a prop by its role (`Text`,
106
- `Heading`) and writes its help from that role, and older imports by the copy itself (`.00`).
107
- Read the section's source and use its own names: `pricing.starterKit.cents` → `Cents`,
108
- `billingFrequency` → `Billing frequency`. Where you know more than the role does, write the
109
- prop's help text: what it is for, where it shows, any limit ("Shown after the price, such as per
110
- month."), never its label or value again. Apply both in one `update_component` call: `propLabel` and
111
- `propHelpText`, keyed by prop key. Repeated cards (plans, features, team members) are ONE `table`
112
- prop whose rows are the cards (`Plans`: Name · Description · Price · Cents · Billing frequency ·
113
- Features · Button), never a set of props per card, so an editor opens one plan at a time and can
114
- add a third. `get_next_steps` lists `labels-are-copy` until every field is named.
115
-
116
- ## 4. Proof, per family
117
-
118
- A write that returned 200 proves nothing about the page. For each family, check in the preview
119
- and after publish:
120
-
121
- - one default edit reaches every instance without an override, and no other;
122
- - one instance override changes only that placement;
123
- - one variant swap keeps the content (read the swap warnings, §3);
124
- - one nested child edit stays inside its own instance;
125
- - one chrome edit reaches every route.
126
-
127
- Then `get_binding_report` and `get_site_composition` (§9).
128
-
129
- ## 5. The page wrap (`--sections`): sections that stay in the site's code
130
-
131
- Lifting a band into a component (§2) needs an inline run and a plan. A page built from imported
132
- section components with props has neither, and it does not need them: `--sections` wraps the
133
- page's top-level sections in `<BcmsSections>`, one named slot each, so the site's own build draws
134
- them in the CMS page's order, with each block's spacing and style, and draws a section added in
135
- the editor between them. It changes no section's markup, props, classes or styles.
136
-
137
- `bettercms init` runs it before the agent starts and commits it locally
138
- (`chore(bettercms): render sections from BetterCMS`); the dispatch of step 6 runs it after
139
- `--componentize`. Do not redo or revert that commit. Pushing it is the user's decision. To run
140
- it yourself, in a clean checkout:
141
-
142
- ```sh
143
- npx @bettercms-ai/convert --sections --root . --dry-run # the plan, per page
144
- npx @bettercms-ai/convert --sections --root .
145
- npx @bettercms-ai/convert --verify-styles --root . # must report no style edits
146
- ```
147
-
148
- A page file that only draws a view component (`<HomeView locale="en" />`) is wrapped in the
149
- view's file, where its sections are written; the dry run names that file per page, and the page
150
- itself only gains the prop that tells the view which page it is ([astro.md](astro.md),
151
- [next.md](next.md)). A lone component that is itself one section is wrapped in the page.
152
-
153
- A page it leaves alone keeps rendering as before, and is listed with one of these:
154
-
155
- | Reason | The page | What you do |
156
- |---|---|---|
157
- | `SECTION_RUN_NOT_FOUND` | Returns markup from several places, or has loose text between sections | Give it one root, wrap loose text in an element, run again; or write the wrap by hand |
158
- | `SECTION_RUN_NOT_CONTIGUOUS` | A child with `slot="…"`, a `<script>` or a `<style>` sits between two sections | Move it above or below the sections, run again |
159
- | `SECTIONS_IN_LAYOUT` | Gives its layout no children: the layout draws the sections | Move the sections into the page, or wrap them by hand in the layout for that one page |
160
- | `SECTION_RUN_DYNAMIC` | A `[slug]` route no collection page has for its item template (a parameter above the last segment, or no collection at that path) | Nothing: its sections stay in code. A collection's own `[slug]` file is wrapped once, for the collection page, and every entry draws that list |
161
- | `CLIENT_COMPONENT` | A `"use client"` page that exports more than the page | Move those exports to a server file and run it again. Any other client page is wrapped: it moves into `page.client.tsx` and its page file becomes the server parent that reads the order |
162
- | `PAGE_PROPS_IN_USE` | Pages Router page on `getInitialProps`, a data function it only re-exports, or a rest parameter | Add `bcmsSections` to its props by hand ([next.md](next.md)) |
163
- | `PAGE_UNKNOWN` | No CMS page for the route | Create and publish the page, run again |
164
- | `DIALECT_UNSUPPORTED` | Markdown, MDX or HTML | Nothing: the body is one `document` field |
165
- | `NAME_TAKEN` | Already uses the name `BcmsSections` for something else | Rename that, run again |
166
- | `SDK_REQUIRED` | The repo's SDK predates the component | Add `--add-sdk`, install, run again |
167
- | `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 |
168
- | `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)) |
169
- | `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 |
170
-
171
- The by-hand markup is in [astro.md](astro.md) and [next.md](next.md), and it follows the rules of
172
- [animation.md](animation.md) §10: slot tags around sections you do not edit, no new element. On
173
- a wrapped page two notes ask for a hand edit. `ROOT_IS_COMPONENT` (Next): the section moves and
174
- takes its spacing on a box around it, which is one block box once styled; write its child as a
175
- function and spread the root props on the component's root element to style the root itself.
176
- `EXPRESSION`: a `.map()` or a conditional is ONE section; to move its rows separately, give each
177
- row its own slot.
178
-
179
- Slots are written `code:<bind>` unless the page already places that bind; a `code:` slot is
180
- never removed by the list. After the page's placements exist, run `--sections` again (or
181
- `bettercms init`): it writes those slots bare, and from then on removing the block removes the
182
- section. Any other framework gets `FRAMEWORK_UNSUPPORTED` and no write.
183
-
184
- Check: after the build, the route's row in `get_binding_report` reads `stamped` equal to `placed`
185
- and `ordered: true`.
1
+ ---
2
+ title: "Componentizing a site"
3
+ description: "Turn a derived, fields-only site into sections backed by components: reading the plan, confirming it, applying it, and the repository half with its receipt."
4
+ ---
5
+
6
+ # Componentizing a site, and deciding what each band becomes
7
+
8
+ Read with [components.md](components.md); § numbers below are that file's. Doctrine: §10 of
9
+ `bettercms://playbook/schema`.
10
+
11
+ ## 1. Classify every band first
12
+
13
+ Look at the source component, the rendered band, its CSS and its props on every route before
14
+ deciding. Matching copy or a shared class name does not make two bands one component.
15
+
16
+ | Band | Becomes |
17
+ |---|---|
18
+ | Navigation, footer, a truly sitewide bar | Chrome: a component in the Global Layout (§4) |
19
+ | Same structure and the same editor fields on several pages | One component; layout differences are variants of one `sectionType` |
20
+ | Different content contract (other fields, other order) | Its own family, even when it looks alike |
21
+ | Genuinely unique | Still a section: a one-off `section` block or a single-use component |
22
+ | Card, button, badge, quote inside a section | Part of that section: a `group`/`table` prop, or a nested component |
23
+
24
+ Inside a section, pick the smallest shape that holds the children:
25
+
26
+ - **Small identical items** (FAQ rows, feature bullets, logos): a `group` or `table` prop.
27
+ - **One replaceable child with its own layout or variants** (a hero's media treatment): a `slot`
28
+ prop. It holds one instance, and never sits inside a `group` or `table` row.
29
+ - **Several different children**: the section's ordered children when the set is fixed; a `slot`
30
+ prop with `config.multiple` when the editor picks and orders them.
31
+
32
+ Never let a component contain itself, directly or through a child.
33
+
34
+ ## 2. The componentize lane (a site whose pages `editor_setup` derived)
35
+
36
+ 1. `get_componentize_plan { summary: true }` first (no values; the full plan is hundreds of KB,
37
+ and `paged: true` reads it 10 pages at a time). It creates nothing and is computed live from
38
+ the PUBLISHED pages. Each field group is one section, and every page gets its OWN components
39
+ (`home-hero`, `about-hero`); a locale copy reuses its default page's. A collection template
40
+ (`pattern: "{slug}"`) is planned once and every prop reads the entry being rendered: only its
41
+ prose body and lists of object rows are sections. Read `pending` for what it will not offer:
42
+ `NO_GROUP_ROOT` (keys are still the derive lane's own, such as `h1-welcome`: rename them into
43
+ families first), `NOT_A_SECTION` (a lone scalar or page metadata; `detail: ENTRY_METADATA` is
44
+ a template's frontmatter: author, images, tags and dates stay entry fields; `detail: DECLINED`
45
+ is a group the user declined) and `IMAGE_AS_TEXT` (a text field holding an image path; it plans
46
+ once the field is an image). A nav, footer or
47
+ modal group is `NOT_A_SECTION` with `detail: LAYOUT_CHROME`: chrome is the Layout on every
48
+ site, never a page band. Author the real sections among those with `create_component` and
49
+ `set_page_content` instead. The other codes need no decision: `ALREADY_COMPONENTIZED` (a
50
+ placement exists), `EMPTY_GROUP` (no field holds a value), `NESTED_REPEATER` (a repeater three
51
+ deep; two levels become a nested `table` sub-field) and `PAGE_NOT_EMPTY` (the page holds
52
+ blocks this lane does not own, and it will not overwrite them).
53
+ 2. Show the user the sections, how many components it creates and which pages it rewrites.
54
+ Confirm before writing. A group the user says is not a section: `componentize_sections
55
+ { digest, sections: [groupKey], pageIds, decline: true }` (`decline: false` undoes it). The
56
+ plan is done when it proposes nothing that was not declined.
57
+ 3. `componentize_sections` with the plan's `digest` and `dryRun: true`. Read
58
+ `components.wouldDuplicate` (§8). A 409 `stale-plan` means the site changed: read the plan
59
+ again and confirm again. Then run it for real.
60
+ 4. What it writes: each component as a DRAFT, and each page's draft blocks as an ordered list of
61
+ instances. By default each instance carries `props.bind` and the words stay in the page's
62
+ field group, so bindings and the coverage meter do not move. `copy: "instance"` is the
63
+ page-builder model: each placement takes its group's CURRENT draft values into its own
64
+ `props.overrides` (a repeater becomes one `table` prop holding every row), records the page
65
+ path each came from in `props.source`, and drops `props.bind`. The old fields are kept and
66
+ marked `origin: "componentized"`; nothing is deleted. Running it twice is safe: an existing placement comes back as
67
+ `ALREADY_COMPONENTIZED`.
68
+ 5. `publish_component` for each, then publish the pages. An unpublished component renders
69
+ nothing on the live site.
70
+ 6. The repository half. With no checkout, `componentize_sections { digest, codemod: true }` runs
71
+ the section-order codemod on the repo's own Actions (`skipped` means there is no repo it may
72
+ commit to). In a checkout, run the codemod with `--componentize` and a `--receipt` file, commit
73
+ and push the files it wrote, and hand that receipt verbatim to `submit_componentize_receipt`. It records
74
+ each extracted section as the source of the component with the same slug, so no
75
+ `set_component_source` per component is needed. Deploy. A page whose bands could not be
76
+ lifted is wrapped where it stands instead (§5).
77
+ 7. Output validation, from step 3 of §6.
78
+ 8. Editor chrome, last and not optional: the components this lane made have bare props. Load
79
+ [editor-chrome.md](editor-chrome.md) and run step 8 of the repair order in
80
+ [finish-site.md](finish-site.md) for each one; `extract_component` ends the same way.
81
+
82
+ ## 3. Names and groups
83
+
84
+ The import's kit entry (`kit: suggested <id> <score>`, the `hint` of its row in `receipt.gate.kit`) is only a suggestion: nothing is recorded, and nothing waits on it. The props already carry the ui that suggestion gives. For each component, read `bettercms://kit/entry/{id}`, check its `htmlSignals` against the section's own HTML and its pitfalls against the props, then confirm it or pick the right one from its category in `bettercms://kit` and name it with `update_component { kitEntry }`. Setting or changing `kitEntry` puts that entry's ui over the derived ui: where the entry specifies a tab, group, control or preview key for a prop, the entry wins; what it says nothing about stays as derived; ui you or a person set is kept. It also carries the entry's rules (showIf, limits, patterns, options, required) onto props that have none of their own. A component whose props still lack some gets a `gate.kit` row with `drift` listing each one as `path.key=value`: add them with `update_component { props }`, or record `kit-drift-accepted` with a note. The `no-kit-match` exemption needs a note.
85
+
86
+ Name a family by what it is (`Hero`, `CTA`, `Testimonials`) and each variant by how it looks
87
+ (`Hero / Split`, `CTA / Centered`). Never put a page name in a reusable definition; which pages
88
+ use it is a usage question (§8). The componentize lane files a section under its page's slug
89
+ (`home-hero`) but keeps the name the section's own (`Hero`). Its components go into the Component
90
+ Group you pass as `group` (a folder of the dashboard's Components tab), else into one named for
91
+ the section family: nav, header, footer or menu into Layout, an unnamed section into Sections,
92
+ any other into the family's own name (Hero, FAQ). Keep related fields together in the
93
+ order an editor reads them:
94
+
95
+ ```text
96
+ Hero / Split
97
+ Heading · Description
98
+ Primary button: Label · Link · Style
99
+ Secondary button: Label · Link · Style
100
+ Image: Asset · Alt text
101
+ ```
102
+
103
+ Expose variants and style tokens, never raw class names, CSS strings or HTML.
104
+
105
+ **Name props from the code, not the copy.** An import labels a prop by its role (`Text`,
106
+ `Heading`) and writes its help from that role, and older imports by the copy itself (`.00`).
107
+ Read the section's source and use its own names: `pricing.starterKit.cents` → `Cents`,
108
+ `billingFrequency` → `Billing frequency`. Where you know more than the role does, write the
109
+ prop's help text: what it is for, where it shows, any limit ("Shown after the price, such as per
110
+ month."), never its label or value again. Apply both in one `update_component` call: `propLabel` and
111
+ `propHelpText`, keyed by prop key. Repeated cards (plans, features, team members) are ONE `table`
112
+ prop whose rows are the cards (`Plans`: Name · Description · Price · Cents · Billing frequency ·
113
+ Features · Button), never a set of props per card, so an editor opens one plan at a time and can
114
+ add a third. `get_next_steps` lists `labels-are-copy` until every field is named.
115
+
116
+ ## 4. Proof, per family
117
+
118
+ A write that returned 200 proves nothing about the page. For each family, check in the preview
119
+ and after publish:
120
+
121
+ - one default edit reaches every instance without an override, and no other;
122
+ - one instance override changes only that placement;
123
+ - one variant swap keeps the content (read the swap warnings, §3);
124
+ - one nested child edit stays inside its own instance;
125
+ - one chrome edit reaches every route.
126
+
127
+ Then `get_binding_report` and `get_site_composition` (§9).
128
+
129
+ ## 5. The page wrap (`--sections`): sections that stay in the site's code
130
+
131
+ Lifting a band into a component (§2) needs an inline run and a plan. A page built from imported
132
+ section components with props has neither, and it does not need them: `--sections` wraps the
133
+ page's top-level sections in `<BcmsSections>`, one named slot each, so the site's own build draws
134
+ them in the CMS page's order, with each block's spacing and style, and draws a section added in
135
+ the editor between them. It changes no section's markup, props, classes or styles.
136
+
137
+ `bettercms init` runs it before the agent starts and commits it locally
138
+ (`chore(bettercms): render sections from BetterCMS`); the dispatch of step 6 runs it after
139
+ `--componentize`. Do not redo or revert that commit. Pushing it is the user's decision. To run
140
+ it yourself, in a clean checkout:
141
+
142
+ ```sh
143
+ npx @bettercms-ai/convert --sections --root . --dry-run # the plan, per page
144
+ npx @bettercms-ai/convert --sections --root .
145
+ npx @bettercms-ai/convert --verify-styles --root . # must report no style edits
146
+ ```
147
+
148
+ A page file that only draws a view component (`<HomeView locale="en" />`) is wrapped in the
149
+ view's file, where its sections are written; the dry run names that file per page, and the page
150
+ itself only gains the prop that tells the view which page it is ([astro.md](astro.md),
151
+ [next.md](next.md)). A lone component that is itself one section is wrapped in the page.
152
+
153
+ A page it leaves alone keeps rendering as before, and is listed with one of these:
154
+
155
+ | Reason | The page | What you do |
156
+ |---|---|---|
157
+ | `SECTION_RUN_NOT_FOUND` | Returns markup from several places, or has loose text between sections | Give it one root, wrap loose text in an element, run again; or write the wrap by hand |
158
+ | `SECTION_RUN_NOT_CONTIGUOUS` | A child with `slot="…"`, a `<script>` or a `<style>` sits between two sections | Move it above or below the sections, run again |
159
+ | `SECTIONS_IN_LAYOUT` | Gives its layout no children: the layout draws the sections | Move the sections into the page, or wrap them by hand in the layout for that one page |
160
+ | `SECTION_RUN_DYNAMIC` | A `[slug]` route no collection page has for its item template (a parameter above the last segment, or no collection at that path) | Nothing: its sections stay in code. A collection's own `[slug]` file is wrapped once, for the collection page, and every entry draws that list |
161
+ | `CLIENT_COMPONENT` | A `"use client"` page that exports more than the page | Move those exports to a server file and run it again. Any other client page is wrapped: it moves into `page.client.tsx` and its page file becomes the server parent that reads the order |
162
+ | `PAGE_PROPS_IN_USE` | Pages Router page on `getInitialProps`, a data function it only re-exports, or a rest parameter | Add `bcmsSections` to its props by hand ([next.md](next.md)) |
163
+ | `PAGE_UNKNOWN` | No CMS page for the route | Create and publish the page, run again |
164
+ | `DIALECT_UNSUPPORTED` | Markdown, MDX or HTML | Nothing: the body is one `document` field |
165
+ | `NAME_TAKEN` | Already uses the name `BcmsSections` for something else | Rename that, run again |
166
+ | `SDK_REQUIRED` | The repo's SDK predates the component | Add `--add-sdk`, install, run again |
167
+ | `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 |
168
+ | `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)) |
169
+ | `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 |
170
+
171
+ The by-hand markup is in [astro.md](astro.md) and [next.md](next.md), and it follows the rules of
172
+ [animation.md](animation.md) §10: slot tags around sections you do not edit, no new element. On
173
+ a wrapped page two notes ask for a hand edit. `ROOT_IS_COMPONENT` (Next): the section moves and
174
+ takes its spacing on a box around it, which is one block box once styled; write its child as a
175
+ function and spread the root props on the component's root element to style the root itself.
176
+ `EXPRESSION`: a `.map()` or a conditional is ONE section; to move its rows separately, give each
177
+ row its own slot.
178
+
179
+ Slots are written `code:<bind>` unless the page already places that bind; a `code:` slot is
180
+ never removed by the list. After the page's placements exist, run `--sections` again (or
181
+ `bettercms init`): it writes those slots bare, and from then on removing the block removes the
182
+ section. Any other framework gets `FRAMEWORK_UNSUPPORTED` and no write.
183
+
184
+ Check: after the build, the route's row in `get_binding_report` reads `stamped` equal to `placed`
185
+ and `ordered: true`.