@caelo-cms/shared 0.10.72 → 0.11.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.
Files changed (184) hide show
  1. package/dist/ai-tools.d.ts +125 -50
  2. package/dist/ai-tools.d.ts.map +1 -1
  3. package/dist/ai-tools.js +48 -36
  4. package/dist/ai-tools.js.map +1 -1
  5. package/dist/build-page.d.ts +110 -30
  6. package/dist/build-page.d.ts.map +1 -1
  7. package/dist/build-page.js +23 -4
  8. package/dist/build-page.js.map +1 -1
  9. package/dist/content.d.ts +201 -100
  10. package/dist/content.d.ts.map +1 -1
  11. package/dist/content.js +176 -74
  12. package/dist/content.js.map +1 -1
  13. package/dist/css-var-aliases.d.ts +20 -0
  14. package/dist/css-var-aliases.d.ts.map +1 -0
  15. package/dist/css-var-aliases.js +95 -0
  16. package/dist/css-var-aliases.js.map +1 -0
  17. package/dist/css-var-scan.d.ts +0 -16
  18. package/dist/css-var-scan.d.ts.map +1 -1
  19. package/dist/css-var-scan.js +24 -0
  20. package/dist/css-var-scan.js.map +1 -1
  21. package/dist/edit-markers.d.ts +41 -5
  22. package/dist/edit-markers.d.ts.map +1 -1
  23. package/dist/edit-markers.js +47 -50
  24. package/dist/edit-markers.js.map +1 -1
  25. package/dist/index.d.ts +3 -1
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +3 -1
  28. package/dist/index.js.map +1 -1
  29. package/dist/interactions.js +2 -2
  30. package/dist/interactions.js.map +1 -1
  31. package/dist/link-canonicalise.d.ts.map +1 -1
  32. package/dist/link-canonicalise.js +7 -1
  33. package/dist/link-canonicalise.js.map +1 -1
  34. package/dist/link-refs.d.ts +1 -1
  35. package/dist/link-refs.js +3 -3
  36. package/dist/lists/address.d.ts +34 -0
  37. package/dist/lists/address.d.ts.map +1 -0
  38. package/dist/lists/address.js +43 -0
  39. package/dist/lists/address.js.map +1 -0
  40. package/dist/lists/default-types.d.ts +10 -0
  41. package/dist/lists/default-types.d.ts.map +1 -0
  42. package/dist/lists/default-types.js +91 -0
  43. package/dist/lists/default-types.js.map +1 -0
  44. package/dist/lists/fields.d.ts +152 -0
  45. package/dist/lists/fields.d.ts.map +1 -0
  46. package/dist/lists/fields.js +258 -0
  47. package/dist/lists/fields.js.map +1 -0
  48. package/dist/lists/index.d.ts +14 -0
  49. package/dist/lists/index.d.ts.map +1 -0
  50. package/dist/lists/index.js +15 -0
  51. package/dist/lists/index.js.map +1 -0
  52. package/dist/lists/item-attr.d.ts +26 -0
  53. package/dist/lists/item-attr.d.ts.map +1 -0
  54. package/dist/lists/item-attr.js +31 -0
  55. package/dist/lists/item-attr.js.map +1 -0
  56. package/dist/lists/items.d.ts +105 -0
  57. package/dist/lists/items.d.ts.map +1 -0
  58. package/dist/lists/items.js +304 -0
  59. package/dist/lists/items.js.map +1 -0
  60. package/dist/lists/list-type.d.ts +401 -0
  61. package/dist/lists/list-type.d.ts.map +1 -0
  62. package/dist/lists/list-type.js +275 -0
  63. package/dist/lists/list-type.js.map +1 -0
  64. package/dist/lists/model.d.ts +14 -0
  65. package/dist/lists/model.d.ts.map +1 -0
  66. package/dist/lists/model.js +15 -0
  67. package/dist/lists/model.js.map +1 -0
  68. package/dist/lists/page-type.d.ts +115 -0
  69. package/dist/lists/page-type.d.ts.map +1 -0
  70. package/dist/lists/page-type.js +66 -0
  71. package/dist/lists/page-type.js.map +1 -0
  72. package/dist/lists/refs.d.ts +13 -0
  73. package/dist/lists/refs.d.ts.map +1 -0
  74. package/dist/lists/refs.js +18 -0
  75. package/dist/lists/refs.js.map +1 -0
  76. package/dist/lists/render-data.d.ts +114 -0
  77. package/dist/lists/render-data.d.ts.map +1 -0
  78. package/dist/lists/render-data.js +3 -0
  79. package/dist/lists/render-data.js.map +1 -0
  80. package/dist/lists/render-deps.d.ts +68 -0
  81. package/dist/lists/render-deps.d.ts.map +1 -0
  82. package/dist/lists/render-deps.js +91 -0
  83. package/dist/lists/render-deps.js.map +1 -0
  84. package/dist/lists/resolve-values.d.ts +63 -0
  85. package/dist/lists/resolve-values.d.ts.map +1 -0
  86. package/dist/lists/resolve-values.js +181 -0
  87. package/dist/lists/resolve-values.js.map +1 -0
  88. package/dist/lists/resolve.d.ts +106 -0
  89. package/dist/lists/resolve.d.ts.map +1 -0
  90. package/dist/lists/resolve.js +878 -0
  91. package/dist/lists/resolve.js.map +1 -0
  92. package/dist/lists/scope.d.ts +81 -0
  93. package/dist/lists/scope.d.ts.map +1 -0
  94. package/dist/lists/scope.js +3 -0
  95. package/dist/lists/scope.js.map +1 -0
  96. package/dist/markup-structure.d.ts +8 -0
  97. package/dist/markup-structure.d.ts.map +1 -1
  98. package/dist/markup-structure.js +2 -0
  99. package/dist/markup-structure.js.map +1 -1
  100. package/dist/media.d.ts +1 -1
  101. package/dist/nested-module-render.d.ts +25 -20
  102. package/dist/nested-module-render.d.ts.map +1 -1
  103. package/dist/nested-module-render.js +122 -31
  104. package/dist/nested-module-render.js.map +1 -1
  105. package/dist/page-log.d.ts +1 -1
  106. package/dist/preview-compose.d.ts +24 -21
  107. package/dist/preview-compose.d.ts.map +1 -1
  108. package/dist/preview-compose.js +34 -118
  109. package/dist/preview-compose.js.map +1 -1
  110. package/dist/source-text-transport.d.ts +17 -0
  111. package/dist/source-text-transport.d.ts.map +1 -0
  112. package/dist/source-text-transport.js +71 -0
  113. package/dist/source-text-transport.js.map +1 -0
  114. package/dist/template-engine.d.ts +44 -31
  115. package/dist/template-engine.d.ts.map +1 -1
  116. package/dist/template-engine.js +75 -174
  117. package/dist/template-engine.js.map +1 -1
  118. package/dist/template-sections.d.ts +69 -0
  119. package/dist/template-sections.d.ts.map +1 -0
  120. package/dist/template-sections.js +423 -0
  121. package/dist/template-sections.js.map +1 -0
  122. package/dist/version.d.ts +2 -2
  123. package/dist/version.d.ts.map +1 -1
  124. package/dist/version.js +1 -1
  125. package/dist/version.js.map +1 -1
  126. package/package.json +8 -1
  127. package/src/__tests__/redos-hardening.test.ts +50 -16
  128. package/src/ai-tools-add-module-modes.test.ts +50 -0
  129. package/src/ai-tools.test.ts +10 -0
  130. package/src/ai-tools.ts +51 -37
  131. package/src/build-page.test.ts +34 -3
  132. package/src/build-page.ts +26 -3
  133. package/src/content.test.ts +111 -3
  134. package/src/content.ts +186 -75
  135. package/src/css-var-aliases.test.ts +61 -0
  136. package/src/css-var-aliases.ts +109 -0
  137. package/src/css-var-scan.ts +9 -0
  138. package/src/edit-markers.test.ts +37 -21
  139. package/src/edit-markers.ts +52 -20
  140. package/src/index.ts +3 -1
  141. package/src/interactions.ts +2 -2
  142. package/src/link-canonicalise.test.ts +36 -6
  143. package/src/link-canonicalise.ts +6 -1
  144. package/src/link-refs.test.ts +66 -42
  145. package/src/link-refs.ts +3 -3
  146. package/src/lists/address.ts +75 -0
  147. package/src/lists/default-types.ts +102 -0
  148. package/src/lists/fields.ts +285 -0
  149. package/src/lists/index.ts +16 -0
  150. package/src/lists/item-attr.test.ts +36 -0
  151. package/src/lists/item-attr.ts +49 -0
  152. package/src/lists/items.ts +402 -0
  153. package/src/lists/list-fixtures.test.ts +302 -0
  154. package/src/lists/list-type.ts +306 -0
  155. package/src/lists/lists.test.ts +328 -0
  156. package/src/lists/model.ts +16 -0
  157. package/src/lists/page-type.ts +71 -0
  158. package/src/lists/refs.ts +21 -0
  159. package/src/lists/render-data.ts +125 -0
  160. package/src/lists/render-deps.test.ts +132 -0
  161. package/src/lists/render-deps.ts +144 -0
  162. package/src/lists/resolve-values.ts +224 -0
  163. package/src/lists/resolve.test.ts +295 -0
  164. package/src/lists/resolve.ts +1148 -0
  165. package/src/lists/scope.ts +69 -0
  166. package/src/lists-compose-parity.test.ts +262 -0
  167. package/src/markup-structure.ts +10 -0
  168. package/src/nested-module-render.test.ts +111 -77
  169. package/src/nested-module-render.ts +85 -48
  170. package/src/preview-compose.test.ts +55 -19
  171. package/src/preview-compose.ts +59 -154
  172. package/src/source-text-transport.test.ts +63 -0
  173. package/src/source-text-transport.ts +70 -0
  174. package/src/template-engine-lists.test.ts +553 -0
  175. package/src/template-engine.test.ts +62 -80
  176. package/src/template-engine.ts +102 -245
  177. package/src/template-sections.ts +500 -0
  178. package/src/version.ts +1 -1
  179. package/dist/structured-sets.d.ts +0 -70
  180. package/dist/structured-sets.d.ts.map +0 -1
  181. package/dist/structured-sets.js +0 -92
  182. package/dist/structured-sets.js.map +0 -1
  183. package/src/structured-sets.ts +0 -114
  184. package/src/template-engine-data-lists.test.ts +0 -90
