@bettercms-ai/mcp 0.83.0 → 0.83.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/README.md +86 -86
- package/SKILL.md +160 -160
- package/dist/index.js +38 -9
- package/dist/index.js.map +1 -1
- package/dist/kit-catalog.json +1509 -1509
- package/dist/skills/bettercms/SKILL.md +206 -206
- package/dist/skills/bettercms/references/accessibility.md +171 -171
- package/dist/skills/bettercms/references/animation.md +296 -296
- package/dist/skills/bettercms/references/astro.md +148 -148
- package/dist/skills/bettercms/references/bindings.md +366 -366
- package/dist/skills/bettercms/references/componentize.md +185 -185
- package/dist/skills/bettercms/references/components.md +415 -415
- package/dist/skills/bettercms/references/content-experiments.md +62 -62
- package/dist/skills/bettercms/references/content-modeling.md +382 -382
- package/dist/skills/bettercms/references/detection.md +465 -465
- package/dist/skills/bettercms/references/dock-input.md +121 -121
- package/dist/skills/bettercms/references/editor-chrome.md +92 -92
- package/dist/skills/bettercms/references/external-content.md +78 -78
- package/dist/skills/bettercms/references/finish-site.md +377 -377
- package/dist/skills/bettercms/references/forms-and-interactions.md +245 -245
- package/dist/skills/bettercms/references/frameworks.md +159 -159
- package/dist/skills/bettercms/references/getting-started.md +156 -156
- package/dist/skills/bettercms/references/integrations.md +364 -364
- package/dist/skills/bettercms/references/kit-catalog.md +3259 -3259
- package/dist/skills/bettercms/references/kit-scenarios.md +712 -712
- package/dist/skills/bettercms/references/localization.md +85 -85
- package/dist/skills/bettercms/references/media-and-brand.md +181 -181
- package/dist/skills/bettercms/references/next.md +189 -189
- package/dist/skills/bettercms/references/other-frameworks.md +85 -85
- package/dist/skills/bettercms/references/portable-text.md +213 -213
- package/dist/skills/bettercms/references/programmatic-seo.md +98 -98
- package/dist/skills/bettercms/references/publishing.md +289 -289
- package/dist/skills/bettercms/references/seo.md +357 -357
- package/dist/skills/bettercms/references/shopify-headless.md +52 -52
- package/dist/skills/bettercms/references/site-blueprints.md +895 -895
- package/dist/skills/bettercms/references/site-review.md +74 -74
- package/dist/skills/bettercms/references/worked-examples-advanced.md +509 -509
- package/dist/skills/bettercms/references/worked-examples.md +885 -885
- package/dist/skills/bettercms/references/write-limits.md +217 -217
- 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`.
|