@bettercms-ai/convert 0.7.0 → 0.8.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 CHANGED
@@ -1,5 +1,53 @@
1
1
  # @bettercms-ai/convert
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8a4e961: Bind array literals written inline in the markup, and drill a repeater row's leaf into the
8
+ component that renders it.
9
+
10
+ `{['Offer', 'Structure', 'RTL'].map((item) => <li>…</li>)}` had no lane at all — the list lane
11
+ searches the module for a NAMED array, and an inline literal has no name — so every one of its
12
+ items came back `IN_EXPRESSION` with the copy in plain sight. The `]` immediately before the
13
+ `.map(` is the literal's close, which is what makes it safe without a name: there is exactly one
14
+ loop over it and nothing else in the file can hold a second reference. A value rendered beside an
15
+ icon (`<li><span>✓</span>{item}</li>`) gets a `<span>` of its own instead of declaring on the
16
+ parent, because a publish writing the value over the parent deletes the icon.
17
+
18
+ `demos.map((demo) => <DemoCard title={demo.title} />)` refused the whole group with
19
+ `PROP_TARGET_NOT_FOUND`: the value is rendered in the component's file, not the page's. It now
20
+ follows the same drill the scalar lane has always used — a row's leaf is that journey with an index
21
+ in the path. The call site declares one `bcmsBindings={{ title: `cards[${i}].title` }}` per element
22
+ and the component gets `data-bcms-field={bcmsBindings?.title}`. `{...row}` counts as passing the
23
+ prop. Every leaf placed or none, as before: a drill that misses refuses the group by its own reason.
24
+
25
+ Also: `tsconfig.json` / `jsconfig.json` are now opened by the scan gate. `aliasesFrom` reads them
26
+ and nothing ever handed it one, so every `paths` alias resolved to nothing and every prop drilled
27
+ through an `@components/*` import refused with the component sitting right there.
28
+
29
+ `bindDataLiterals` is async for this; it is exported, and a caller outside this package has to
30
+ await it.
31
+
32
+ - 07f6648: Bind arrays of primitives and tuples rendered through `.map`.
33
+
34
+ `const promise = [['Once', 'One message'], …]` iterated as `promise.map(([title, text], i) => …)`
35
+ is the same shape as a row of objects one rung simpler, and the derive lane describes it as a
36
+ repeater group all the same. The declaration becomes `bcmsTuples(page, [[…paths…], …], [ …the
37
+ original array, verbatim… ])` — `bcmsList` for a list one wide — and each rendered element carries
38
+ the same templated `data-bcms-field` a repeater writes, so a second run reads it back and changes
39
+ nothing. The committed helper is v6; an existing v5 helper upgrades in place.
40
+
41
+ The read maps over the path list rather than over the CMS array, so every row's text is editable
42
+ and the number of rows is still the template's: the receipt adds one `PRIMITIVE_LIST` note per
43
+ array saying a row added in the CMS does not appear and a deleted one falls back to the template's
44
+ own copy. A list whose paths are unrelated scalars, one used anywhere but that single `.map` or
45
+ `export`ed, one rendered inside a larger expression, one whose tuple position changes kind between
46
+ rows, and one the brief covers only part of are all refused by name.
47
+
48
+ On the Shatter acceptance repository this moves 16 paths from `IN_EXPRESSION` to `rewritten`
49
+ (219 → 235 of 735).
50
+
3
51
  ## 0.7.0
4
52
 
5
53
  ### Minor Changes
package/README.md CHANGED
@@ -88,6 +88,58 @@ inherits row 0's styling). Each row element then declares
88
88
  `` data-bcms-field={`cards[${i}].<leaf>`} ``, and the callback gains an index parameter if it had
89
89
  none. An imported data module stays `IN_DATA_FILE`.
90
90
 