@@ -15,6 +15,8 @@
15
15
  */
16
16
 
17
17
  import { describe, expect, it } from "bun:test";
18
+ import { fixtureData } from "./lists/list-fixtures.test.js";
19
+ import { createListRenderContext } from "./lists/resolve.js";
18
20
  import { renderTemplate, type TemplateField } from "./template-engine.js";
19
21
 
20
22
  describe("renderTemplate — primitive substitution", () => {
@@ -89,96 +91,65 @@ describe("renderTemplate — primitive substitution", () => {
89
91
  });
90
92
  });
91
93
 
92
- describe("renderTemplate — text-list section", () => {
93
- const textListField: TemplateField = { name: "items", kind: "text-list" };
94
+ // A `list` field over the shipped `texts` / `links` types (epic #1107);
95
+ // the full list syntax lives in template-engine-lists.test.ts.
96
+ const lists = createListRenderContext(fixtureData()).forPage(null);
97
+ const uid = (n: number) => `00000000-0000-4000-9000-${String(n).padStart(12, "0")}`;
98
+ const texts = (...t: string[]) =>
99
+ t.map((text, i) => ({ id: uid(i + 1), node: "text", values: { text } }));
100
+ const unstamp = (html: string) => html.replace(/ data-caelo-list-item="[^"]*"/g, "");
94
101
 
95
- it("iterates with {{.}} per element", () => {
96
- const r = renderTemplate({
97
- html: "<ul>{{#items}}<li>{{.}}</li>{{/items}}</ul>",
98
- fields: [textListField],
99
- contentValues: { items: ["a", "b", "c"] },
100
- });
101
- expect(r.html).toBe("<ul><li>a</li><li>b</li><li>c</li></ul>");
102
- expect(r.missingSlots).toEqual([]);
103
- });
104
-
105
- it("iterates with {{item}} alias per element", () => {
106
- const r = renderTemplate({
107
- html: "<ul>{{#items}}<li>{{item}}</li>{{/items}}</ul>",
108
- fields: [textListField],
109
- contentValues: { items: ["a", "b", "c"] },
110
- });
111
- expect(r.html).toBe("<ul><li>a</li><li>b</li><li>c</li></ul>");
112
- });
102
+ describe("renderTemplate — list section", () => {
103
+ const listField: TemplateField = { name: "items", kind: "list", listType: "texts" };
113
104
 
114
- it("coerces numeric + boolean elements to strings", () => {
105
+ it("iterates a local list per entry", () => {
115
106
  const r = renderTemplate({
116
- html: "<ul>{{#items}}<li>{{.}}</li>{{/items}}</ul>",
117
- fields: [textListField],
118
- contentValues: { items: [1, true, "x"] },
107
+ html: "<ul>{{#items}}<li>{{text}}</li>{{/items}}</ul>",
108
+ fields: [listField],
109
+ contentValues: { items: texts("a", "b", "c") },
110
+ lists,
119
111
  });
120
- expect(r.html).toBe("<ul><li>1</li><li>true</li><li>x</li></ul>");
112
+ expect(unstamp(r.html)).toBe("<ul><li>a</li><li>b</li><li>c</li></ul>");
113
+ expect(r.missingSlots).toEqual([]);
121
114
  });
122
115
 
123
116
  it("empty array renders as empty section, no missingSlots", () => {
124
117
  const r = renderTemplate({
125
- html: "<ul>{{#items}}<li>{{.}}</li>{{/items}}</ul>",
126
- fields: [textListField],
118
+ html: "<ul>{{#items}}<li>{{text}}</li>{{/items}}</ul>",
119
+ fields: [listField],
127
120
  contentValues: { items: [] },
121
+ lists,
128
122
  });
129
123
  expect(r.html).toBe("<ul></ul>");
130
124
  expect(r.missingSlots).toEqual([]);
131
125
  });
132
126
 
133
- it("per-element malformed object becomes loud comment + tracks text-list-malformed", () => {
127
+ it("a malformed entry becomes a loud raw section + tracks list-malformed", () => {
134
128
  const r = renderTemplate({
135
- html: "<ul>{{#items}}<li>{{.}}</li>{{/items}}</ul>",
136
- fields: [textListField],
137
- contentValues: { items: ["a", { bogus: true }, "c"] },
129
+ html: "<ul>{{#items}}<li>{{text}}</li>{{/items}}</ul>",
130
+ fields: [listField],
131
+ contentValues: { items: [...texts("a"), { bogus: true }] },
132
+ lists,
138
133
  });
139
- expect(r.html).toContain("<li>a</li>");
140
- expect(r.html).toContain("<li>c</li>");
141
- expect(r.html).toContain("<!-- caelo:missing reason=text-list-malformed items[1] -->");
142
- expect(r.missingSlots).toContain("text-list-malformed:items[1]");
134
+ expect(r.html).toBe("<ul>{{#items}}<li>{{text}}</li>{{/items}}</ul>");
135
+ expect(r.missingSlots).toEqual(["list-malformed:items[items[1]]"]);
143
136
  });
144
- });
145
-
146
- describe("renderTemplate — link-list section (AC #1)", () => {
147
- const linkListField: TemplateField = { name: "nav_items", kind: "link-list" };
148
137
 
149
- it("AC #1 verbatim fixture", () => {
138
+ it("links: label + link per entry (AC #1, list kind)", () => {
150
139
  const r = renderTemplate({
151
- html: '<nav>{{#nav_items}}<a href="{{href}}">{{label}}</a>{{/nav_items}}</nav>',
152
- fields: [linkListField],
140
+ html: '<nav>{{#nav_items}}<a href="{{link}}">{{label}}</a>{{/nav_items}}</nav>',
141
+ fields: [{ name: "nav_items", kind: "list", listType: "links" }],
153
142
  contentValues: {
154
143
  nav_items: [
155
- { label: "Docs", href: "/docs" },
156
- { label: "Blog", href: "/blog" },
144
+ { id: uid(1), node: "link", values: { label: "Docs", link: { href: "/docs" } } },
145
+ { id: uid(2), node: "link", values: { label: "Blog", link: { href: "/blog" } } },
157
146
  ],
158
147
  },
148
+ lists,
159
149
  });
160
- expect(r.html).toBe('<nav><a href="/docs">Docs</a><a href="/blog">Blog</a></nav>');
161
- expect(r.html).not.toMatch(/\{\{[#/]/);
162
- expect(r.html).not.toMatch(/\{\{label/);
163
- expect(r.html).not.toMatch(/\{\{href/);
150
+ expect(unstamp(r.html)).toBe('<nav><a href="/docs">Docs</a><a href="/blog">Blog</a></nav>');
164
151
  expect(r.missingSlots).toEqual([]);
165
152
  });
166
-
167
- it("per-element malformed (missing href) becomes loud comment + tracks link-list-malformed", () => {
168
- const r = renderTemplate({
169
- html: '<nav>{{#nav_items}}<a href="{{href}}">{{label}}</a>{{/nav_items}}</nav>',
170
- fields: [linkListField],
171
- contentValues: {
172
- nav_items: [
173
- { label: "OK", href: "/a" },
174
- { label: "OK only" }, // missing href
175
- ],
176
- },
177
- });
178
- expect(r.html).toContain('<a href="/a">OK</a>');
179
- expect(r.html).toContain("<!-- caelo:missing reason=link-list-malformed nav_items[1] -->");
180
- expect(r.missingSlots).toContain("link-list-malformed:nav_items[1]");
181
- });
182
153
  });
183
154
 
184
155
  describe("renderTemplate — module-list section", () => {
@@ -305,23 +276,33 @@ describe("renderTemplate — empty single module field (#1041)", () => {
305
276
  });
306
277
 
307
278
  describe("renderTemplate — literal braces in content are data (#1053)", () => {
308
- it("a text-list item keeps a literal {{Page URL}}", () => {
279
+ it("a list entry keeps a literal {{Page URL}}", () => {
309
280
  const r = renderTemplate({
310
- html: "<ul>{{#items}}<li>{{.}}</li>{{/items}}</ul>",
311
- fields: [{ name: "items", kind: "text-list" }],
312
- contentValues: { items: ["link.href = '{{Page URL}}';", "{{title}}"] },
281
+ html: "<ul>{{#items}}<li>{{text}}</li>{{/items}}</ul>",
282
+ fields: [{ name: "items", kind: "list", listType: "texts" }],
283
+ contentValues: { items: texts("link.href = '{{Page URL}}';", "{{title}}") },
284
+ lists,
313
285
  });
314
- expect(r.html).toBe("<ul><li>link.href = '{{Page URL}}';</li><li>{{title}}</li></ul>");
286
+ expect(unstamp(r.html)).toBe("<ul><li>link.href = '{{Page URL}}';</li><li>{{title}}</li></ul>");
315
287
  expect(r.missingSlots).toEqual([]);
316
288
  });
317
289
 
318
- it("a link-list label is never re-substituted as a placeholder", () => {
290
+ it("a list entry's label is never re-substituted as a placeholder", () => {
319
291
  const r = renderTemplate({
320
- html: '<nav>{{#nav}}<a href="{{href}}">{{label}}</a>{{/nav}}</nav>',
321
- fields: [{ name: "nav", kind: "link-list" }],
322
- contentValues: { nav: [{ label: "{{href}} {{Click URL}}", href: "/x" }] },
292
+ html: '<nav>{{#nav}}<a href="{{link}}">{{label}}</a>{{/nav}}</nav>',
293
+ fields: [{ name: "nav", kind: "list", listType: "links" }],
294
+ contentValues: {
295
+ nav: [
296
+ {
297
+ id: uid(1),
298
+ node: "link",
299
+ values: { label: "{{link}} {{Click URL}}", link: { href: "/x" } },
300
+ },
301
+ ],
302
+ },
303
+ lists,
323
304
  });
324
- expect(r.html).toBe('<nav><a href="/x">{{href}} {{Click URL}}</a></nav>');
305
+ expect(unstamp(r.html)).toBe('<nav><a href="/x">{{link}} {{Click URL}}</a></nav>');
325
306
  });
326
307
 
327
308
  it("rendered nested module HTML is pasted verbatim, not parsed again", () => {
@@ -393,11 +374,12 @@ describe("renderTemplate — whitespace tolerance in markers", () => {
393
374
 
394
375
  it("section {{ #items }}…{{ /items }} (whitespace inside braces) iterates normally", () => {
395
376
  const r = renderTemplate({
396
- html: "<ul>{{ #items }}<li>{{ . }}</li>{{ /items }}</ul>",
397
- fields: [{ name: "items", kind: "text-list" }],
398
- contentValues: { items: ["a", "b"] },
377
+ html: "<ul>{{ #items }}<li>{{ text }}</li>{{ /items }}</ul>",
378
+ fields: [{ name: "items", kind: "list", listType: "texts" }],
379
+ contentValues: { items: texts("a", "b") },
380
+ lists,
399
381
  });
400
- expect(r.html).toBe("<ul><li>a</li><li>b</li></ul>");
382
+ expect(unstamp(r.html)).toBe("<ul><li>a</li><li>b</li></ul>");
401
383
  });
402
384
 
403
385
  it("partial {{ > hero }} (whitespace inside braces) resolves normally", () => {
@@ -456,10 +438,10 @@ describe("renderTemplate — failure-marker parity", () => {
456
438
  it("kind-mismatch on {{>name}} against a list field", () => {
457
439
  const r = renderTemplate({
458
440
  html: "<aside>{{>items}}</aside>",
459
- fields: [{ name: "items", kind: "text-list" }],
441
+ fields: [{ name: "items", kind: "list", listType: "texts" }],
460
442
  contentValues: {},
461
443
  });
462
- const expected = "kind-mismatch:items expected=module actual=text-list";
444
+ const expected = "kind-mismatch:items expected=module actual=list";
463
445
  expect(r.html).toBe(`<aside><!-- caelo:missing reason=${expected} --></aside>`);
464
446
  expect(r.missingSlots).toContain(expected);
465
447
  });