@caelo-cms/shared 0.10.72 → 0.11.1

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 (204) hide show
  1. package/dist/ai-tools.d.ts +103 -142
  2. package/dist/ai-tools.d.ts.map +1 -1
  3. package/dist/ai-tools.js +79 -167
  4. package/dist/ai-tools.js.map +1 -1
  5. package/dist/build-page.d.ts +115 -35
  6. package/dist/build-page.d.ts.map +1 -1
  7. package/dist/build-page.js +31 -12
  8. package/dist/build-page.js.map +1 -1
  9. package/dist/content.d.ts +214 -102
  10. package/dist/content.d.ts.map +1 -1
  11. package/dist/content.js +219 -84
  12. package/dist/content.js.map +1 -1
  13. package/dist/css-scope-scan.js +1 -1
  14. package/dist/css-var-aliases.d.ts +20 -0
  15. package/dist/css-var-aliases.d.ts.map +1 -0
  16. package/dist/css-var-aliases.js +95 -0
  17. package/dist/css-var-aliases.js.map +1 -0
  18. package/dist/css-var-scan.d.ts +0 -16
  19. package/dist/css-var-scan.d.ts.map +1 -1
  20. package/dist/css-var-scan.js +24 -0
  21. package/dist/css-var-scan.js.map +1 -1
  22. package/dist/edit-markers.d.ts +41 -5
  23. package/dist/edit-markers.d.ts.map +1 -1
  24. package/dist/edit-markers.js +47 -50
  25. package/dist/edit-markers.js.map +1 -1
  26. package/dist/escaped-line-breaks.d.ts +44 -0
  27. package/dist/escaped-line-breaks.d.ts.map +1 -0
  28. package/dist/escaped-line-breaks.js +203 -0
  29. package/dist/escaped-line-breaks.js.map +1 -0
  30. package/dist/index.d.ts +4 -1
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +4 -1
  33. package/dist/index.js.map +1 -1
  34. package/dist/interactions.js +6 -6
  35. package/dist/interactions.js.map +1 -1
  36. package/dist/link-canonicalise.d.ts.map +1 -1
  37. package/dist/link-canonicalise.js +7 -1
  38. package/dist/link-canonicalise.js.map +1 -1
  39. package/dist/link-refs.d.ts +1 -1
  40. package/dist/link-refs.js +3 -3
  41. package/dist/lists/address.d.ts +34 -0
  42. package/dist/lists/address.d.ts.map +1 -0
  43. package/dist/lists/address.js +43 -0
  44. package/dist/lists/address.js.map +1 -0
  45. package/dist/lists/default-types.d.ts +22 -0
  46. package/dist/lists/default-types.d.ts.map +1 -0
  47. package/dist/lists/default-types.js +127 -0
  48. package/dist/lists/default-types.js.map +1 -0
  49. package/dist/lists/fields.d.ts +152 -0
  50. package/dist/lists/fields.d.ts.map +1 -0
  51. package/dist/lists/fields.js +259 -0
  52. package/dist/lists/fields.js.map +1 -0
  53. package/dist/lists/index.d.ts +14 -0
  54. package/dist/lists/index.d.ts.map +1 -0
  55. package/dist/lists/index.js +15 -0
  56. package/dist/lists/index.js.map +1 -0
  57. package/dist/lists/item-attr.d.ts +26 -0
  58. package/dist/lists/item-attr.d.ts.map +1 -0
  59. package/dist/lists/item-attr.js +31 -0
  60. package/dist/lists/item-attr.js.map +1 -0
  61. package/dist/lists/items.d.ts +105 -0
  62. package/dist/lists/items.d.ts.map +1 -0
  63. package/dist/lists/items.js +304 -0
  64. package/dist/lists/items.js.map +1 -0
  65. package/dist/lists/list-type.d.ts +401 -0
  66. package/dist/lists/list-type.d.ts.map +1 -0
  67. package/dist/lists/list-type.js +275 -0
  68. package/dist/lists/list-type.js.map +1 -0
  69. package/dist/lists/model.d.ts +14 -0
  70. package/dist/lists/model.d.ts.map +1 -0
  71. package/dist/lists/model.js +15 -0
  72. package/dist/lists/model.js.map +1 -0
  73. package/dist/lists/page-type.d.ts +115 -0
  74. package/dist/lists/page-type.d.ts.map +1 -0
  75. package/dist/lists/page-type.js +66 -0
  76. package/dist/lists/page-type.js.map +1 -0
  77. package/dist/lists/refs.d.ts +13 -0
  78. package/dist/lists/refs.d.ts.map +1 -0
  79. package/dist/lists/refs.js +18 -0
  80. package/dist/lists/refs.js.map +1 -0
  81. package/dist/lists/render-data.d.ts +123 -0
  82. package/dist/lists/render-data.d.ts.map +1 -0
  83. package/dist/lists/render-data.js +3 -0
  84. package/dist/lists/render-data.js.map +1 -0
  85. package/dist/lists/render-deps.d.ts +69 -0
  86. package/dist/lists/render-deps.d.ts.map +1 -0
  87. package/dist/lists/render-deps.js +93 -0
  88. package/dist/lists/render-deps.js.map +1 -0
  89. package/dist/lists/resolve-values.d.ts +63 -0
  90. package/dist/lists/resolve-values.d.ts.map +1 -0
  91. package/dist/lists/resolve-values.js +181 -0
  92. package/dist/lists/resolve-values.js.map +1 -0
  93. package/dist/lists/resolve.d.ts +119 -0
  94. package/dist/lists/resolve.d.ts.map +1 -0
  95. package/dist/lists/resolve.js +970 -0
  96. package/dist/lists/resolve.js.map +1 -0
  97. package/dist/lists/scope.d.ts +81 -0
  98. package/dist/lists/scope.d.ts.map +1 -0
  99. package/dist/lists/scope.js +3 -0
  100. package/dist/lists/scope.js.map +1 -0
  101. package/dist/markup-structure.d.ts +8 -0
  102. package/dist/markup-structure.d.ts.map +1 -1
  103. package/dist/markup-structure.js +2 -0
  104. package/dist/markup-structure.js.map +1 -1
  105. package/dist/media.d.ts +1 -1
  106. package/dist/nested-module-render.d.ts +38 -20
  107. package/dist/nested-module-render.d.ts.map +1 -1
  108. package/dist/nested-module-render.js +156 -31
  109. package/dist/nested-module-render.js.map +1 -1
  110. package/dist/page-log.d.ts +1 -1
  111. package/dist/preview-compose.d.ts +24 -21
  112. package/dist/preview-compose.d.ts.map +1 -1
  113. package/dist/preview-compose.js +34 -118
  114. package/dist/preview-compose.js.map +1 -1
  115. package/dist/redirect-plan.d.ts +13 -0
  116. package/dist/redirect-plan.d.ts.map +1 -1
  117. package/dist/redirect-plan.js +22 -0
  118. package/dist/redirect-plan.js.map +1 -1
  119. package/dist/seo.d.ts +21 -0
  120. package/dist/seo.d.ts.map +1 -1
  121. package/dist/seo.js +32 -0
  122. package/dist/seo.js.map +1 -1
  123. package/dist/source-text-transport.d.ts +43 -0
  124. package/dist/source-text-transport.d.ts.map +1 -0
  125. package/dist/source-text-transport.js +79 -0
  126. package/dist/source-text-transport.js.map +1 -0
  127. package/dist/template-engine.d.ts +44 -31
  128. package/dist/template-engine.d.ts.map +1 -1
  129. package/dist/template-engine.js +75 -174
  130. package/dist/template-engine.js.map +1 -1
  131. package/dist/template-sections.d.ts +69 -0
  132. package/dist/template-sections.d.ts.map +1 -0
  133. package/dist/template-sections.js +436 -0
  134. package/dist/template-sections.js.map +1 -0
  135. package/dist/version.d.ts +2 -2
  136. package/dist/version.d.ts.map +1 -1
  137. package/dist/version.js +1 -1
  138. package/dist/version.js.map +1 -1
  139. package/package.json +8 -1
  140. package/src/__tests__/redos-hardening.test.ts +51 -16
  141. package/src/ai-tools-add-module-modes.test.ts +51 -1
  142. package/src/ai-tools.test.ts +7 -10
  143. package/src/ai-tools.ts +90 -180
  144. package/src/bug-intake.test.ts +1 -1
  145. package/src/build-page.test.ts +34 -3
  146. package/src/build-page.ts +34 -11
  147. package/src/content.test.ts +137 -4
  148. package/src/content.ts +230 -85
  149. package/src/css-scope-scan.ts +1 -1
  150. package/src/css-var-aliases.test.ts +61 -0
  151. package/src/css-var-aliases.ts +109 -0
  152. package/src/css-var-scan.ts +9 -0
  153. package/src/edit-markers.test.ts +37 -21
  154. package/src/edit-markers.ts +52 -20
  155. package/src/escaped-line-breaks.ts +221 -0
  156. package/src/index.ts +4 -1
  157. package/src/interactions.ts +6 -6
  158. package/src/link-canonicalise.test.ts +36 -6
  159. package/src/link-canonicalise.ts +6 -1
  160. package/src/link-refs.test.ts +67 -42
  161. package/src/link-refs.ts +3 -3
  162. package/src/lists/address.ts +75 -0
  163. package/src/lists/default-types.ts +141 -0
  164. package/src/lists/fields.ts +286 -0
  165. package/src/lists/index.ts +16 -0
  166. package/src/lists/item-attr.test.ts +36 -0
  167. package/src/lists/item-attr.ts +49 -0
  168. package/src/lists/items.ts +402 -0
  169. package/src/lists/list-fixtures.test.ts +308 -0
  170. package/src/lists/list-type.ts +306 -0
  171. package/src/lists/lists.test.ts +328 -0
  172. package/src/lists/model.ts +16 -0
  173. package/src/lists/page-type.ts +71 -0
  174. package/src/lists/refs.ts +21 -0
  175. package/src/lists/render-data.ts +134 -0
  176. package/src/lists/render-deps.test.ts +132 -0
  177. package/src/lists/render-deps.ts +147 -0
  178. package/src/lists/resolve-values.ts +224 -0
  179. package/src/lists/resolve.test.ts +472 -0
  180. package/src/lists/resolve.ts +1246 -0
  181. package/src/lists/scope.ts +69 -0
  182. package/src/lists-compose-parity.test.ts +262 -0
  183. package/src/markup-structure.ts +10 -0
  184. package/src/nested-module-render.test.ts +147 -74
  185. package/src/nested-module-render.ts +120 -48
  186. package/src/preview-compose.test.ts +55 -19
  187. package/src/preview-compose.ts +59 -154
  188. package/src/redirect-plan.test.ts +25 -0
  189. package/src/redirect-plan.ts +24 -0
  190. package/src/seo.test.ts +58 -0
  191. package/src/seo.ts +49 -0
  192. package/src/source-text-transport.test.ts +158 -0
  193. package/src/source-text-transport.ts +81 -0
  194. package/src/template-engine-lists.test.ts +608 -0
  195. package/src/template-engine.test.ts +62 -80
  196. package/src/template-engine.ts +102 -245
  197. package/src/template-sections.ts +514 -0
  198. package/src/version.ts +1 -1
  199. package/dist/structured-sets.d.ts +0 -70
  200. package/dist/structured-sets.d.ts.map +0 -1
  201. package/dist/structured-sets.js +0 -92
  202. package/dist/structured-sets.js.map +0 -1
  203. package/src/structured-sets.ts +0 -114
  204. package/src/template-engine-data-lists.test.ts +0 -90
