@bettercms-ai/mcp 0.71.0 → 0.72.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +364 -45
- package/dist/index.js.map +1 -1
- package/dist/skills/bettercms/SKILL.md +9 -6
- package/dist/skills/bettercms/references/animation.md +21 -5
- package/dist/skills/bettercms/references/astro.md +45 -0
- package/dist/skills/bettercms/references/bindings.md +15 -2
- package/dist/skills/bettercms/references/componentize.md +65 -6
- package/dist/skills/bettercms/references/components.md +37 -10
- package/dist/skills/bettercms/references/content-modeling.md +6 -1
- package/dist/skills/bettercms/references/finish-site.md +116 -9
- package/dist/skills/bettercms/references/forms-and-interactions.md +7 -0
- package/dist/skills/bettercms/references/getting-started.md +2 -0
- package/dist/skills/bettercms/references/media-and-brand.md +20 -3
- package/dist/skills/bettercms/references/next.md +59 -0
- package/dist/skills/bettercms/references/publishing.md +75 -7
- package/dist/skills/bettercms/references/seo.md +2 -1
- package/package.json +1 -1
|
@@ -59,7 +59,10 @@ These hold on every task. The reference in brackets has the detail and the codes
|
|
|
59
59
|
beside a link field. [components.md, content-modeling.md]
|
|
60
60
|
4. **Nothing is live until it is published, and a page is not its content.** A draft form,
|
|
61
61
|
component or entry renders nothing live. Read `warnings` on every write: `ENTRY_STILL_DRAFT`
|
|
62
|
-
and `ENTRY_SEED_FAILED` mean content did not ship.
|
|
62
|
+
and `ENTRY_SEED_FAILED` mean content did not ship. An answer that leads with `held`, and a
|
|
63
|
+
publish reading `outcome.status: "partial"`, did not reach the site: do the action it names
|
|
64
|
+
before you report it.
|
|
65
|
+
[publishing.md]
|
|
63
66
|
5. **Name the folder system every time you say "folder"**: Content sidebar folders never touch a
|
|
64
67
|
URL, URL page folders are part of the path, media folders organise assets.
|
|
65
68
|
[content-modeling.md]
|
|
@@ -126,7 +129,7 @@ resources: `bettercms://skill/bettercms` for this file, and each reference under
|
|
|
126
129
|
| Components, props, variants, component publish codes | [components.md](references/components.md) | creating, swapping, publishing or validating a component | component, prop, variant, sectionType, publish component |
|
|
127
130
|
| One section, source HTML to bound template: hero, feature grid, pricing table, testimonials, FAQ | [worked-examples.md](references/worked-examples.md) | you want the whole path for one section in production shapes | example, hero, pricing, FAQ, testimonial, source to schema |
|
|
128
131
|
| The same for an article entry, navigation in the Global Layout, tabs holding columns | [worked-examples-advanced.md](references/worked-examples-advanced.md) | a collection entry with a body, site chrome, or nested blocks | example, blog post, document body, mega menu, tabs, columns |
|
|
129
|
-
| Componentize a derived site | [componentize.md](references/componentize.md) | `get_site_composition` shows pages made of fields only | componentize, sections, fields-only, get_componentize_plan |
|
|
132
|
+
| Componentize a derived site | [componentize.md](references/componentize.md) | `get_site_composition` shows pages made of fields only | componentize, sections, fields-only, get_componentize_plan, BcmsSections, --sections |
|
|
130
133
|
| Click-to-edit attributes, addresses, declared mode | [bindings.md](references/bindings.md) | `grep -rl "data-bcms-" src` finds files; a value the editor cannot select | data-bcms-field, click to edit, binding report, stega, declared |
|
|
131
134
|
| Animated sites: GSAP, ScrollTrigger, split text, Framer Motion, Lenis, marquees, carousels, Lottie, hydrated islands | [animation.md](references/animation.md) | `package.json` lists `gsap`, `lenis`, `framer-motion`, `swiper` or a text splitter; a publish refused with `SPLIT_PATTERN_UNKNOWN` or `ISLAND_PROPS_UNMATCHED`; a `client-only-island` gate exception | animation, GSAP, ScrollTrigger, SplitText, Lenis, Framer Motion, marquee, Swiper, Lottie, island, client:only, data-bcms-split |
|
|
132
135
|
| Wiring, env, hosting modes, media host, snapshots | [frameworks.md](references/frameworks.md) | `package.json` lists an adapter; `bcms-content.json` exists | hosting, headless, env, codegen, types, revalidate, snapshot |
|
|
@@ -140,7 +143,7 @@ resources: `bettercms://skill/bettercms` for this file, and each reference under
|
|
|
140
143
|
| SEO and AEO | [seo.md](references/seo.md) | meta, JSON-LD, sitemap, robots, redirects, AI crawlers, `list_seo_issues` | SEO, meta, JSON-LD, schema.org, sitemap, robots, OG image, redirect, 410, llms.txt |
|
|
141
144
|
| Accessibility: alt text, form labels, contrast, the audit codes and the manual checks | [accessibility.md](references/accessibility.md) | writing alt text; building a form; before reporting a site done | accessibility, WCAG, alt text, decorative, label, contrast, captions |
|
|
142
145
|
| Page families from data | [programmatic-seo.md](references/programmatic-seo.md) | a keyword list or CSV should become pages | programmatic SEO, CSV, dataset, landing pages, glossary |
|
|
143
|
-
| Publish, deploy, roll back, reconcile a re-import | [publishing.md](references/publishing.md) | a release; a code push that conflicts with CMS edits | publish, deploy, rollback, sync, webhook, schedule, automation |
|
|
146
|
+
| Publish, deploy, roll back, reconcile a re-import; add a section to a page or set its spacing | [publishing.md](references/publishing.md) | a release; a code push that conflicts with CMS edits; "add a section here", "more padding on this section"; a write that answers `held` or a publish that reads `partial` | publish, deploy, rollback, sync, webhook, schedule, automation, add section, insertSection, sectionStyle, spacing, padding, margin, held, partial |
|
|
144
147
|
| Read-only launch review | [site-review.md](references/site-review.md) | "is this ready to launch?" without fixing | launch review, audit, is it ready, QA |
|
|
145
148
|
| A/B tests on section variants | [content-experiments.md](references/content-experiments.md) | an experiment plan or readout | A/B test, experiment, variant test |
|
|
146
149
|
| Headless Shopify storefront | [shopify-headless.md](references/shopify-headless.md) | Shopify owns commerce, BetterCMS the editorial | Shopify, Hydrogen, storefront, product |
|
|
@@ -182,9 +185,9 @@ Stop at the first step that is not clean, fix, then restart. A report that does
|
|
|
182
185
|
3. `get_site_composition`: every page you touched is made of sections, with the intended
|
|
183
186
|
components.
|
|
184
187
|
4. `list_seo_issues`: no issue on a page you created or edited.
|
|
185
|
-
5. `get_next_steps`: `receipt.complete` is `true`. While it is `false`,
|
|
186
|
-
`receipt.gate.
|
|
187
|
-
setup. The gate, its bars and the exceptions it accepts are in
|
|
188
|
+
5. `get_next_steps`: `receipt.complete` is `true`. While it is `false`, run
|
|
189
|
+
`receipt.gate.nextAction` and read again after each batch; nothing else ends an imported
|
|
190
|
+
site's setup, and an exception is recorded only for a reason that is true. The gate, its bars and the exceptions it accepts are in
|
|
188
191
|
[finish-site.md](references/finish-site.md). Suggestions still in `missing` after that, and
|
|
189
192
|
owner actions, go in your final message by name.
|
|
190
193
|
|
|
@@ -12,9 +12,10 @@ Read this when `package.json` lists `gsap`, `lenis`, `framer-motion` or `motion`
|
|
|
12
12
|
|
|
13
13
|
## 1. What a publish does to animated markup
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
text nodes and
|
|
15
|
+
The bound element picks the writer. One that holds text and nothing else is replaced whole, as
|
|
16
|
+
it always was. One that holds markup (inline elements, split spans, a `<br>`, or
|
|
17
|
+
`data-bcms-split`) is written into its text nodes, and every element inside it is kept. That is
|
|
18
|
+
what the recipes below rely on:
|
|
18
19
|
|
|
19
20
|
| The page has | A publish does |
|
|
20
21
|
|---|---|
|
|
@@ -22,6 +23,8 @@ text nodes and keeps every element inside it. That is what the recipes below rel
|
|
|
22
23
|
| Text cut into `word` / `char` / `line` spans, same word count | writes each span in place |
|
|
23
24
|
| The same, word count changed, spans made by a library at runtime | puts the plain text back; the library splits it on its next run |
|
|
24
25
|
| The same, spans written by your own template | rebuilds them from their own pattern, a new one copied from the last |
|
|
26
|
+
| A split headline under an `aria-label` that says the same words (SplitText's own output) | one field on the headline: writes the label and the words |
|
|
27
|
+
| A counter with its number in `data-target`, `data-count` or a like attribute | writes the attribute; writes the number shown only where it showed the old target, never a script's starting `0` |
|
|
25
28
|
| A copy of the value under `aria-hidden="true"` or `data-bcms-clone` | writes the copy too (coverage kind `duplicate-track`) |
|
|
26
29
|
| The value inside `astro-island` props, `__NEXT_DATA__` or the app router's flight scripts | rewrites the serialized string (coverage kind `island-props`) |
|
|
27
30
|
|
|
@@ -32,8 +35,10 @@ It refuses, and says so on the publish, when the change would have to remove som
|
|
|
32
35
|
`ISLAND_PROPS_UNMATCHED` (a hydrated island draws the text from props that do not hold it).
|
|
33
36
|
Each is fixed in the template with the recipe for its library.
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
38
|
+
The last two rows follow a value only where the element was written into its text nodes. An
|
|
39
|
+
operator can switch a project, or the fleet, to write every text value that way, so a plain-text
|
|
40
|
+
value's copies and props follow it too; and can switch the text-node writer off, after which a
|
|
41
|
+
publish replaces an element's content with plain text and refuses what that would break.
|
|
37
42
|
|
|
38
43
|
## 2. Two rules for every library
|
|
39
44
|
|
|
@@ -218,6 +223,17 @@ the heading. Text, several children, and in Next a component child are still wra
|
|
|
218
223
|
`display:contents` span, and `as="div"` still gives a real wrapper. A site built with an older
|
|
219
224
|
package version keeps its wrapper spans until it is rebuilt.
|
|
220
225
|
|
|
226
|
+
`<BcmsSections>` follows the same rule for a whole section: the block id, the style marker and
|
|
227
|
+
the block's style go on the section's own root, and in Astro the list is delimited by two HTML
|
|
228
|
+
comments, never an element. `main > section`, `:first-child` and a ScrollTrigger on `.hero` match
|
|
229
|
+
what they matched before. The one exception is a section that is not one element (several roots,
|
|
230
|
+
an island, in Next a component child with no function form): it gets a box of ours, a node
|
|
231
|
+
between `main` and the section. With no style on the block the box is `display:contents` and
|
|
232
|
+
draws nothing. Once the block has a style the box is `display:flow-root` and carries it: one
|
|
233
|
+
block box, so in a flex or grid parent its children are one item instead of several, and their
|
|
234
|
+
margins stop collapsing with the neighbours'. Give such a section one root element, or the
|
|
235
|
+
function form ([next.md](next.md)), when a selector or the layout depends on it.
|
|
236
|
+
|
|
221
237
|
## 11. Lottie, canvas, WebGL, video
|
|
222
238
|
|
|
223
239
|
Pixels drawn by a script are not in the HTML, so nothing in them is a field by itself. What is
|
|
@@ -40,6 +40,7 @@ build. Pass `apiUrl` and `workspace` as options, or let the snapshot supply them
|
|
|
40
40
|
| Import | Use |
|
|
41
41
|
|---|---|
|
|
42
42
|
| `@bettercms-ai/astro/components/BcmsBlocks.astro` | Server render of a block tree. Pass `components` or instances render nothing |
|
|
43
|
+
| `@bettercms-ai/astro/components/BcmsSections.astro` | The page's own sections as named slots, drawn in the CMS page's order (below) |
|
|
43
44
|
| `@bettercms-ai/astro/components/BcmsLiveBlocks.astro` | Same tree as a hydrated island that follows the editor's draft bridge |
|
|
44
45
|
| `@bettercms-ai/astro/components/BcmsField.astro` | `path`, `kind` (`richtext`/`image`), `as`; emits `data-bcms-field` + `data-bcms-kind` on its single child element, with no wrapper ([animation.md](animation.md) §10) |
|
|
45
46
|
| `@bettercms-ai/astro/components/BcmsButton.astro` | A `button` field: `path`, `value`, `class`. One `<a>` with the label bound, the href in `data-bcms-props` and `data-variant` / `data-size`. Style only the looks the site has ([components.md](components.md) §5); renders nothing without a label |
|
|
@@ -51,6 +52,48 @@ build. Pass `apiUrl` and `workspace` as options, or let the snapshot supply them
|
|
|
51
52
|
- Its CSS class names differ from `BcmsBlocks`. Swapping one for the other unstyles a page unless
|
|
52
53
|
your stylesheet covers both spellings.
|
|
53
54
|
|
|
55
|
+
## Sections in the CMS order
|
|
56
|
+
|
|
57
|
+
A page whose sections are written in its `.astro` file follows the CMS page's block list through
|
|
58
|
+
`<BcmsSections>`. `bettercms init` writes this wrap (`npx @bettercms-ai/convert --sections`);
|
|
59
|
+
write it by hand for a page the codemod refused ([componentize.md](componentize.md) §5):
|
|
60
|
+
|
|
61
|
+
```astro
|
|
62
|
+
---
|
|
63
|
+
import BcmsSections from "@bettercms-ai/astro/components/BcmsSections.astro";
|
|
64
|
+
---
|
|
65
|
+
<Layout>
|
|
66
|
+
<BcmsSections page="home">
|
|
67
|
+
<Fragment slot="hero"><Hero title={title} /></Fragment>
|
|
68
|
+
<Fragment slot="code:sec1"><LogoStrip /></Fragment>
|
|
69
|
+
<Fragment slot="pricing">{plans.length > 0 && <Pricing plans={plans} />}</Fragment>
|
|
70
|
+
</BcmsSections>
|
|
71
|
+
</Layout>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- `page` is the CMS page's slug. Each section is one `<Fragment slot="…">` around markup you do
|
|
75
|
+
not change. Anything with its own `slot="…"` (it targets the layout) stays outside.
|
|
76
|
+
- A slot is named by its placement's bind: the family of the section's first bound field
|
|
77
|
+
(`pricing-title` → `pricing`), which is the `bind` on the page's `component` block.
|
|
78
|
+
- A block bound to a slot renders it at the block's position. `data-bcms-block`, `data-bcms-style`
|
|
79
|
+
and the block's style go on the section's own root element; no element is added, so the stamp
|
|
80
|
+
never comes between a parent and a child a selector or an animation names
|
|
81
|
+
([animation.md](animation.md) §10). A slot that renders several roots, or an island, is stamped
|
|
82
|
+
on a box: `display:contents` while its block has no style, one `display:flow-root` box once it
|
|
83
|
+
has. Give it one root element where the layout must not gain a box.
|
|
84
|
+
- A section added in the editor (a block with no bind) is drawn by `BcmsBlocks`, which
|
|
85
|
+
`BcmsSections` imports itself: nothing to pass, and it builds whether or not `bettercms()` is
|
|
86
|
+
registered on that build (a config that adds it only with credentials still draws the section).
|
|
87
|
+
- A slot no block names: `code:<bind>` always renders where it is written. A bare `<bind>` does
|
|
88
|
+
too until the list names one of the page's slots; from then on it is a removed section. Write
|
|
89
|
+
a slot bare only when the page already places that bind (`get_page`), otherwise `code:`.
|
|
90
|
+
- No snapshot (a local build, the API down): every slot renders in source order, untouched.
|
|
91
|
+
- The list is the published snapshot's, or the draft's when the Visual Editor frames a
|
|
92
|
+
server-rendered page (`prerender = false`). `blocks={…}` gives it yourself.
|
|
93
|
+
|
|
94
|
+
Check: build, then view source. `<!--bcms-sections:start page="home"-->` precedes the sections,
|
|
95
|
+
and each placed section's root carries `data-bcms-block` and `data-bcms-style`.
|
|
96
|
+
|
|
54
97
|
## Bindings
|
|
55
98
|
|
|
56
99
|
A section root your own code renders spreads `sectionRootAttrs(block)` from `@bettercms-ai/astro`
|
|
@@ -72,5 +115,7 @@ helpers. Use them inside component instances so addresses resolve to the instanc
|
|
|
72
115
|
| Resized images 404 on a non-production site | `mediaUrl` still the production default | Set `PUBLIC_BCMS_MEDIA_URL` to that rung's API origin |
|
|
73
116
|
| Resized images still on the CDN after moving media | `mediaUrl` not changed | Set it to `mediaBaseUrl` from `get_project` |
|
|
74
117
|
| Component instances empty | `components` prop missing | Pass definitions to `BcmsBlocks` |
|
|
118
|
+
| A code section vanished after a build | Its slot is bare and the page's block list does not place its bind | Name it `code:<bind>`, or place the bind on the page |
|
|
119
|
+
| Build fails on `bettercms:config` | A page imports `BcmsSearch` or the virtual module on a build where `bettercms()` is not registered | Register it unconditionally. `BcmsSections`, `BcmsBlocks`, `BcmsImage` and `BcmsForm` need no integration |
|
|
75
120
|
| Draft link 500 | `BCMS_DRAFT_SECRET` unset | Set it on the server env, redeploy |
|
|
76
121
|
| `ReferenceError: X is not defined` from a `[slug].astro` route | `getStaticPaths()` reads a `const` declared in the frontmatter; Astro runs it in its own scope, before the page, seeing only imports | Declare the client or reader call inside `getStaticPaths`, or import it from a module |
|
|
@@ -68,7 +68,19 @@ value over everything else inside it, so:
|
|
|
68
68
|
`button` field rendered with `BcmsButton` ([components.md](components.md)).
|
|
69
69
|
- **SVG text is never bound; the icon is.** No field is derived from the text inside an `<svg>`,
|
|
70
70
|
and no binding there is measured. Each inline icon IS an editable `image` field (`config.media:
|
|
71
|
-
"icon"`, an svg value): an editor swaps it. Never remove one to tidy a form; group it.
|
|
71
|
+
"icon"`, an svg value): an editor swaps it. Never remove one to tidy a form; group it. So is a
|
|
72
|
+
sprite icon (`<svg><use href="#id">`) whose `<symbol>` is in the same page, a wordmark set in
|
|
73
|
+
`<text>`, and an illustration up to 64 KB. A swap keeps the element's classes and size and
|
|
74
|
+
stays inline, so `currentColor` still applies; scripts and remote references are stripped.
|
|
75
|
+
- **Copy in an attribute is a field of its own.** Every release derives a text field for an
|
|
76
|
+
`aria-label`, a `title`, a `placeholder` outside a form, and the number a count-up script
|
|
77
|
+
counts to (`data-target`, `data-count`, `data-value` and their kin). Its `config.attr` names the
|
|
78
|
+
attribute; it is bound on `data-bcms-props` as `<path>|text|<attribute>` and published into that
|
|
79
|
+
attribute alone, never matched against the page's words. A label that only repeats the words
|
|
80
|
+
on its own element gets none. To declare one in code, write the same `data-bcms-props` entry.
|
|
81
|
+
- **Text beside an icon in a plain element** (`<span>Pages<svg/></span>`) is a field when it is
|
|
82
|
+
the element's one text node: the save writes that node and leaves the icon. Split over several
|
|
83
|
+
text nodes it stays `BARE_TEXT`, so wrap the label in its own `<span>` where you can.
|
|
72
84
|
- **Icon-only links in the nav and footer** take their field name from what a screen reader
|
|
73
85
|
reads: the anchor's `aria-label`, its `sr-only` text, a `title`, the SVG `<title>` or an `alt`
|
|
74
86
|
(never `<style>` or `<script>` text). Give each one an `aria-label` or `sr-only` label, or every
|
|
@@ -107,7 +119,8 @@ picture a page shows: an `<img>` (its `src`, or where that is a placeholder a la
|
|
|
107
119
|
`data-src`, then the largest `srcset` candidate), a whole `<picture>` as ONE field, a
|
|
108
120
|
`<video poster>`, an inline or STYLESHEET `background-image` (Tailwind `bg-[url(...)]` included)
|
|
109
121
|
and an inline `<svg>`. What it declines is listed with a reason in the binding report's
|
|
110
|
-
`imageGaps
|
|
122
|
+
`imageGaps`, and everything else it declines in `elementGaps`
|
|
123
|
+
([media-and-brand.md](media-and-brand.md) Where images hide).
|
|
111
124
|
|
|
112
125
|
Publishing an image keeps the page responsive. A library image re-points every `srcset`
|
|
113
126
|
candidate and each `<source>` at the new picture at that candidate's own width and format, and
|
|
@@ -46,7 +46,10 @@ Never let a component contain itself, directly or through a child.
|
|
|
46
46
|
once the field is an image). A nav, footer or
|
|
47
47
|
modal group is `NOT_A_SECTION` with `detail: LAYOUT_CHROME`: chrome is the Layout on every
|
|
48
48
|
site, never a page band. Author the real sections among those with `create_component` and
|
|
49
|
-
`set_page_content` instead.
|
|
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).
|
|
50
53
|
2. Show the user the sections, how many components it creates and which pages it rewrites.
|
|
51
54
|
Confirm before writing. A group the user says is not a section: `componentize_sections
|
|
52
55
|
{ digest, sections: [groupKey], pageIds, decline: true }` (`decline: false` undoes it). The
|
|
@@ -56,9 +59,11 @@ Never let a component contain itself, directly or through a child.
|
|
|
56
59
|
again and confirm again. Then run it for real.
|
|
57
60
|
4. What it writes: each component as a DRAFT, and each page's draft blocks as an ordered list of
|
|
58
61
|
instances. By default each instance carries `props.bind` and the words stay in the page's
|
|
59
|
-
field group, so bindings and the coverage meter do not move. `copy: "instance"`
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
62
67
|
`ALREADY_COMPONENTIZED`.
|
|
63
68
|
5. `publish_component` for each, then publish the pages. An unpublished component renders
|
|
64
69
|
nothing on the live site.
|
|
@@ -67,7 +72,8 @@ Never let a component contain itself, directly or through a child.
|
|
|
67
72
|
commit to). In a checkout, run the codemod with `--componentize` and a `--receipt` file, commit
|
|
68
73
|
and push the files it wrote, and hand that receipt verbatim to `submit_componentize_receipt`. It records
|
|
69
74
|
each extracted section as the source of the component with the same slug, so no
|
|
70
|
-
`set_component_source` per component is needed. Deploy.
|
|
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).
|
|
71
77
|
7. Output validation, from step 3 of §6.
|
|
72
78
|
|
|
73
79
|
## 3. Names and groups
|
|
@@ -75,7 +81,10 @@ Never let a component contain itself, directly or through a child.
|
|
|
75
81
|
Name a family by what it is (`Hero`, `CTA`, `Testimonials`) and each variant by how it looks
|
|
76
82
|
(`Hero / Split`, `CTA / Centered`). Never put a page name in a reusable definition; which pages
|
|
77
83
|
use it is a usage question (§8). The componentize lane files a section under its page's slug
|
|
78
|
-
(`home-hero`) but keeps the name the section's own (`Hero`).
|
|
84
|
+
(`home-hero`) but keeps the name the section's own (`Hero`). Its components go into the Component
|
|
85
|
+
Group you pass as `group` (a folder of the dashboard's Components tab), else into one named for
|
|
86
|
+
the section family: nav, header, footer or menu into Layout, an unnamed section into Sections,
|
|
87
|
+
any other into the family's own name (Hero, FAQ). Keep related fields together in the
|
|
79
88
|
order an editor reads them:
|
|
80
89
|
|
|
81
90
|
```text
|
|
@@ -111,3 +120,53 @@ and after publish:
|
|
|
111
120
|
- one chrome edit reaches every route.
|
|
112
121
|
|
|
113
122
|
Then `get_binding_report` and `get_site_composition` (§9).
|
|
123
|
+
|
|
124
|
+
## 5. The page wrap (`--sections`): sections that stay in the site's code
|
|
125
|
+
|
|
126
|
+
Lifting a band into a component (§2) needs an inline run and a plan. A page built from imported
|
|
127
|
+
section components with props has neither, and it does not need them: `--sections` wraps the
|
|
128
|
+
page's top-level sections in `<BcmsSections>`, one named slot each, so the site's own build draws
|
|
129
|
+
them in the CMS page's order, with each block's spacing and style, and draws a section added in
|
|
130
|
+
the editor between them. It changes no section's markup, props, classes or styles.
|
|
131
|
+
|
|
132
|
+
`bettercms init` runs it before the agent starts and commits it locally
|
|
133
|
+
(`chore(bettercms): render sections from BetterCMS`); the dispatch of step 6 runs it after
|
|
134
|
+
`--componentize`. Do not redo or revert that commit. Pushing it is the user's decision. To run
|
|
135
|
+
it yourself, in a clean checkout:
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
npx @bettercms-ai/convert --sections --root . --dry-run # the plan, per page
|
|
139
|
+
npx @bettercms-ai/convert --sections --root .
|
|
140
|
+
npx @bettercms-ai/convert --verify-styles --root . # must report no style edits
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
A page it leaves alone keeps rendering as before, and is listed with one of these:
|
|
144
|
+
|
|
145
|
+
| Reason | The page | What you do |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| `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 |
|
|
148
|
+
| `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 |
|
|
149
|
+
| `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 |
|
|
150
|
+
| `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 |
|
|
151
|
+
| `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 |
|
|
152
|
+
| `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)) |
|
|
153
|
+
| `PAGE_UNKNOWN` | No CMS page for the route | Create and publish the page, run again |
|
|
154
|
+
| `DIALECT_UNSUPPORTED` | Markdown, MDX or HTML | Nothing: the body is one `document` field |
|
|
155
|
+
| `NAME_TAKEN` | Already uses the name `BcmsSections` for something else | Rename that, run again |
|
|
156
|
+
| `SDK_REQUIRED` | The repo's SDK predates the component | Add `--add-sdk`, install, run again |
|
|
157
|
+
|
|
158
|
+
The by-hand markup is in [astro.md](astro.md) and [next.md](next.md), and it follows the rules of
|
|
159
|
+
[animation.md](animation.md) §10: slot tags around sections you do not edit, no new element. On
|
|
160
|
+
a wrapped page two notes ask for a hand edit. `ROOT_IS_COMPONENT` (Next): the section moves and
|
|
161
|
+
takes its spacing on a box around it, which is one block box once styled; write its child as a
|
|
162
|
+
function and spread the root props on the component's root element to style the root itself.
|
|
163
|
+
`EXPRESSION`: a `.map()` or a conditional is ONE section; to move its rows separately, give each
|
|
164
|
+
row its own slot.
|
|
165
|
+
|
|
166
|
+
Slots are written `code:<bind>` unless the page already places that bind; a `code:` slot is
|
|
167
|
+
never removed by the list. After the page's placements exist, run `--sections` again (or
|
|
168
|
+
`bettercms init`): it writes those slots bare, and from then on removing the block removes the
|
|
169
|
+
section. Any other framework gets `FRAMEWORK_UNSUPPORTED` and no write.
|
|
170
|
+
|
|
171
|
+
Check: after the build, the route's row in `get_binding_report` reads `stamped` equal to `placed`
|
|
172
|
+
and `ordered: true`.
|
|
@@ -192,16 +192,24 @@ component.
|
|
|
192
192
|
|
|
193
193
|
### When the site's own code renders the sections
|
|
194
194
|
|
|
195
|
-
A code-built site ships only what its templates draw, whatever the page's `blockJson` says.
|
|
195
|
+
A code-built site ships only what its templates draw, whatever the page's `blockJson` says. The
|
|
196
|
+
setup gate judges it per route (`sections` and `style`, [finish-site.md](finish-site.md)), and
|
|
197
|
+
adding or spacing one section is the recipe in [publishing.md](publishing.md).
|
|
196
198
|
|
|
197
199
|
- **Render the page's block list, never hard-coded bands.** A section added or moved in the
|
|
198
200
|
editor reaches the live site only when the template loops over the page's blocks in order and
|
|
199
|
-
each section root carries its block id as `data-bcms-block`. The codemod
|
|
200
|
-
|
|
201
|
+
each section root carries its block id as `data-bcms-block`. The codemod writes both: `--sections`
|
|
202
|
+
wraps a page as it is (below), and `--componentize` lifts its bands into components and
|
|
203
|
+
generates `src/lib/bcms-sections.ts` for the loop. Until one ships, the
|
|
201
204
|
editor still adds, moves, swaps and removes sections (saved, and drawn on the canvas at once),
|
|
202
205
|
tells the author that publishing leaves the live order to the site's code, and its "Let BetterCMS
|
|
203
206
|
control section order" button commits that same codemod to the connected repository. `submit_conversion_receipt` keeps
|
|
204
207
|
`complete: false` with one `SECTIONS_UNSTAMPED` row per route.
|
|
208
|
+
- **A page of imported section components is wrapped, not lifted.** `<BcmsSections>` takes each
|
|
209
|
+
top-level section as a named slot and draws the block list in order, stamping each section's
|
|
210
|
+
own root; a section added in the editor renders between them. `bettercms init` writes and
|
|
211
|
+
commits the wrap; what it refused, and the markup to write by hand, is in
|
|
212
|
+
[componentize.md](componentize.md) §5.
|
|
205
213
|
- **The order is measured, not assumed.** A release reads the stamped section roots in document order:
|
|
206
214
|
a build that draws them in the CMS order (`ordered: true` on the binding report's structure row)
|
|
207
215
|
owns nothing, whatever sections it has no code for; one that draws them in its own order
|
|
@@ -221,13 +229,32 @@ A code-built site ships only what its templates draw, whatever the page's `block
|
|
|
221
229
|
carrying `data-variant` and `data-size`, so every look control (style, size, arrow, icon, new
|
|
222
230
|
tab) applies on the canvas and on publish.
|
|
223
231
|
- **A button's looks are the ones the site already has.** Each release measures the built pages
|
|
224
|
-
and keeps the project's Button family equal to the looks found there
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
232
|
+
and keeps the project's Button family equal to the looks found there. Style offers those looks
|
|
233
|
+
and no others, and a publish paints a pick with the site's own classes. Never write CSS for a
|
|
234
|
+
look so that a control "works": there is no fixed list of variants to implement. A `button`
|
|
235
|
+
value's `variant` is one of those looks; a refused value comes back with the looks the project
|
|
236
|
+
has. A look the site drops is flagged, never deleted. A new look is the owner's design
|
|
237
|
+
decision: add its class to the site, then Rebuild.
|
|
238
|
+
- **Each look is named for the role it plays on that site, and a site has only the roles it
|
|
239
|
+
uses.** One site's family is Primary, Secondary, Tertiary; another's is Primary, Secondary,
|
|
240
|
+
Ghost, Outline, Text Link. Never create, rename toward, or ask for a role the site does not
|
|
241
|
+
use, and never number one ("Outline 2"): the release gives the names, in this order.
|
|
242
|
+
|
|
243
|
+
| Role | The look that gets it |
|
|
244
|
+
|---|---|
|
|
245
|
+
| The site's own name | A look the site names keeps that name: `btn-primary` is Primary, `btn-ghost` is Ghost, `data-variant="link"` is Text Link. Its stored `variant` value stays the site's own. |
|
|
246
|
+
| Primary | Exactly one: the filled look used most (placements, then pages, then hero placements). On a site that fills nothing, its most used outline or ghost. |
|
|
247
|
+
| Brand | The most used look filled with a colour, when Primary is a neutral dark or light fill, or when the site names a less used look Primary. |
|
|
248
|
+
| Secondary | The look most often beside Primary in a button group; else the next most used look that is not a text link. |
|
|
249
|
+
| Tertiary | A third filled look. Further fills are Dark, Light, or the colour's name. |
|
|
250
|
+
| Outline | Bordered and not filled. |
|
|
251
|
+
| Ghost | No fill and no border, padded like a button or filled on hover. |
|
|
252
|
+
| 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
|
+
|
|
254
|
+
A light and a dark rendition, a hover or disabled state, a size, and one paint spelled in two
|
|
255
|
+
sets of classes are all ONE look. Two looks left with one role are told apart in words from
|
|
256
|
+
their paint ("Outline on dark", "Pill Outline"). A role once given stays: a look added later
|
|
257
|
+
takes a role that is free and renames nothing, and a name a person gave is never changed back.
|
|
231
258
|
Without the SDK, write that same anchor: `data-bcms-button="<path>"`, the label in its own
|
|
232
259
|
`<span data-bcms-field="<path>.label">`, the href through `data-bcms-props`. The arrow is the
|
|
233
260
|
`svg.bcms-btn-arrow` it draws; never leave a hand-written arrow `<svg>` beside it, or turning
|
|
@@ -179,7 +179,12 @@ fail, because the next redesign makes them lie.
|
|
|
179
179
|
|
|
180
180
|
Short labels named for what the editor sees: Heading, Body, Primary button, Label, Link, Image,
|
|
181
181
|
Alt text — or the name the site's code already uses (`cents` → Cents), never the copy itself
|
|
182
|
-
(`.00`).
|
|
182
|
+
(`.00`). An import names every field it derives the same way, by its section and its role
|
|
183
|
+
there: "Hero headline", "Hero intro", "Hero primary button", "Pricing plans", "Footer link 2
|
|
184
|
+
URL", "Header logo"; a list's columns by their role in the row ("Title", "Description", "Link
|
|
185
|
+
URL"). A role is numbered only where it repeats in one section, and labels are unique on a page.
|
|
186
|
+
Keys never change. Rename a label where the site's code has a better word: a name you give is
|
|
187
|
+
kept on every later release, and one the import gave follows the import. Most fields need no help line from you (below). Group by what renders
|
|
183
188
|
together, not by the DOM or the source file: core content first, optional appearance second, technical fields hidden. Repeatable items need a meaningful preview label (the FAQ question, the
|
|
184
189
|
button label), reorder, and stable item ids. An empty optional field removes its element without
|
|
185
190
|
leaving a gap; a long value wraps, and clamps only where the design intends it.
|
|
@@ -9,6 +9,13 @@ description: "Take an imported or converted site to fully editable: the gap ledg
|
|
|
9
9
|
verified with evidence. "Fully editable" is a measured result, never an inference from imported
|
|
10
10
|
schemas. New to the project, or no repo on disk yet: [getting-started.md](getting-started.md).
|
|
11
11
|
|
|
12
|
+
## Contents
|
|
13
|
+
|
|
14
|
+
- [Inputs and baseline](#inputs-and-baseline) · [The finish line for a convert or build job](#the-finish-line-for-a-convert-or-build-job)
|
|
15
|
+
- [The setup gate: what "finished" is measured by](#the-setup-gate-what-finished-is-measured-by)
|
|
16
|
+
- [The gap ledger](#the-gap-ledger-bettercmsledgerjson) · [Repair order](#repair-order-and-the-reference-for-each-step)
|
|
17
|
+
- [Proof loop](#proof-loop) · [Launch decision](#launch-decision)
|
|
18
|
+
|
|
12
19
|
## Inputs and baseline
|
|
13
20
|
|
|
14
21
|
Require the project, source repository or import snapshot, site URL, import manifest, hosting
|
|
@@ -50,15 +57,100 @@ line), which is what a script waiting on the setup reads.
|
|
|
50
57
|
| `text` | 2% or less of the visible characters sit outside a field | `routes.rows[].text` |
|
|
51
58
|
| `image` | each image a visitor sees belongs to a field, and the release named no picture it could not make one | `routes.rows[].image`, `imageGaps` |
|
|
52
59
|
| `link` | 95% of the links have a field for the label or the destination | `routes.rows[].link` |
|
|
60
|
+
| `sections` | every section the page places is a stamped root on the built page, none twice, none published after the build, and a section added in BetterCMS has somewhere to show (`accepts` `list` or `in-place`) | `routes.rows[].sections`: `accepts`, `renderer` (`sdk`, `registry`, `annotator`), and when it fails `refusal` (why the release cannot write the page) and `wrap` (`wrapped`, or `refused:<REASON>` from the sections conversion) |
|
|
61
|
+
| `style` | every stamped section root carries the style marker (`data-bcms-style`, or `data-bcms-block-style` on a root that is a field's element) or is drawn by the route's block renderer, counted by setup on the served page, so a block's spacing, padding and background paint | `routes.rows[].style` |
|
|
62
|
+
| `svg` | every inline SVG a visitor sees (a logo, an icon, an illustration) belongs to a field; a hidden symbol sheet is not counted | `routes.rows[].svg` |
|
|
63
|
+
| `number` | every element whose whole text is a number (a stat, a price, a rating) belongs to a field | `routes.rows[].number` |
|
|
64
|
+
| `attribute` | every `alt`, `aria-label`, `title` and `placeholder` with words in it belongs to a field | `routes.rows[].attribute` |
|
|
65
|
+
| `names` | no field of the page, and no component, has a name a person cannot read: a slug, a hash, a structural word (`Section 2`, `wrap`), or a number beside the same name (`Icon 2`) | `blocking.rows` |
|
|
66
|
+
| `seo` | the page has a title of at most 70 characters and a description of at most 200, and a canonical where the build ships one for this site's origin; site-wide, every page has a share image, its own or the site's default | `blocking.rows` |
|
|
67
|
+
| `structuredData` | JSON-LD the build ships on the route is on the page or the entry | `blocking.rows` |
|
|
68
|
+
| `collections` | sibling routes that are one family (three under one parent, or two under `tags`, `category`, `author`) are a collection; reported on the family's listing route, built or not | `blocking.rows` |
|
|
69
|
+
| `buttons` | every button field is one of the site's live Button looks, the Button family is the looks setup measured on this build (the same looks, not the same count), and no look is named by a number or a build's class | `blocking.rows`, `route: null` |
|
|
70
|
+
| `components` | the authoring question is answered, and no section is left that a componentize run would place | `blocking.rows`, `route: null` |
|
|
71
|
+
| `forms` | every form of the customer's has an address to notify; a form setup ignored (sign-in, add to basket) is not counted | `blocking.rows`, `route: null` |
|
|
53
72
|
|
|
54
73
|
`pages` counts routes built, created and published. `coverage` sums `editable` of `total` across
|
|
55
|
-
the site
|
|
74
|
+
the site, `svg`, `number` and `attribute` included, and `open` counts the rows still open per
|
|
75
|
+
check. `routes.rows` is ordered worst first, with `failing` per route and the reasons the
|
|
56
76
|
deriver `skipped` copy there. `blocking.rows` is the work list: each row carries a `detail` with
|
|
57
|
-
its number and a `fix`, the
|
|
58
|
-
|
|
77
|
+
its number, a `fixCode` and a `fix`, the call that closes it.
|
|
78
|
+
|
|
79
|
+
`nextAction` is the one thing to do now: the fix a tool can make that closes the most rows
|
|
80
|
+
(`fixCode`, `count`, the `fix` of its first row, the first `routes`), two with the same count in a
|
|
81
|
+
fixed order. Run it on every row that carries that `fixCode`, call `get_next_steps`, and repeat
|
|
82
|
+
until `complete`: following it reaches `complete` with no step that changes nothing. A fix no
|
|
83
|
+
tool can make is named only when nothing else is left. A `fix` of `null` means no tool closes that row (a collection's listing
|
|
59
84
|
route, a URL that cannot be placed, two pages nobody but the owner can choose between): put it to
|
|
60
85
|
the user and stop retrying. Treat a row that its fix did not move the same way.
|
|
61
86
|
|
|
87
|
+
A `sections` or `style` row is a page nobody can add a section to, reorder or space, however
|
|
88
|
+
complete its copy is. Its `fixCode` names the one fix, and `fix` the call when a tool can make it:
|
|
89
|
+
|
|
90
|
+
| `fixCode` | The fix |
|
|
91
|
+
|---|---|
|
|
92
|
+
| `run-sections-conversion` | Only the site's code can draw this page's sections: its app renders the route, a framework hydrates the page whole (`PAGE_HYDRATED`), or the release could not map its bands. In a checkout run `npx @bettercms-ai/convert@latest --sections --root .` ([componentize.md](componentize.md) §5), or call `componentize_sections` with `codemod: true` and the plan's `digest`; `--upgrade-sections` for a `style` row. Then push and release |
|
|
93
|
+
| `rebuild` | The build that serves is older than what makes the row pass: a published section, the wrap's commit (`wrap: "wrapped"` on a row that still fails), or the in-place switch. No tool starts one: ask the user to push and Rebuild |
|
|
94
|
+
| `enable-in-place-structure` | A static page the release can write by itself, and the project's in-place structure switch is off. Setup switches it on for a project it creates; on an existing one it is the operator's call. No repository change |
|
|
95
|
+
| `run-setup` | Nothing measured what the row reads, or a page is still to register: `editor_setup { force: true }`, then read again |
|
|
96
|
+
|
|
97
|
+
The other rows name their fix the same way:
|
|
98
|
+
|
|
99
|
+
| `fixCode` | The fix |
|
|
100
|
+
|---|---|
|
|
101
|
+
| `bind-elements` | `get_conversion_brief { complete: true }` lists what no field owns on the route; bind each (an SVG as an image or icon field, a number as its own field, attribute copy with `data-bcms-props`) |
|
|
102
|
+
| `rename-fields` | `get_page`, then `update_field` with a name a person would say; `update_component` for a component |
|
|
103
|
+
| `fill-seo` | `update_page` with `metaTitle` and `metaDescription` (`update_content_entry` with `meta` for an entry; `generate_seo_meta` drafts one from text you give it); a missing canonical is setup's to copy from the built head, so that row carries `editor_setup { force: true }`; the site-wide row wants the site's default share image, `update_seo` with `seoDefaults.ogImage` |
|
|
104
|
+
| `import-structured-data` | `editor_setup { force: true }` imports the build's JSON-LD; `update_seo` when it has to be written by hand |
|
|
105
|
+
| `model-collection` | `create_content_model` with the family's `urlPattern` (`/tags/:slug`), then its entries: one item template for the family ([content-modeling.md](content-modeling.md)) |
|
|
106
|
+
| `derive-buttons` | `editor_setup { force: true }` mints a Button for each look the build draws, retires one it no longer draws and maps each button to its look; rename a look with `update_component` ([components.md](components.md)) |
|
|
107
|
+
| `componentize` | `set_authoring_preference` when nobody answered, then `get_componentize_plan` and `componentize_sections` ([componentize.md](componentize.md)) |
|
|
108
|
+
| `set-form-recipient` | ask the user where the form's submissions go, then `update_form` with `notifyEmails`; `update_form { ignored: true }` when it is not one of their forms |
|
|
109
|
+
|
|
110
|
+
A row nothing measured reads `unmeasured: true` with `run-setup`, and `gate.unmeasured` lists the
|
|
111
|
+
checks that have one: a release older than a count, or a project set up before setup recorded
|
|
112
|
+
what it found on the build (the style marker of each section root, the looks it measured, the
|
|
113
|
+
canonicals the build ships). It is not a failure of the site and not a pass either: run
|
|
114
|
+
`editor_setup { force: true }` once and read again.
|
|
115
|
+
|
|
116
|
+
Setup closes what is deterministic by itself, on every run, so your first receipt is already past
|
|
117
|
+
it and an open row of these means run setup again: `run-setup`, `derive-buttons` (the family and
|
|
118
|
+
the map), `import-structured-data`, `fill-seo` from the built head and the page's own copy,
|
|
119
|
+
`rename-fields` for a field still carrying a derived name, `componentize` once the authoring
|
|
120
|
+
answer is `components`, and `enable-in-place-structure` on a project it creates. What is left
|
|
121
|
+
takes judgement and is yours: `bind-elements`, `model-collection`, `set-form-recipient` (ask the
|
|
122
|
+
user), `run-sections-conversion`, a name a person gave, a title over its limit, a look named by a
|
|
123
|
+
number, and the authoring answer itself (the user's). `rebuild` is the user's.
|
|
124
|
+
|
|
125
|
+
Beside the gate, `data` lists what else is unfinished, each item by its `ruleId`: content
|
|
126
|
+
(`mcp-pages-draft`, `mcp-pages-missing-meta`, `mcp-model-empty`, `mcp-form-no-recipient`,
|
|
127
|
+
`mcp-pending-review`), bindings and structure (`conversion-pending`, `bindings-not-declared`,
|
|
128
|
+
`unaddressable-copy`, `upgrade-section-roots`, `upgrade-element-styles`, `componentize-pages`,
|
|
129
|
+
`components-unpublished`), the authoring answer (`authoring-undecided`), the Layout lane
|
|
130
|
+
(`layout-write-blocked`, `layout-field-unread`, `layout-locator-stale`, `layout-rebuild-required`,
|
|
131
|
+
`layout-unpublished`, `chrome-duplicated`), publishes (`publish-held`) and the Content sidebar
|
|
132
|
+
(`organise-content`). `setup-gate` is first while the gate is open and `publish-held` next.
|
|
133
|
+
|
|
134
|
+
Stamps are not enough to pass either check: the lane that serves a route must have PROVED it.
|
|
135
|
+
Each release adds a probe section to the middle of one built page of each kind (a page, a
|
|
136
|
+
listing, an item template) and a probe padding to one of its sections, in memory, finds both in
|
|
137
|
+
the result and takes them out again; the sections conversion proves each page it wraps the same
|
|
138
|
+
way. `routes.rows[].proof` is that record (`lane`, `route`, `insert`, `style`: `ok` or the
|
|
139
|
+
reason). A row whose proof was never run asks for it where it runs: `editor_setup { force: true }`
|
|
140
|
+
for the release, the conversion again for a wrapped page. `SLOTS_NOT_PLACED` on a wrapped page
|
|
141
|
+
means every slot is still `code:`: place the page's sections, then run the conversion again.
|
|
142
|
+
|
|
143
|
+
The gate picks the fix per route, in that order of evidence: a wrapped page waits for its build; a
|
|
144
|
+
page a script draws needs the conversion; a static page needs only the switch. Run the fix the
|
|
145
|
+
row names and no other: the conversion does nothing for a row that says `rebuild`.
|
|
146
|
+
|
|
147
|
+
Two rows have no `fixCode`. A page that places a section twice: remove the second placement with
|
|
148
|
+
`update_page`. A route neither lane serves: the release cannot write it (`refusal`) and the
|
|
149
|
+
conversion refused it (`wrap`); its `detail` names both. Put it to the user: write the wrap by
|
|
150
|
+
hand ([astro.md](astro.md), [next.md](next.md)) or change the page. No exception covers it
|
|
151
|
+
(`document-body` excuses only a route that places no section). Check a route's row again after
|
|
152
|
+
each release; never mark one done from the codemod's own output.
|
|
153
|
+
|
|
62
154
|
A `page` row on a route setup left without a page carries the pages lane's reason in its
|
|
63
155
|
`detail` (`invalid-path`, `html-too-large`, `page-deleted`, `slug-taken`, `folder-conflict`,
|
|
64
156
|
`entry-not-registered`, `authored-content` and the rest), with what closes it: a change in the
|
|
@@ -67,7 +159,17 @@ and `not-registered` carry a `fix`. An `image` row with a `null` fix names a pic
|
|
|
67
159
|
([media-and-brand.md](media-and-brand.md) Where images hide): the change is in the site's code.
|
|
68
160
|
`imageGaps` also lists the decoration the import declined, which blocks nothing. On a
|
|
69
161
|
collection's listing route the rows that link to its entries are counted as the collection's,
|
|
70
|
-
so the listing page is judged on its own heading, intro and calls to action
|
|
162
|
+
so the listing page is judged on its own heading, intro and calls to action, and on the words
|
|
163
|
+
of a row the release bound to its entry (a field's there too). The release binds
|
|
164
|
+
each such row to its entry: the row carries `data-bcms-entry`, a part showing a field as stored
|
|
165
|
+
carries `data-bcms-entry-field` (`entry:<id>:<path>`, written when that entry publishes), and a
|
|
166
|
+
part the site's code made from a field (a cut excerpt, a reformatted date or price, a thumbnail,
|
|
167
|
+
the item link) carries `data-bcms-entry-from` with the transform, read-only: edit the entry.
|
|
168
|
+
Never add a field for a row to the listing page. A taxonomy's term routes (`/tags/<term>`, each
|
|
169
|
+
listing another collection's entries) are a small collection of terms, and their rows are bound
|
|
170
|
+
the same way. A term has a `body` only where its template has an article. Chrome the Layout
|
|
171
|
+
could not take (an icon with no Media asset) is template copy on item routes: one shared field
|
|
172
|
+
of the collection, as it is a page's own field on a page.
|
|
71
173
|
|
|
72
174
|
### Exceptions go on the record
|
|
73
175
|
|
|
@@ -79,11 +181,16 @@ in a sentence a person can verify. `reason: null` withdraws one.
|
|
|
79
181
|
|---|---|
|
|
80
182
|
| `form-validation-copy` | `text` |
|
|
81
183
|
| `legal-boilerplate` | `text` |
|
|
82
|
-
| `third-party-widget` | `text`, `image`, `link` |
|
|
83
|
-
| `decorative` | `text`, `image` |
|
|
84
|
-
| `duplicate-track` | `text`, `image`, `link` (the cloned track of a marquee or slider) |
|
|
184
|
+
| `third-party-widget` | `text`, `image`, `link`, `svg`, `number`, `attribute` |
|
|
185
|
+
| `decorative` | `text`, `image`, `svg` |
|
|
186
|
+
| `duplicate-track` | `text`, `image`, `link`, `svg`, `number`, `attribute` (the cloned track of a marquee or slider) |
|
|
85
187
|
| `client-only-island` | nothing: it lists an island the build cannot show, and the route still meets the bar outside it |
|
|
86
|
-
| `user-declined` |
|
|
188
|
+
| `user-declined` | `page`, `published`, `text`, `image`, `link`, `svg`, `number`, `attribute`, `seo`, `structuredData`, `collections`, and only with the user's own words quoted in `note` (`User: "leave pricing in code"`) |
|
|
189
|
+
| `document-body` | `sections`, and only on a route that places no section at all: a page that is one document (a markdown post, a legal page) |
|
|
190
|
+
|
|
191
|
+
Nothing else excuses `sections`, and nothing excuses `style`, `twin`, `names`, `buttons`,
|
|
192
|
+
`components` or `forms`: not the user's decision either. A name is fixed by renaming, and a form
|
|
193
|
+
that is not the customer's is marked ignored.
|
|
87
194
|
|
|
88
195
|
A row excuses what its check read when it was recorded. When the route reads differently
|
|
89
196
|
later (more copy nobody owns, another picture), the row stops counting and the check blocks
|
|
@@ -149,7 +256,7 @@ and add the new result. Update the file as each row moves, so a reset loses at m
|
|
|
149
256
|
|---|---|---|
|
|
150
257
|
| 0 | Editor setup: `editor_setup { check: true }` reads `ready` for the live build (run `editor_setup` when it does not) | [getting-started.md](getting-started.md) |
|
|
151
258
|
| 1 | Routing and data shape: page / collection / entry classification and folders; keep public URLs | [content-modeling.md](content-modeling.md) |
|
|
152
|
-
| 2 | Sections and reuse: every band a section; dedupe compatible patterns into variants and overrides; nav and footer as globals (`get_componentize_plan`, `componentize_sections` on a components project) | [components.md](components.md), [componentize.md](componentize.md) |
|
|
259
|
+
| 2 | Sections and reuse: every band a section; dedupe compatible patterns into variants and overrides; nav and footer as globals (`get_componentize_plan`, `componentize_sections` on a components project). `bettercms init` has already wrapped each page's sections in `<BcmsSections>` and committed it: fix only the pages it refused ([componentize.md](componentize.md) §5) | [components.md](components.md), [componentize.md](componentize.md) |
|
|
153
260
|
| 3 | Editor contract: field types, labels, help text on every element (`get_next_steps` shows no `help-text-missing`), validation, Portable Text accept lists, nested groups | [portable-text.md](portable-text.md) |
|
|
154
261
|
| 4 | Design: media, logo roles, fonts and brand tokens, without changing visual intent | [media-and-brand.md](media-and-brand.md) |
|
|
155
262
|
| 5 | Bindings: the site reads BetterCMS data and each intended leaf or attribute is bound; build, preview and runtime agree | [bindings.md](bindings.md), [frameworks.md](frameworks.md) |
|
|
@@ -184,6 +184,13 @@ only reveals the next group, to `step` fields; keep one `required` per real ques
|
|
|
184
184
|
|
|
185
185
|
`update_form` with `fields` replaces the whole array: send every field you want to keep.
|
|
186
186
|
|
|
187
|
+
Setup imports two kinds of form already ignored, each kept as a restorable row: the site's own
|
|
188
|
+
sign-in, register and password forms, and a shop's add-to-basket, buy-now or checkout form (it
|
|
189
|
+
posts to the cart and asks only a quantity or a variant). Both stay the site's code. An imported
|
|
190
|
+
form is named for what it is for: its `aria-label`, its one legend, a heading inside it or in its
|
|
191
|
+
own band, else its purpose ("Newsletter signup", "Contact"). Rename it with `update_form` if the
|
|
192
|
+
site calls it something else.
|
|
193
|
+
|
|
187
194
|
### 4. Wiring a self-hosted site
|
|
188
195
|
|
|
189
196
|
- **Next**: `getForm('Name')` (or an id) reads the form from `bcms-content.json`; render
|
|
@@ -85,6 +85,8 @@ and `get_componentize_plan` proposes nothing more.
|
|
|
85
85
|
with `componentize_sections`, then the repository half (`componentize_sections { codemod: true }`
|
|
86
86
|
when there is no checkout) and `submit_componentize_receipt` ([componentize.md](componentize.md)).
|
|
87
87
|
5. Work the rest through [finish-site.md](finish-site.md) (gap ledger, repair order, proof loop).
|
|
88
|
+
The gate's `sections` and `style` rows are part of it: a setup whose pages cannot take a new
|
|
89
|
+
section, a new order or spacing is not finished.
|
|
88
90
|
|
|
89
91
|
### Phase 4 — Publish and verify
|
|
90
92
|
|