91
+ **An array of PRIMITIVES or TUPLES is converted through its paths, not through its name.** The
92
+ same shape one rung simpler: `const deliverables = ["…", "…", "…"]`, or
93
+ `const faqs = [["q", "a"], …]`, iterated by `.map` with the callback rendering the item (or its
94
+ destructured positions) on one element each. There is no row object to key, so every element's
95
+ path is passed to the read:
96
+
97
+ ```js
98
+ const faqs = bcmsTuples(bcmsLanding, [["faqs[0].dt-field", "faqs[0].dd-field"], …],
99
+ [ …the original array, verbatim… ]);
100
+ ```
101
+
102
+ `bcmsList` is the same for a list one wide. Each rendered element declares
103
+ `` data-bcms-field={`faqs[${i}].<leaf>`} `` — the same templated path a repeater writes, so a
104
+ second run reads it back and changes nothing — and the callback gains an index parameter, and a
105
+ jsx row its `key`, if it had none.
106
+
107
+ These arrays render exactly the paths this run wrote. Unlike `bcmsRows`, which reads the array
108
+ itself, `bcmsList`/`bcmsTuples` map over that fixed path list — so every row's TEXT is editable and
109
+ the NUMBER of rows is still the template's: a row added in the CMS has no path and never appears,
110
+ and a row deleted falls back to the copy the template shipped with. The receipt says so out loud,
111
+ once per array, as a `PRIMITIVE_LIST` note. Reading the array itself is the derive lane's job, not a
112
+ fourth shape here.
113
+
114
+ The lane refuses, by name: `IN_EXPRESSION` when the identifier is used anywhere but that one `.map`
115
+ or is `export`ed (the read replaces the value everywhere, including in files this run cannot see),
116
+ when the callback renders the item inside a larger expression, when an element is not a string
117
+ literal, when one tuple position is text in one row and richtext in the next (one element has one
118
+ sink), when the brief's paths do not cover EVERY element of the array, and when the paths are
119
+ unrelated scalars rather than one group's rows — that last one could only be declared as an
120
+ expression nothing downstream can verify; `REPEATER_AMBIGUOUS` when two loops iterate the array.
121
+
122
+ **An array literal written INLINE in the markup is the same lane, with no name between the two
123
+ halves.** `{['Offer', 'Structure', 'RTL'].map((item) => <li>…</li>)}` is what a template reaches
124
+ for whenever the list is four words long — nobody lifts four bullet points into a `const` — and the
125
+ copy then sits inside an expression the matcher is blind to by construction. The `]` immediately
126
+ before the `.map(` IS the literal's close, so there is exactly one loop over it and nothing else in
127
+ the file can hold a second reference to it: no "used anywhere but that one `.map`" test is needed,
128
+ and everything else — the paths, the templated declaration, the index parameter, the refusals — is
129
+ the named lane's. A value rendered BESIDE something else (`<li><span>✓</span>{item}</li>`) gets a
130
+ `<span>` of its own rather than declaring on the parent, because a publish writing the value over
131
+ the parent deletes the icon; the same wrap the scalar lane writes for a mixed element.
132
+
133
+ **A row's leaf handed to a component is drilled, exactly as a scalar prop is.**
134
+ `demos.map((demo) => <DemoCard title={demo.title} />)` renders the value in the COMPONENT's file,
135
+ so the page's call site declares `` bcmsBindings={{ title: `cards[${i}].title` }} `` — one object
136
+ per element, merged when a row hands two leaves to one component — and the component's element gets
137
+ `data-bcms-field={bcmsBindings?.title}` with the prop added to its destructuring and its props
138
+ type. `{...row}` counts as passing the prop: it is how most templates hand a row over. The group
139
+ still binds whole or not at all, so a drill that misses (the component cannot be resolved, nothing
140
+ in it renders the prop, this conversion already rewrites that file, or the call site already
141
+ carries a `bcmsBindings`) refuses the WHOLE group by the drill's own reason.
142
+
91
143
  **An image is bound through its import, not through its url.** An image's `original` is the name
92
144
  the bundler minted (`/_astro/hero.do77EGgx_ZNfVA8.jpg`) — it exists in no file, and the asset
93
145
  itself is not a source candidate, so a text search can never find it. What survives the build is
@@ -470,12 +522,15 @@ and removing them would mean editing statements no section asked about.
470
522
 
471
523
  ## Not here yet
472
524
 
473
- An array of PRIMITIVES — `const items = ["a", "b", "c"]`, or of tuples — whose elements the brief
474
- gave a separate NON-indexed path each (`li-field`, `li-rtl`, …) rather than one repeater group.
475
- There is no group to key and no constant to rewrite, so those stay `IN_EXPRESSION`; what would fix
476
- them is the derive lane minting the group, not a third shape here. Svelte and vue data literals are
477
- not converted either — both lanes are written against the two dialects that keep their module and
478
- their markup in one file with an AST this package already holds.
525
+ GROWABLE lists. An array of primitives or tuples is bound (above) but renders the number of rows
526
+ the template shipped with, because the read maps over a fixed path list rather than over the CMS
527
+ array — and when the brief gave its elements unrelated scalar paths (`li-field`, `li-rtl`, …)
528
+ rather than one group's rows, it is not bound at all: the only declaration that could carry a
529
+ different path per row is an expression, and one nothing downstream can verify is worse than the
530
+ honest `IN_EXPRESSION`. Both are the same fix on the derive side — minting the group — not a
531
+ further shape here. Svelte and vue data literals are not converted
532
+ either; all three lanes are written against the two dialects that keep their module and their
533
+ markup in one file with an AST this package already holds.
479
534
 
480
535
  SHARED CHROME the brief still calls page copy. A header rendered by one component that nine routes
481
536
  each declare a `nav[0].label` for is nine targets on one element, and there is no honest binding: a