@@ -19,6 +19,8 @@
19
19
  */
20
20
 
21
21
  import { describe, expect, it } from "bun:test";
22
+ import { fixtureData, INSTANCE, ITEM, LIST, MODULE, PAGE } from "./lists/list-fixtures.test.js";
23
+ import { createListRenderContext } from "./lists/resolve.js";
22
24
  import {
23
25
  collectNestedRefs,
24
26
  type NestedContentInstanceResource,
@@ -27,6 +29,7 @@ import {
27
29
  type NestedRenderResolver,
28
30
  renderModuleWithContent,
29
31
  renderPlacedModule,
32
+ rewriteNestedRefs,
30
33
  } from "./nested-module-render.js";
31
34
 
32
35
  function buildResolver(
@@ -288,148 +291,214 @@ describe("renderModuleWithContent — empty optional module field (#1041)", () =
288
291
  });
289
292
  });
290
293
 
291
- describe("renderModuleWithContent — list-of-primitive slots", () => {
292
- it("text-list iterates {{.}} per string element", () => {
294
+ describe("renderModuleWithContent — list fields (epic #1107)", () => {
295
+ const lists = createListRenderContext(fixtureData()).forPage(PAGE.home);
296
+ const T = (n: number) => `00000000-0000-4000-9000-${String(n).padStart(12, "0")}`;
297
+ const texts = (...t: string[]) =>
298
+ t.map((text, i) => ({ id: T(i + 1), node: "text", values: { text } }));
299
+ const PARENT_CI = "00000000-0000-4000-8000-00000000bbb1";
300
+
301
+ it("iterates a local list and addresses entries by the content instance", () => {
293
302
  const resolver = buildResolver(
294
303
  [
295
304
  {
296
305
  moduleId: PARENT_MOD_ID,
297
306
  slug: "tag-cloud",
298
- html: "<div>{{#tags}}<span>{{.}}</span>{{/tags}}</div>",
307
+ html: "<div>{{#tags}}<span>{{text}}</span>{{/tags}}</div>",
299
308
  css: "",
300
309
  js: "",
301
- fields: [{ name: "tags", kind: "text-list" }],
310
+ fields: [{ name: "tags", kind: "list", listType: "texts" }],
302
311
  },
303
312
  ],
304
313
  [
305
314
  {
306
- id: PARENT_CI_ID,
315
+ id: PARENT_CI,
307
316
  moduleId: PARENT_MOD_ID,
308
- values: { tags: ["alpha", "beta", "gamma"] },
317
+ values: { tags: texts("alpha", "beta") },
309
318
  deletedAt: null,
310
319
  },
311
320
  ],
312
321
  );
313
- const r = renderModuleWithContent(PARENT_MOD_ID, PARENT_CI_ID, resolver);
314
- expect(r.html).toBe("<div><span>alpha</span><span>beta</span><span>gamma</span></div>");
322
+ const r = renderModuleWithContent(PARENT_MOD_ID, PARENT_CI, resolver, undefined, lists);
323
+ expect(r.html).toBe(
324
+ `<div><span data-caelo-list-item="instance/${PARENT_CI}/tags/${T(1)}">alpha</span>` +
325
+ `<span data-caelo-list-item="instance/${PARENT_CI}/tags/${T(2)}">beta</span></div>`,
326
+ );
315
327
  expect(r.missingSlots).toEqual([]);
316
328
  });
317
329
 
318
- it("text-list also accepts the {{item}} alias", () => {
330
+ it("an edit-marked copy keeps the real instance as the entries' address", () => {
319
331
  const resolver = buildResolver(
320
332
  [
321
333
  {
322
334
  moduleId: PARENT_MOD_ID,
323
- slug: "bullets",
324
- html: "<ul>{{#bullets}}<li>{{item}}</li>{{/bullets}}</ul>",
335
+ slug: "tag-cloud",
336
+ html: "{{#tags}}<i>{{text}}</i>{{/tags}}",
325
337
  css: "",
326
338
  js: "",
327
- fields: [{ name: "bullets", kind: "text-list" }],
339
+ fields: [{ name: "tags", kind: "list", listType: "texts" }],
328
340
  },
329
341
  ],
330
342
  [
331
343
  {
332
- id: PARENT_CI_ID,
344
+ id: "edit:content#0",
333
345
  moduleId: PARENT_MOD_ID,
334
- values: { bullets: ["one", "two"] },
346
+ values: { tags: texts("a") },
335
347
  deletedAt: null,
348
+ listHolderId: PARENT_CI,
336
349
  },
337
350
  ],
338
351
  );
339
- const r = renderModuleWithContent(PARENT_MOD_ID, PARENT_CI_ID, resolver);
340
- expect(r.html).toBe("<ul><li>one</li><li>two</li></ul>");
352
+ const r = renderModuleWithContent(PARENT_MOD_ID, "edit:content#0", resolver, undefined, lists);
353
+ expect(r.html).toContain(`instance/${PARENT_CI}/tags/`);
341
354
  });
342
355
 
343
- it("text-list falls back to field.default when values entry is absent", () => {
356
+ it("falls back to the field default when the values entry is absent", () => {
344
357
  const resolver = buildResolver(
345
358
  [
346
359
  {
347
360
  moduleId: PARENT_MOD_ID,
348
361
  slug: "tag-cloud",
349
- html: "<div>{{#tags}}<span>{{.}}</span>{{/tags}}</div>",
362
+ html: "<div>{{#tags}}{{text}};{{/tags}}</div>",
350
363
  css: "",
351
364
  js: "",
352
365
  fields: [
353
- {
354
- name: "tags",
355
- kind: "text-list",
356
- default: ["alpha", "beta"],
357
- },
366
+ { name: "tags", kind: "list", listType: "texts", default: texts("alpha", "beta") },
358
367
  ],
359
368
  },
360
369
  ],
361
370
  [{ id: PARENT_CI_ID, moduleId: PARENT_MOD_ID, values: {}, deletedAt: null }],
362
371
  );
363
- const r = renderModuleWithContent(PARENT_MOD_ID, PARENT_CI_ID, resolver);
364
- expect(r.html).toBe("<div><span>alpha</span><span>beta</span></div>");
372
+ const r = renderModuleWithContent(PARENT_MOD_ID, PARENT_CI_ID, resolver, undefined, lists);
373
+ expect(r.html).toBe("<div>alpha;beta;</div>");
365
374
  });
366
375
 
367
- it("link-list iterates {{label}} + {{href}} per element", () => {
376
+ it("renders a list entry's embedded module through the recursion (depth + cycle guards apply)", () => {
368
377
  const resolver = buildResolver(
369
378
  [
370
379
  {
371
380
  moduleId: PARENT_MOD_ID,
372
- slug: "primary-nav",
373
- html: '<nav>{{#menu}}<a href="{{href}}">{{label}}</a>{{/menu}}</nav>',
381
+ slug: "mega",
382
+ html: "<nav>{{#mega}}{{#columns}}{{#is_promo}}<div>{{>teaser}}</div>{{/is_promo}}{{/columns}}{{/mega}}</nav>",
374
383
  css: "",
375
384
  js: "",
376
- fields: [{ name: "menu", kind: "link-list" }],
385
+ fields: [
386
+ {
387
+ name: "mega",
388
+ kind: "list",
389
+ listType: "mega-menu",
390
+ bind: "shared",
391
+ list: `caelo:list/${LIST.mega}`,
392
+ },
393
+ ],
377
394
  },
378
- ],
379
- [
380
395
  {
381
- id: PARENT_CI_ID,
382
- moduleId: PARENT_MOD_ID,
383
- values: {
384
- menu: [
385
- { label: "Home", href: "/" },
386
- { label: "About", href: "/about" },
387
- { label: "Contact", href: "/contact" },
388
- ],
389
- },
390
- deletedAt: null,
396
+ moduleId: MODULE.teaser,
397
+ slug: "teaser",
398
+ html: "<p>{{title}}</p>",
399
+ css: "",
400
+ js: "",
401
+ fields: [{ name: "title", kind: "text" }],
391
402
  },
392
403
  ],
404
+ [
405
+ { id: PARENT_CI_ID, moduleId: PARENT_MOD_ID, values: {}, deletedAt: null },
406
+ { id: INSTANCE.teaser, moduleId: MODULE.teaser, values: { title: "Try" }, deletedAt: null },
407
+ ],
393
408
  );
394
- const r = renderModuleWithContent(PARENT_MOD_ID, PARENT_CI_ID, resolver);
409
+ const r = renderModuleWithContent(PARENT_MOD_ID, PARENT_CI_ID, resolver, undefined, lists);
395
410
  expect(r.html).toBe(
396
- '<nav><a href="/">Home</a><a href="/about">About</a><a href="/contact">Contact</a></nav>',
411
+ `<nav><div data-caelo-list-item="${LIST.mega}/${ITEM.promo}"><p ${tag(MODULE.teaser)}>Try</p></div></nav>`,
397
412
  );
413
+ expect(r.touchedModuleIds.has(MODULE.teaser)).toBe(true);
398
414
  expect(r.missingSlots).toEqual([]);
399
415
  });
400
416
 
401
- it("link-list malformed element gets a per-element marker, others render", () => {
417
+ it("a broken embedded module is attributed to the list entry", () => {
402
418
  const resolver = buildResolver(
403
419
  [
404
420
  {
405
421
  moduleId: PARENT_MOD_ID,
406
- slug: "primary-nav",
407
- html: '<nav>{{#menu}}<a href="{{href}}">{{label}}</a>{{/menu}}</nav>',
422
+ slug: "mega",
423
+ html: "{{#mega}}{{#columns}}{{#is_promo}}{{>teaser}}{{/is_promo}}{{/columns}}{{/mega}}",
408
424
  css: "",
409
425
  js: "",
410
- fields: [{ name: "menu", kind: "link-list" }],
426
+ fields: [
427
+ {
428
+ name: "mega",
429
+ kind: "list",
430
+ listType: "mega-menu",
431
+ bind: "shared",
432
+ list: `caelo:list/${LIST.mega}`,
433
+ },
434
+ ],
411
435
  },
412
436
  ],
413
- [
414
- {
415
- id: PARENT_CI_ID,
416
- moduleId: PARENT_MOD_ID,
417
- values: {
418
- menu: [
419
- { label: "Home", href: "/" },
420
- { label: "missing-href" }, // malformed
421
- { label: "About", href: "/about" },
437
+ [{ id: PARENT_CI_ID, moduleId: PARENT_MOD_ID, values: {}, deletedAt: null }],
438
+ );
439
+ const r = renderModuleWithContent(PARENT_MOD_ID, PARENT_CI_ID, resolver, undefined, lists);
440
+ expect(r.failures).toEqual([
441
+ { field: `mega[${ITEM.promo}].teaser`, reason: `module-missing:${MODULE.teaser}` },
442
+ ]);
443
+ });
444
+
445
+ it("collectNestedRefs finds module fields inside local list entries", () => {
446
+ expect(
447
+ collectNestedRefs({
448
+ mega: [
449
+ {
450
+ id: T(1),
451
+ node: "entry",
452
+ values: {},
453
+ children: [
454
+ {
455
+ id: T(2),
456
+ node: "promo",
457
+ values: { teaser: { moduleId: MODULE.teaser, contentInstanceId: INSTANCE.teaser } },
458
+ },
422
459
  ],
423
460
  },
424
- deletedAt: null,
461
+ ],
462
+ }),
463
+ ).toEqual([{ moduleId: MODULE.teaser, contentInstanceId: INSTANCE.teaser }]);
464
+ });
465
+
466
+ it("rewriteNestedRefs remaps every ref collectNestedRefs finds and copies the rest", () => {
467
+ const COPY = "00000000-0000-4000-8000-00000000c0c0";
468
+ const values = {
469
+ heading: "Keep me",
470
+ cta: { moduleId: MODULE.teaser, contentInstanceId: INSTANCE.teaser },
471
+ cards: [{ moduleId: MODULE.teaser, contentInstanceId: INSTANCE.teaser }],
472
+ mega: [
473
+ {
474
+ id: T(1),
475
+ node: "entry",
476
+ values: { label: "Entry" },
477
+ children: [
478
+ {
479
+ id: T(2),
480
+ node: "promo",
481
+ values: { teaser: { moduleId: MODULE.teaser, contentInstanceId: INSTANCE.teaser } },
482
+ },
483
+ ],
425
484
  },
426
485
  ],
486
+ };
487
+ const rewritten = rewriteNestedRefs(values, (id) => {
488
+ if (id !== INSTANCE.teaser) throw new Error(`no copy for ${id}`);
489
+ return COPY;
490
+ });
491
+ expect(collectNestedRefs(rewritten)).toEqual([
492
+ { moduleId: MODULE.teaser, contentInstanceId: COPY },
493
+ { moduleId: MODULE.teaser, contentInstanceId: COPY },
494
+ { moduleId: MODULE.teaser, contentInstanceId: COPY },
495
+ ]);
496
+ expect(rewritten.heading).toBe("Keep me");
497
+ expect((rewritten.mega as { values: unknown }[])[0]?.values).toEqual({ label: "Entry" });
498
+ // The source is not mutated.
499
+ expect(collectNestedRefs(values).every((r) => r.contentInstanceId === INSTANCE.teaser)).toBe(
500
+ true,
427
501
  );
428
- const r = renderModuleWithContent(PARENT_MOD_ID, PARENT_CI_ID, resolver);
429
- expect(r.html).toContain('<a href="/">Home</a>');
430
- expect(r.html).toContain('<a href="/about">About</a>');
431
- expect(r.html).toContain("link-list-malformed");
432
- expect(r.missingSlots.some((s) => s.startsWith("link-list-malformed"))).toBe(true);
433
502
  });
434
503
  });
435
504
 
@@ -668,7 +737,7 @@ describe("renderModuleWithContent — engine-routing parity (#71)", () => {
668
737
  });
669
738
 
670
739
  it("kind-mismatch on {{>name}} against a list field emits the exact legacy string", () => {
671
- const expected = "kind-mismatch:items expected=module actual=text-list";
740
+ const expected = "kind-mismatch:items expected=module actual=list";
672
741
  const resolver = buildResolver(
673
742
  [
674
743
  {
@@ -677,7 +746,7 @@ describe("renderModuleWithContent — engine-routing parity (#71)", () => {
677
746
  html: "<aside>{{>items}}</aside>",
678
747
  css: "",
679
748
  js: "",
680
- fields: [{ name: "items", kind: "text-list" }],
749
+ fields: [{ name: "items", kind: "list", listType: "texts" }],
681
750
  },
682
751
  ],
683
752
  [{ id: PARENT_CI_ID, moduleId: PARENT_MOD_ID, values: {}, deletedAt: null }],
@@ -713,32 +782,36 @@ describe("renderModuleWithContent — engine-routing parity (#71)", () => {
713
782
  expect(r.missingSlots).toContain("module-ref-malformed:hero");
714
783
  });
715
784
 
716
- it("text-list-malformed per element preserves the exact legacy marker", () => {
785
+ it("list-malformed names the entry path and keeps the section raw", () => {
717
786
  const resolver = buildResolver(
718
787
  [
719
788
  {
720
789
  moduleId: PARENT_MOD_ID,
721
- slug: "text-list-malformed",
722
- html: "<ul>{{#items}}<li>{{.}}</li>{{/items}}</ul>",
790
+ slug: "list-malformed",
791
+ html: "<ul>{{#items}}<li>{{text}}</li>{{/items}}</ul>",
723
792
  css: "",
724
793
  js: "",
725
- fields: [{ name: "items", kind: "text-list" }],
794
+ fields: [{ name: "items", kind: "list", listType: "texts" }],
726
795
  },
727
796
  ],
728
797
  [
729
798
  {
730
799
  id: PARENT_CI_ID,
731
800
  moduleId: PARENT_MOD_ID,
732
- values: { items: ["a", { bogus: true }, "c"] },
801
+ values: {
802
+ items: [
803
+ { id: "00000000-0000-4000-9000-000000000001", node: "text", values: { text: "a" } },
804
+ { bogus: true },
805
+ ],
806
+ },
733
807
  deletedAt: null,
734
808
  },
735
809
  ],
736
810
  );
737
- const r = renderModuleWithContent(PARENT_MOD_ID, PARENT_CI_ID, resolver);
738
- expect(r.html).toContain("<li>a</li>");
739
- expect(r.html).toContain("<li>c</li>");
740
- expect(r.html).toContain("<!-- caelo:missing reason=text-list-malformed items[1] -->");
741
- expect(r.missingSlots).toContain("text-list-malformed:items[1]");
811
+ const lists = createListRenderContext(fixtureData()).forPage(null);
812
+ const r = renderModuleWithContent(PARENT_MOD_ID, PARENT_CI_ID, resolver, undefined, lists);
813
+ expect(r.html).toBe("<ul>{{#items}}<li>{{text}}</li>{{/items}}</ul>");
814
+ expect(r.missingSlots).toContain("list-malformed:items[items[1]]");
742
815
  });
743
816
 
744
817
  it("module-list-malformed per element preserves the exact legacy marker", () => {