@bettercms-ai/convert 0.2.1 → 0.4.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/CHANGELOG.md +337 -0
- package/README.md +182 -22
- package/dist/cli.js +1185 -209
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +128 -5
- package/dist/index.js +1171 -208
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,6 +71,56 @@ A path counts as rewritten only when EVERY located occurrence of it was. A value
|
|
|
71
71
|
the home page and a shared header is two places a reader sees it, and converting one of them reads
|
|
72
72
|
as a finished job.
|
|
73
73
|
|
|
74
|
+
**A declaration this package did not write still counts.** An element that names the target —
|
|
75
|
+
`data-bcms-field`, `data-bcms-layout-field` or an address in `data-bcms-props` — but gets its copy
|
|
76
|
+
some other way (a literal somebody left there, the project's own data module) is DECLARED: the
|
|
77
|
+
publisher injects into that element and the editor makes it click-to-edit. It is simply not reading
|
|
78
|
+
through the committed helper. Those land in `alreadyDeclared` with a receipt row of their own,
|
|
79
|
+
`readsFromCms: false`, rather than in `NO_ORIGINAL`:
|
|
80
|
+
|
|
81
|
+
```jsonc
|
|
82
|
+
"declarations": [{ "route": "/", "scope": "page", "path": "hero.title",
|
|
83
|
+
"kind": "text", "file": "src/pages/index.astro", "readsFromCms": false }]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The evidence may never be ANOTHER ROUTE'S PAGE FILE — one the brief itself names, since
|
|
87
|
+
`routeOfFile` reads the repository's convention and the two legitimately disagree about a dynamic
|
|
88
|
+
route — and it may never be an element two routes both claim. The same rule gates a declaration
|
|
89
|
+
that DOES read from the CMS: a home page rendering a teaser of `/about`'s headline reads `/about`'s
|
|
90
|
+
snapshot legitimately, and counting it left `/about`'s own page hardcoded and unreported. Without a read there is nothing in a declaration that says which route it is for, and two
|
|
91
|
+
routes legitimately carry `hero.title` — so "some file declares that string" would mark the other
|
|
92
|
+
route bound and leave it hardcoded forever. A path with no original at all is stricter still: the
|
|
93
|
+
only evidence available is the file's identity, so it has to be the one file the router says serves
|
|
94
|
+
that route. A layout path has no route by construction, and any file may carry it.
|
|
95
|
+
|
|
96
|
+
**A tsconfig is JSONC.** Comments and trailing commas are read (that is what `tsc --init` writes),
|
|
97
|
+
because a config this package cannot parse declares no `paths` aliases as far as it is concerned —
|
|
98
|
+
and then every `@/components/Hero` resolves to nothing and the refusal that follows names the wrong
|
|
99
|
+
cause. Its alias fields are shape-checked too: `"paths": "src/*"` is valid JSON and the wrong type,
|
|
100
|
+
and reading it threw a `TypeError` out of a pure function naming nothing. One it still cannot read
|
|
101
|
+
is a receipt NOTE rather than a silence — on the componentize receipt as well, which resolves a
|
|
102
|
+
page's `<Hero />` through exactly those aliases:
|
|
103
|
+
|
|
104
|
+
```jsonc
|
|
105
|
+
"notes": [{ "code": "TSCONFIG_UNREADABLE", "file": "tsconfig.json", "message": "…" }]
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**The address grammar, in one place.** Page copy is `data-bcms-field="<path>"` plus
|
|
109
|
+
`data-bcms-kind="<kind>"`. The shared chrome is a DIFFERENT attribute carrying a prefixed address,
|
|
110
|
+
`data-bcms-layout-field="layout:<section>:<field>"`, so the block lane's own `[data-bcms-field]`
|
|
111
|
+
query cannot reach a chrome marker. A value that lives in an ATTRIBUTE (a promoted nav link's
|
|
112
|
+
`href`) has no field attribute of its own and rides `data-bcms-props="<address>|<kind>|<attr>;…"` —
|
|
113
|
+
where `<address>` carries the same `layout:` prefix when it is chrome. A section root additionally
|
|
114
|
+
carries `data-bcms-block="<blockId>"`, which is what the editor hit-tests for section chrome and
|
|
115
|
+
reorder — and inside an extracted section that root's own placement decides which lane its elements
|
|
116
|
+
address: the page path while the copy is the page's, `data-bcms-field="<blockId>__overrides.<key>"`
|
|
117
|
+
once the copy is the instance's. The page-lane address is re-based onto **that placement's** group:
|
|
118
|
+
the markup was cut from one page and carries its paths, and a second page renders the same
|
|
119
|
+
component bound to a group of its own, where `hero.title` is a field that does not exist.
|
|
120
|
+
**One `data-bcms-field` per element, always.** Two would leave the publisher and the editor reading
|
|
121
|
+
different attributes off one node, and which of them won would depend on the dialect — JSX takes
|
|
122
|
+
the last, Astro the first.
|
|
123
|
+
|
|
74
124
|
## As a library
|
|
75
125
|
|
|
76
126
|
```ts
|
|
@@ -92,6 +142,17 @@ proposer re-derives them from its own scan rather than believing the receipt. A
|
|
|
92
142
|
become a loop — non-consecutive siblings, one row shaped differently, the html dialect — is still
|
|
93
143
|
declared and read per index and reported `REPEATER_FIXED_LENGTH`.
|
|
94
144
|
|
|
145
|
+
**A row that holds a list of its own becomes a loop inside the loop.** `cards[i].items[j].label` is
|
|
146
|
+
a nested `.map` in astro and jsx, reading its rows off the ROW — `bcmsRows(card, "items", […])`,
|
|
147
|
+
the same helper one level down, because `bcmsRows` passes a nested array through untouched and
|
|
148
|
+
`bcmsSection` hands a section its group whole. The binding is
|
|
149
|
+
`` data-bcms-field={`cards[${i}].items[${j}].label`} `` and the shape is `cards[*].items[*].label`.
|
|
150
|
+
ONE level: that is what the plan mints and what the component prop validator accepts, so a leaf
|
|
151
|
+
indexed twice keeps the length the template gives it. One inner row is enough to make the loop —
|
|
152
|
+
the plan already says `items` is an array, and reading a card that ships a single bullet as "not a
|
|
153
|
+
list" is how the second bullet an editor adds never renders. The html dialect has no loop form at all, so
|
|
154
|
+
it stays fixed-length there — every index declared, the number of rows the template's.
|
|
155
|
+
|
|
95
156
|
**A prop-drilled literal is converted in two hops, in one proposal.** `<Hero title="…" />` becomes
|
|
96
157
|
`title={bcms(…)}` plus `bcmsBindings={{ title: "hero.title" }}`, and the `<h1>{title}</h1>` inside
|
|
97
158
|
`Hero` — resolved through the file's own imports, relative or through the project's tsconfig
|
|
@@ -119,8 +180,10 @@ left alone. A repeater on a dynamic route passes the same parameter to `bcmsRows
|
|
|
119
180
|
## Componentize
|
|
120
181
|
|
|
121
182
|
The second mode. P2 above makes a page's copy editable in place; `--componentize` makes its
|
|
122
|
-
SECTIONS movable — each band becomes a component the CMS can reorder, duplicate and swap
|
|
123
|
-
copy
|
|
183
|
+
SECTIONS movable — each band becomes a component the CMS can reorder, duplicate and swap. Where the
|
|
184
|
+
copy lives is a separate question, and the plan answers it: **Phase A** leaves it in the page's
|
|
185
|
+
field groups (`props.bind`), **Phase B** moves it onto the instance (`props.overrides`). The same
|
|
186
|
+
extracted files serve both.
|
|
124
187
|
|
|
125
188
|
The division of labour is the one that already works by hand: **the CMS owns STRUCTURE, the repo
|
|
126
189
|
owns RENDERING.** The server decides which field groups are sections (`get_componentize_plan` →
|
|
@@ -142,20 +205,45 @@ modify src/pages/index.astro
|
|
|
142
205
|
modify bcms-content/home.json
|
|
143
206
|
add bcms-content/components.json
|
|
144
207
|
|
|
145
|
-
5 sections extracted (0 inline-only, 0 already extracted, 0 pending)
|
|
208
|
+
5 sections extracted (0 registered, 0 inline-only, 0 already extracted, 0 pending), copy: bind
|
|
146
209
|
```
|
|
147
210
|
|
|
148
211
|
`--plan <file>` is required and is only read in this mode. `--dry-run`, `--receipt`, `--strict` and
|
|
149
212
|
`--overwrite-helper` mean what they mean above; `--strict` exits 2 on any pending SECTION.
|
|
150
213
|
|
|
214
|
+
`--copy bind|instance` (default `bind`) says which copy model the plan is expected to describe. It
|
|
215
|
+
is an **assertion, not a switch** — the extracted files are byte-identical either way — and it
|
|
216
|
+
exists to catch one order-of-operations mistake that would otherwise be silent: running
|
|
217
|
+
`--copy instance` in the repository BEFORE the server has moved the copy leaves a tree that looks
|
|
218
|
+
converted and renders every section out of page fields the dashboard is about to hide. A plan whose
|
|
219
|
+
placements all still carry `bind` says so, and every section is refused `COPY_MODE_MISMATCH` with
|
|
220
|
+
nothing written. The receipt carries the mode as `copy`, the same word the server's receipt uses.
|
|
221
|
+
|
|
151
222
|
### What is written
|
|
152
223
|
|
|
153
224
|
- **`src/components/bcms/<Name>.<astro|tsx>`** — the section root's markup **verbatim**, P2's
|
|
154
|
-
`
|
|
155
|
-
|
|
156
|
-
`bcmsSection(page, bind, overrides)` in the
|
|
157
|
-
|
|
158
|
-
|
|
225
|
+
`bcms(...)` reads and its page paths included, plus `data-bcms-block={blockId}` on the root (the
|
|
226
|
+
editor hit-tests that for section chrome and reorder; a root that already carried one has it
|
|
227
|
+
REPLACED — and any further ones removed — never joined). Its copy comes from `bcmsSection(page, bind, overrides)` in the
|
|
228
|
+
committed helper — `overrides[key] ?? pageGroup(bind)[key] ??` the fallbacks the template already
|
|
229
|
+
carries. **The placement's own `bind`, with no synthesized default:** `bind ?? "<groupKey>"`
|
|
230
|
+
belonged to Phase A, where every placement was bound and the default could only restate what the
|
|
231
|
+
placement already said; under Phase B it put the page's field group back underneath an instance
|
|
232
|
+
that owns its copy, so a key the instance had not been given rendered stale page copy out of
|
|
233
|
+
fields the dashboard has already hidden. A placement that names no group has none, and every
|
|
234
|
+
`bcms(...)` renders what the template shipped with — and each of P2's bindings is wrapped in the placement's own chooser,
|
|
235
|
+
`bcmsFields({ blockId, bind, overrides }, "<prefix>")`, so the page path is still verbatim in the
|
|
236
|
+
file and the ADDRESS is decided when the component renders. Both names it introduces are
|
|
237
|
+
allocated (`bcmsField_1`, and so on) against the page's own top-level bindings **and against
|
|
238
|
+
every identifier-shaped token in the bytes being lifted** — all of them, since a section using
|
|
239
|
+
`bcmsField` *and* `bcmsField_1` would otherwise just move the capture one suffix along — because
|
|
240
|
+
a page that declared `bcmsField` would
|
|
241
|
+
otherwise have every use of theirs inside the extracted section silently rebound to our chooser
|
|
242
|
+
— and in a TSX page that declaration lives inside the component function, where a module-level
|
|
243
|
+
check cannot see it. Reserving the name puts the reference back on the honest path, which is
|
|
244
|
+
`SECTION_FREE_IDENTIFIERS` naming the identifier that could not travel. One component serves every page whose group has the same shape,
|
|
245
|
+
which is what the plan's `shapeHash` is for — and one component serves both copy models, which is
|
|
246
|
+
what makes moving a page's copy a data change rather than another cut.
|
|
159
247
|
- **`src/lib/bcms-sections.ts`** — `pageSections(pageSlug)` over the committed snapshots'
|
|
160
248
|
`blocks` plus `bcms-content/components.json`, returning `[{ blockId, slug, bind, overrides }]`.
|
|
161
249
|
An instance naming a component this repo does not have is **skipped, never a hole**.
|
|
@@ -165,9 +253,26 @@ add bcms-content/components.json
|
|
|
165
253
|
look like, so rendering nothing there would blank the site on the first build after the codemod
|
|
166
254
|
ran, with nothing in the diff to explain it. The fallback is decided on the **raw** block list:
|
|
167
255
|
a page whose blocks all name components this repo does not have has still been *arranged*, and
|
|
168
|
-
putting the shipped bands back would undo an editor's work on the next build.
|
|
256
|
+
putting the shipped bands back would undo an editor's work on the next build. A shipped placement
|
|
257
|
+
carries `bind` only while the plan says its copy is the page's: an instance-owned one has none,
|
|
258
|
+
because binding it there would make a fresh clone render a section out of fields the dashboard
|
|
259
|
+
has already hidden.
|
|
260
|
+
- **Nothing at all, for a section the repository has ALREADY componentised.** A page that renders
|
|
261
|
+
`<Hero />` from a file it imports has made this cut itself: the existing file is REGISTERED under
|
|
262
|
+
the section's slug — the registry imports it where it lives, **in the shape its module actually
|
|
263
|
+
exports**: a default import, `import { Hero }` for a named export, or `import * as Bands` plus the
|
|
264
|
+
member the page's own call site names — and the page keeps calling it. When one module is bound
|
|
265
|
+
twice (`import Default, { Hero } from "./bands"`), the binding the page actually CALLS is the one
|
|
266
|
+
registered. It has to be a file this page imports, the only one it imports that renders the
|
|
267
|
+
group, and one the registry's dialect can render; a section the page ALSO renders inline is two
|
|
268
|
+
answers and is refused. Everything else is `SECTION_ROOT_AMBIGUOUS` with the reason spelled out.
|
|
169
269
|
- **The page** — the contiguous run of section roots replaced by one `<Sections page="<slug>" />`,
|
|
170
|
-
with the import added.
|
|
270
|
+
with the import added. Contiguous means consecutive element siblings **and** nothing but
|
|
271
|
+
whitespace or comments in the bytes between them: `<Hero />Read this first<Features />` has no
|
|
272
|
+
element in the way, and one splice over that run would delete the sentence. A page with a REGISTERED section is all or nothing: `<Sections>` renders
|
|
273
|
+
every placement the page has, so the run is replaced only when every one of the page's sections
|
|
274
|
+
resolved and every call site is a bare tag in that run. Otherwise the call sites stay exactly
|
|
275
|
+
where their author put them — replacing a subset would render a registered section twice.
|
|
171
276
|
- **The stubs** — `blocks: []` added to each page's snapshot (keys it already has are kept), and
|
|
172
277
|
`bcms-content/components.json` = `[]`.
|
|
173
278
|
|
|
@@ -211,15 +316,21 @@ import travels as `import * as X`, not as a named one.
|
|
|
211
316
|
|
|
212
317
|
| reason | |
|
|
213
318
|
|---|---|
|
|
214
|
-
| `SECTION_ROOT_AMBIGUOUS` | the group's fields sit under no single element, one section's root contains another's, or
|
|
319
|
+
| `SECTION_ROOT_AMBIGUOUS` | the group's fields sit under no single element, one section's root contains another's, the page renders it BOTH inline and through a component it imports, two imported components render it, or it is rendered somewhere this cannot register — a file this page does not import, a component of another dialect, a namespace the page never calls. The message says which. |
|
|
215
320
|
| `SECTION_NOT_CONTIGUOUS` | the page's section roots share no container at all. **The components are still written**; the page is left exactly as its author wrote it. |
|
|
216
321
|
| `SECTION_FREE_IDENTIFIERS` | the markup uses a name the component file cannot be given. The identifier is named in the message. |
|
|
217
322
|
| `COMPONENT_CONFLICT` | a file already sits at `src/components/bcms/<Name>` and this package did not write it. Decided on our marker, never on the path — that is a name a person may reasonably have chosen first. |
|
|
218
323
|
| `REGISTRY_CONFLICT` | the same, for `Sections.astro`/`Sections.tsx` or `src/lib/bcms-sections.ts`. Both are rebuilt whole on every run, so an unmarked file there would be destroyed. `--overwrite-helper` proceeds. |
|
|
324
|
+
| `REGISTRY_UNREADABLE` | the registry IS ours and its `REGISTRY`/`SHIPPED` tables cannot be read back — a `REGISTRY` naming no components at all (this module is only emitted when there is at least one, so a table with no rows is one whose rows were lost), a `SHIPPED` row that is not an object with a string `slug` (and string `bind`/`blockId` when present), a page whose placement list is EMPTY (a row is only written for a page that has some, and the fallback rendering that page blank is the worst thing this could accept), or a `SHIPPED` slug the `REGISTRY` does not name. `[null]` is valid JSON and threw a `TypeError` out of a pure function the first time anything dereferenced it. An EMPTY `SHIPPED` is read, deliberately: a run whose only page was `SECTION_NOT_CONTIGUOUS` writes the components and no order at all, so refusing that would strand the repository — a component it names is GONE from the tree, a row is not a property this can read, or the tables are missing. Every property is parsed structurally and all of them must convert; a partial read is what silently drops a page's bands. It is rebuilt whole every run, so re-emitting it from what this run happened to see would blank those pages. No flag overrides this one: deleting the file is a decision only a person can take. |
|
|
219
325
|
| `ROUTE_FILE_AMBIGUOUS` | two files serve one route. |
|
|
220
326
|
| `DIALECT_UNSUPPORTED` | the page is svelte, vue or html. Nothing is written for it. |
|
|
221
|
-
| `HELPER_CONFLICT` | `src/bcms-content.ts` is a module this package did not write. `--overwrite-helper` proceeds. |
|
|
327
|
+
| `HELPER_CONFLICT` | `src/bcms-content.ts` is a module this package did not write. Refused **for the whole run, with an empty file list**: `bcmsSection` and `bcmsFields` are what every extracted *and* every upgraded component reads through, so pending it per page while the rest of the run proceeded rewrote v1 components to import an export the helper does not provide. `--overwrite-helper` proceeds. |
|
|
222
328
|
| `SNAPSHOT_INVALID` | `bcms-content/<page>.json` is not JSON this can add `blocks` to. Refused before a byte is written for that route. |
|
|
329
|
+
| `COPY_MODE_MISMATCH` | `--copy instance` was asked of a plan whose placements are all still bound to page field groups. Refused before this repository is even opened — **the run returns an empty file list**, because a refusal that upgrades the helper and rewrites the registry is not a refusal. The message says to run `componentize_sections { copy: "instance" }` on the server first. The reverse pairing is NOT a refusal: `--copy bind` over an instance plan still produces a tree that renders, so refusing it would only stop a run that works. |
|
|
330
|
+
| `PROP_MAP_INCONSISTENT` | the plan's `fields[].path` is not `<one common prefix> + fields[].key` — `{ path: "hero.heading", key: "title" }`, or two fields that disagree about the prefix. A page path cannot be turned into a prop key losslessly, so the block-lane address would name a key the instance does not hold; falling back to `<groupKey>.` sent every edit to something nothing reads. Checked **before this repository is consulted at all**, so an already-componentized page is held to it too — doing it where the markup is cut reported such a page complete while its component went on addressing the wrong key. And checked again against the addresses it has to CONVERT: `{ groupKey: "hero", key: "title", path: "other.title" }` is internally consistent and wrong about the section, whose markup declares `hero.title` — a prefix that converts none of what the section actually declares would leave every element on the page lane whatever the placement said. A field address this cannot read statically counts as one it cannot convert, and is named in the message. Only two shapes are provable — a string literal and NOTHING else, and a template literal whose every interpolation is a LONE IDENTIFIER used as an index (`[${i}]`, what a repeater writes, which cannot change the prefix; `[${getKey()}]` names whatever that returns and is not). `{computedPath}`, `{"a" + x}` and `{"hero.title".replace("hero","footer")}` are all opaque: starting with a quote is not a proof, since that last one names `footer.title` at render time. A field carrying only one of the two says nothing about the mapping and is skipped, not refused: a Phase A plan spells no `key` at all. |
|
|
331
|
+
| `DUPLICATE_FIELD_BINDING` | one element in the section carries more than one `data-bcms-field`. There is no single address to rewrite, and rewriting both leaves the extracted element still violating the one-attribute invariant. |
|
|
332
|
+
| `INLINE_NEEDS_INSTANCE` | the page cannot be looped and the placement's copy has MOVED. The in-place call site the codemod writes — `<Hero bind="hero" page={pageSnapshot("home")} />` — comes from the plan and carries no `blockId` and no `overrides`: it never consults the CMS's blocks at all, which is what makes the inline form componentisation *without* the page builder. Emitting it would render the page's field group (the copy the dashboard is about to hide) and declare the page lane for an address that no longer holds anything. The component is still written; the page is left as its author wrote it. |
|
|
333
|
+
| `SECTION_UNMIGRATABLE` | a component an OLDER version of this package wrote cannot be brought to the current format: it no longer parses, it no longer declares its placement, its element carries two field attributes, **the prefix its markup was cut with cannot be recovered** (see below), or it already binds `bcmsField`/`bcmsFields` to something this package did not write — adding a second declaration is a file that does not compile, reported as a successful upgrade. A name is not a provenance: an import is reused only when its specifier RESOLVES to the helper module this package writes, and a `bcmsField` only when it is declared as `const bcmsField = <that exact local>(…)` — read structurally, in the module scope **and** in the component function's own body, which is where a `.tsx` component declares it. It is left exactly as it is, and the section is reported rather than counted `alreadyExtracted` on a format it is not on. |
|
|
223
334
|
| `NOT_IN_SOURCE` | no file we were given renders the group's fields, or the route names no page file. |
|
|
224
335
|
|
|
225
336
|
A plan spanning TWO frameworks gets two of everything that is dialect-shaped: its own components,
|
|
@@ -233,29 +344,75 @@ Completion is decided per ROUTE, from that page's own bytes — it imports our r
|
|
|
233
344
|
somewhere carries our marker", which is true of every group that has ever been extracted and left a
|
|
234
345
|
brand-new page untouched.
|
|
235
346
|
|
|
347
|
+
**The marker carries a FORMAT VERSION, and anything older is upgraded before it is called done.**
|
|
348
|
+
A 0.3 component (`section v1`: page-path bindings, no render-time lane) carries a marker too, so
|
|
349
|
+
reading the marker alone reported every already-componentized page finished and left its components
|
|
350
|
+
on the page lane forever — `--copy instance` there placed five sections and moved no address at
|
|
351
|
+
all. Each older component is instead brought to the current format in place: every
|
|
352
|
+
`data-bcms-field` wrapped in the chooser, the chooser declared and imported, the marker restamped.
|
|
353
|
+
The Phase A `bind ?? "<group>"` fallback is rewritten away in the same pass — through the module's own AST, by offset and through the file's OWN local name for the helper's export, so neither a call a formatter has reflowed nor one made through an aliased import is missed. A module with no readable program is refused, and so is one where nothing was rewritten while a `??` fallback sits in a call this cannot identify as the helper's. An upgrade never leaves that behaviour under a current marker. It happens once; the next run sees the current version and writes nothing. A component that binds nothing needs no prefix and cannot be ambiguous about one — the marker is its whole upgrade. A component the plan
|
|
354
|
+
says nothing about is left alone — nothing asked about it, and a v1 file is still a correct Phase A
|
|
355
|
+
component. One that cannot be upgraded is `SECTION_UNMIGRATABLE`, never silently skipped — and that refusal is about ONE component, keyed
|
|
356
|
+
by dialect as well as slug, so an unmigratable `Hero.astro` says nothing about the `Hero.tsx`
|
|
357
|
+
beside it. (Each registry also resolves its own extensionless imports to a file its dialect can
|
|
358
|
+
render, which is what makes two same-named components in one directory readable at all.)
|
|
359
|
+
|
|
360
|
+
**Which group an upgraded component was cut from is read off the FILE, not off the plan.** One
|
|
361
|
+
component serves every page whose group has its shape, and its markup carries the paths of the one
|
|
362
|
+
page it was cut from; taking the plan's first placement for that slug gave a component reading
|
|
363
|
+
`hero.*` the prefix `landingHero.` as soon as that placement came first, so every wrapped address
|
|
364
|
+
matched nothing, the block lane never fired, and the file was stamped current for good. The
|
|
365
|
+
candidate prefixes — one per placement of the slug, **server-pending ones included**, because which
|
|
366
|
+
group the bytes were cut from is a fact about the past that a refused placement may be the only
|
|
367
|
+
thing to explain — are matched against the addresses the file actually declares (a repeater's contributes everything before its first interpolation), and a slug
|
|
368
|
+
whose placements cannot be told apart that way is refused rather than guessed at.
|
|
369
|
+
|
|
370
|
+
**An already-componentized page still has a shipped entry, and the plan may have moved its copy.**
|
|
371
|
+
Its placements' `bind` is restated from the current plan on every run — in the order the registry
|
|
372
|
+
already holds, since a page whose markup was cut has no roots left to read an order off — so a bind
|
|
373
|
+
run followed by an instance run drops `bind` from the fallback table and the reverse puts it back.
|
|
374
|
+
Repeated placements of one component (two groups sharing a `shapeHash`, which is what `shapeHash` is
|
|
375
|
+
FOR) are matched **positionally**, registry order against plan order per slug: keying them by slug
|
|
376
|
+
collapsed them, and a page with one group still bound and another moved wrote both from the second.
|
|
377
|
+
Every plan occurrence holds its slot whether or not this run can act on it — one it cannot (refused,
|
|
378
|
+
server-pending) consumes its position and keeps the earlier record, because a missed update is
|
|
379
|
+
recoverable and a wrong one is a page rendering someone else's copy.
|
|
380
|
+
Nothing is extracted there, and the registry is rewritten only if those bytes actually change.
|
|
381
|
+
|
|
236
382
|
The registry and the sections library are rebuilt WHOLE each run, so they are first read back:
|
|
237
383
|
components an earlier run wrote are reused rather than rewritten, and the shipped order of every
|
|
238
384
|
page already served is carried forward. Without that, componentizing one new page emitted a
|
|
239
|
-
registry that had never heard of the others, and they rendered blank.
|
|
385
|
+
registry that had never heard of the others, and they rendered blank. The read-back goes through
|
|
386
|
+
the dialect's own PARSER, so a formatter that rewrote our imports (single quotes, a wrapped line)
|
|
387
|
+
changes nothing; the two tables are brace-matched with strings and comments skipped and every
|
|
388
|
+
property is parsed structurally, so a one-line table, a wrapped one, or a comment containing a `}`
|
|
389
|
+
are all read whole. An import must resolve to a file that is actually there — composing the path
|
|
390
|
+
from our own naming convention re-emitted a registry importing a component somebody had deleted.
|
|
391
|
+
When any of that cannot be reconstructed the run refuses by name rather than rebuilding from what
|
|
392
|
+
it happens to see.
|
|
240
393
|
|
|
241
394
|
A run whose roots DO share a container but have a foreign sibling between them is not refused: each
|
|
242
395
|
root becomes its own `<Hero …/>` where its markup was, counted `inlineOnly`. The markup is
|
|
243
|
-
componentised and the ORDER stays the repository's — looping that page would move the sibling too.
|
|
396
|
+
componentised and the ORDER stays the repository's — looping that page would move the sibling too. A placement whose copy the plan has moved cannot take that form and is
|
|
397
|
+
`INLINE_NEEDS_INSTANCE`: the in-place call site carries no `blockId` and no `overrides`.
|
|
244
398
|
|
|
245
399
|
The arithmetic is checked rather than hoped for:
|
|
246
400
|
|
|
247
401
|
```
|
|
248
|
-
plan sections === extracted + inlineOnly + alreadyExtracted + pending.length
|
|
402
|
+
plan sections === extracted + inlineOnly + alreadyExtracted + registered + pending.length
|
|
249
403
|
```
|
|
250
404
|
|
|
251
|
-
`
|
|
252
|
-
|
|
405
|
+
`registered` is orthogonal to placement: nothing was written for those sections — the file was
|
|
406
|
+
somebody's before the run and stays theirs — so counting them `extracted` would claim authorship of
|
|
407
|
+
bytes this package never produced. `alreadyExtracted` is the bucket the plan document does not
|
|
408
|
+
name, and it is what makes the round trip observable: a second run over an already-componentised tree reports `extracted: 0,
|
|
253
409
|
pending: []` **and** still accounts for every section. Our own output is recognised by a marker
|
|
254
410
|
comment, never by its path — `src/components/bcms/` is a directory anybody may put a component in.
|
|
255
411
|
|
|
256
412
|
Known leftovers, stated rather than hidden: a page whose every read moved into a component keeps
|
|
257
|
-
the now-unused `bcms`/snapshot imports P2 added
|
|
258
|
-
|
|
413
|
+
the now-unused `bcms`/snapshot imports P2 added, and a page whose registered call sites became one
|
|
414
|
+
`<Sections>` keeps the imports of the components it used to call. They are harmless at build time
|
|
415
|
+
and removing them would mean editing statements no section asked about.
|
|
259
416
|
|
|
260
417
|
## Not here yet
|
|
261
418
|
|
|
@@ -263,6 +420,9 @@ would mean editing statements no section asked about.
|
|
|
263
420
|
bind their own copy; only `findPropTargets` is jsx/astro-only, so a drilled prop there is
|
|
264
421
|
`PROP_TARGET_NOT_FOUND` rather than a wrong edit).
|
|
265
422
|
|
|
266
|
-
Componentize: svelte and vue extraction
|
|
267
|
-
|
|
268
|
-
|
|
423
|
+
Componentize: svelte and vue extraction. Phase B — copy owned by the INSTANCE — is here for both
|
|
424
|
+
dialects this lane extracts: the block-lane address is chosen at render time, so moving a page's
|
|
425
|
+
copy onto its instances is a data change rather than another cut of the repository. What Phase B
|
|
426
|
+
does NOT reach is the `inlineOnly` shape: a page whose roots could not be looped keeps calling
|
|
427
|
+
`<Hero bind="hero" …/>` in place, which never consults the CMS's blocks at all, so its copy stays
|
|
428
|
+
the page's whatever the plan says.
|