@bendyline/squisq 1.5.2 → 2.0.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 (263) hide show
  1. package/README.md +63 -10
  2. package/dist/{Doc-BBVGq1_9.d.ts → Doc-ylqWKDc_.d.ts} +248 -37
  3. package/dist/{ImageEditDoc-FuyGtt6o.d.ts → ImageEditDoc-D828IXJ1.d.ts} +1 -1
  4. package/dist/annotationCoercion-Dr_DY9Hx.d.ts +86 -0
  5. package/dist/{chunk-VWUFZ6ZG.js → chunk-3E5F2XMR.js} +2 -29
  6. package/dist/chunk-3E5F2XMR.js.map +1 -0
  7. package/dist/chunk-4X3JQXNM.js +35 -0
  8. package/dist/chunk-4X3JQXNM.js.map +1 -0
  9. package/dist/{chunk-P3K7NLHF.js → chunk-5DKBFXPY.js} +31 -121
  10. package/dist/chunk-5DKBFXPY.js.map +1 -0
  11. package/dist/{chunk-DLXZMS5K.js → chunk-5J4HKRJF.js} +51 -3
  12. package/dist/chunk-5J4HKRJF.js.map +1 -0
  13. package/dist/chunk-5UYI3RJB.js +2503 -0
  14. package/dist/chunk-5UYI3RJB.js.map +1 -0
  15. package/dist/{chunk-BEQODJWV.js → chunk-66QO7XFB.js} +11 -291
  16. package/dist/chunk-66QO7XFB.js.map +1 -0
  17. package/dist/{chunk-KKNUBQ6Y.js → chunk-6PJRB2CF.js} +156 -91
  18. package/dist/chunk-6PJRB2CF.js.map +1 -0
  19. package/dist/{chunk-HWVFJAAH.js → chunk-AMIR72JP.js} +28 -10
  20. package/dist/chunk-AMIR72JP.js.map +1 -0
  21. package/dist/chunk-FVJUUTEM.js +74 -0
  22. package/dist/chunk-FVJUUTEM.js.map +1 -0
  23. package/dist/{chunk-5TYS5V3G.js → chunk-GF3BVNLU.js} +4127 -3538
  24. package/dist/chunk-GF3BVNLU.js.map +1 -0
  25. package/dist/{chunk-Q3ROPT5H.js → chunk-GZY66SDN.js} +1 -1
  26. package/dist/{chunk-Q3ROPT5H.js.map → chunk-GZY66SDN.js.map} +1 -1
  27. package/dist/chunk-JOQYPRDZ.js +127 -0
  28. package/dist/chunk-JOQYPRDZ.js.map +1 -0
  29. package/dist/{chunk-A4O7GIWE.js → chunk-LYNJEOAC.js} +10 -73
  30. package/dist/chunk-LYNJEOAC.js.map +1 -0
  31. package/dist/{chunk-LDQ2HJIX.js → chunk-PXLVXTLW.js} +252 -54
  32. package/dist/chunk-PXLVXTLW.js.map +1 -0
  33. package/dist/{chunk-TQWLI6S2.js → chunk-Q7LROV2H.js} +4 -2
  34. package/dist/{chunk-TQWLI6S2.js.map → chunk-Q7LROV2H.js.map} +1 -1
  35. package/dist/{chunk-T5UK6YOB.js → chunk-QTSJOY54.js} +60 -17
  36. package/dist/chunk-QTSJOY54.js.map +1 -0
  37. package/dist/chunk-QVANWDPH.js +2358 -0
  38. package/dist/chunk-QVANWDPH.js.map +1 -0
  39. package/dist/{chunk-GPLTCSXO.js → chunk-R2GC6RLD.js} +7 -7
  40. package/dist/chunk-R2GC6RLD.js.map +1 -0
  41. package/dist/chunk-R2YZTCLO.js +292 -0
  42. package/dist/chunk-R2YZTCLO.js.map +1 -0
  43. package/dist/chunk-S7A5D7ZJ.js +174 -0
  44. package/dist/chunk-S7A5D7ZJ.js.map +1 -0
  45. package/dist/chunk-T3TI7GSZ.js +884 -0
  46. package/dist/chunk-T3TI7GSZ.js.map +1 -0
  47. package/dist/{chunk-LTN6I7QW.js → chunk-TUWMK62B.js} +55 -4
  48. package/dist/chunk-TUWMK62B.js.map +1 -0
  49. package/dist/{chunk-IMSCRLLV.js → chunk-VR7UPJ7C.js} +35 -25
  50. package/dist/chunk-VR7UPJ7C.js.map +1 -0
  51. package/dist/doc/index.d.ts +2334 -5
  52. package/dist/doc/index.js +127 -33
  53. package/dist/generate/index.d.ts +7 -2
  54. package/dist/generate/index.js +1 -1
  55. package/dist/imageEdit/index.d.ts +9 -6
  56. package/dist/imageEdit/index.js +3 -3
  57. package/dist/index.d.ts +12 -10
  58. package/dist/index.js +246 -80
  59. package/dist/jsonForm/index.d.ts +7 -3
  60. package/dist/jsonForm/index.js +9 -4
  61. package/dist/markdown/index.d.ts +6 -70
  62. package/dist/markdown/index.js +13 -9
  63. package/dist/narration/index.d.ts +618 -0
  64. package/dist/narration/index.js +82 -0
  65. package/dist/recommend/index.d.ts +15 -1
  66. package/dist/recommend/index.js +2 -2
  67. package/dist/schemas/index.d.ts +28 -6
  68. package/dist/schemas/index.js +15 -13
  69. package/dist/spatial/index.d.ts +0 -1
  70. package/dist/spatial/index.js +1 -1
  71. package/dist/storage/index.js +1 -1
  72. package/dist/{themeLibrary-CehcJhzz.d.ts → themeLibrary-RjVgLpUi.d.ts} +10 -11
  73. package/dist/timing/index.js +5 -3
  74. package/dist/transform/index.d.ts +35 -24
  75. package/dist/transform/index.js +9 -10
  76. package/dist/{types-DlAZ7MW4.d.ts → types-BwMPl2CB.d.ts} +1 -1
  77. package/dist/{types-D98li1gV.d.ts → types-C4kiRn_k.d.ts} +3 -1
  78. package/dist/versions/index.d.ts +2 -1
  79. package/dist/versions/index.js +2 -2
  80. package/package.json +6 -6
  81. package/src/__tests__/applyNarrationTiming.test.ts +190 -0
  82. package/src/__tests__/applyRenderStyle.test.ts +3 -3
  83. package/src/__tests__/asciiDiagramBattle.test.ts +246 -0
  84. package/src/__tests__/asciiDiagramDetect.test.ts +162 -0
  85. package/src/__tests__/asciiDiagramMapping.test.ts +38 -0
  86. package/src/__tests__/asciiDiagramParse.test.ts +233 -0
  87. package/src/__tests__/asciiDiagramPipeline.test.ts +273 -0
  88. package/src/__tests__/asciiDiagramRender.test.ts +162 -0
  89. package/src/__tests__/asciiDiagramRepair.test.ts +84 -0
  90. package/src/__tests__/asciiTimelineDetect.test.ts +157 -0
  91. package/src/__tests__/asciiTimelineParse.test.ts +205 -0
  92. package/src/__tests__/asciiTimelinePipeline.test.ts +349 -0
  93. package/src/__tests__/asciiTimelineRender.test.ts +376 -0
  94. package/src/__tests__/attrTokens.test.ts +5 -0
  95. package/src/__tests__/contentContainer.test.ts +43 -0
  96. package/src/__tests__/customTemplatesFrontmatter.test.ts +0 -15
  97. package/src/__tests__/dataTable.test.ts +25 -15
  98. package/src/__tests__/diagramBlock.test.ts +3 -3
  99. package/src/__tests__/diagramText.test.ts +26 -0
  100. package/src/__tests__/docToMarkdownMedia.test.ts +83 -0
  101. package/src/__tests__/drawingBlock.test.ts +3 -3
  102. package/src/__tests__/fixtures/asciiDiagramBattle.ts +512 -0
  103. package/src/__tests__/fixtures/asciiDiagrams.ts +287 -0
  104. package/src/__tests__/fixtures/asciiTimelines.ts +35 -0
  105. package/src/__tests__/fixtures/treeviewFixtures.ts +243 -0
  106. package/src/__tests__/getLayers.test.ts +32 -18
  107. package/src/__tests__/getLayersFallback.test.ts +8 -8
  108. package/src/__tests__/imageEditVersions.test.ts +2 -0
  109. package/src/__tests__/jsonForm.pointer.test.ts +47 -1
  110. package/src/__tests__/layoutBlock.test.ts +3 -3
  111. package/src/__tests__/majorApiTypes.test.ts +18 -0
  112. package/src/__tests__/markdown.test.ts +41 -0
  113. package/src/__tests__/matchFontFamily.test.ts +114 -0
  114. package/src/__tests__/materializeBlockLayers.test.ts +135 -0
  115. package/src/__tests__/materializeTestUtils.ts +17 -0
  116. package/src/__tests__/narrationAlign.test.ts +150 -0
  117. package/src/__tests__/narrationFeatures.test.ts +71 -0
  118. package/src/__tests__/narrationNuclei.test.ts +45 -0
  119. package/src/__tests__/narrationPacing.test.ts +189 -0
  120. package/src/__tests__/narrationScript.test.ts +149 -0
  121. package/src/__tests__/narrationSessionRepro.test.ts +62 -0
  122. package/src/__tests__/narrationSidecar.test.ts +88 -0
  123. package/src/__tests__/narrationSyllables.test.ts +65 -0
  124. package/src/__tests__/narrationTestSignals.ts +167 -0
  125. package/src/__tests__/narrationVad.test.ts +66 -0
  126. package/src/__tests__/resolveDocTheme.test.ts +16 -2
  127. package/src/__tests__/rootExports.test.ts +18 -0
  128. package/src/__tests__/scopedContentContainer.test.ts +20 -0
  129. package/src/__tests__/shapeGeometry.test.ts +12 -0
  130. package/src/__tests__/templateAnnotationParse.test.ts +10 -0
  131. package/src/__tests__/templateInputs.test.ts +69 -0
  132. package/src/__tests__/templateMetadata.test.ts +2 -1
  133. package/src/__tests__/templates.test.ts +143 -17
  134. package/src/__tests__/themeCompile.test.ts +49 -22
  135. package/src/__tests__/transformProvenance.test.ts +160 -0
  136. package/src/__tests__/transformV2.test.ts +40 -14
  137. package/src/__tests__/treeviewBattle.test.ts +76 -0
  138. package/src/__tests__/treeviewDetect.test.ts +168 -0
  139. package/src/__tests__/treeviewParse.test.ts +118 -0
  140. package/src/__tests__/treeviewPipeline.test.ts +183 -0
  141. package/src/__tests__/treeviewRender.test.ts +91 -0
  142. package/src/__tests__/versions.test.ts +44 -0
  143. package/src/doc/applyNarrationTiming.ts +244 -0
  144. package/src/doc/asciiDiagram/chars.ts +210 -0
  145. package/src/doc/asciiDiagram/detect.ts +175 -0
  146. package/src/doc/asciiDiagram/index.ts +38 -0
  147. package/src/doc/asciiDiagram/mapping.ts +225 -0
  148. package/src/doc/asciiDiagram/parse.ts +1095 -0
  149. package/src/doc/asciiDiagram/render.ts +963 -0
  150. package/src/doc/asciiDiagram/repair.ts +400 -0
  151. package/src/doc/asciiDiagram/types.ts +65 -0
  152. package/src/doc/asciiTimeline/detect.ts +138 -0
  153. package/src/doc/asciiTimeline/index.ts +26 -0
  154. package/src/doc/asciiTimeline/mapping.ts +43 -0
  155. package/src/doc/asciiTimeline/parse.ts +870 -0
  156. package/src/doc/asciiTimeline/render.ts +427 -0
  157. package/src/doc/asciiTimeline/types.ts +73 -0
  158. package/src/doc/asciiTimeline/wrappedFlow.ts +427 -0
  159. package/src/doc/audioMapping.ts +60 -46
  160. package/src/doc/customTemplatesFrontmatter.ts +0 -41
  161. package/src/doc/docToMarkdown.ts +70 -2
  162. package/src/doc/index.ts +76 -4
  163. package/src/doc/markdownToDoc.ts +135 -1
  164. package/src/doc/materializeBlockLayers.ts +304 -0
  165. package/src/doc/mediaAnnotations.ts +20 -0
  166. package/src/doc/resolveDocTheme.ts +8 -7
  167. package/src/doc/templateInputs.ts +185 -2
  168. package/src/doc/templates/__tests__/customTemplateRegistry.test.ts +22 -8
  169. package/src/doc/templates/diagramBlock.ts +237 -38
  170. package/src/doc/templates/fallbackBlock.ts +1 -1
  171. package/src/doc/templates/index.ts +134 -279
  172. package/src/doc/templates/inputDescriptors.ts +20 -1
  173. package/src/doc/templates/metadata.ts +10 -0
  174. package/src/doc/templates/persistentLayers.ts +12 -126
  175. package/src/doc/templates/registry.ts +88 -0
  176. package/src/doc/templates/templateNames.ts +22 -0
  177. package/src/doc/templates/timelineBlock.ts +613 -0
  178. package/src/doc/templates/treeBlock.ts +131 -0
  179. package/src/doc/treeview/chars.ts +72 -0
  180. package/src/doc/treeview/detect.ts +145 -0
  181. package/src/doc/treeview/index.ts +30 -0
  182. package/src/doc/treeview/mapping.ts +95 -0
  183. package/src/doc/treeview/parse.ts +201 -0
  184. package/src/doc/treeview/render.ts +41 -0
  185. package/src/doc/treeview/types.ts +43 -0
  186. package/src/doc/utils/applyRenderStyle.ts +2 -9
  187. package/src/doc/utils/diagramText.ts +78 -0
  188. package/src/doc/utils/shapeGeometry.ts +67 -5
  189. package/src/doc/validate.ts +1 -1
  190. package/src/generate/templateMapper.ts +7 -0
  191. package/src/imageEdit/export.ts +6 -5
  192. package/src/imageEdit/versionPaths.ts +13 -26
  193. package/src/imageEdit/versions.ts +21 -100
  194. package/src/index.ts +1 -0
  195. package/src/internal/immutable.ts +58 -0
  196. package/src/jsonForm/index.ts +9 -1
  197. package/src/jsonForm/pointer.ts +68 -23
  198. package/src/markdown/attrTokens.ts +7 -5
  199. package/src/markdown/index.ts +1 -0
  200. package/src/markdown/stringify.ts +4 -9
  201. package/src/markdown/utils.ts +36 -14
  202. package/src/narration/align.ts +514 -0
  203. package/src/narration/features.ts +231 -0
  204. package/src/narration/index.ts +86 -0
  205. package/src/narration/nuclei.ts +118 -0
  206. package/src/narration/pacing.ts +211 -0
  207. package/src/narration/script.ts +239 -0
  208. package/src/narration/session.ts +100 -0
  209. package/src/narration/sidecar.ts +187 -0
  210. package/src/narration/syllables.ts +85 -0
  211. package/src/narration/trace.ts +50 -0
  212. package/src/narration/types.ts +299 -0
  213. package/src/narration/vad.ts +131 -0
  214. package/src/recommend/numberHighlight.ts +25 -0
  215. package/src/recommend/templates.ts +108 -12
  216. package/src/schemas/BlockTemplates.ts +156 -7
  217. package/src/schemas/Doc.ts +72 -9
  218. package/src/schemas/Media.ts +12 -2
  219. package/src/schemas/Theme.ts +45 -25
  220. package/src/schemas/Viewport.ts +2 -2
  221. package/src/schemas/fontStacks.ts +79 -0
  222. package/src/schemas/themeCompile.ts +15 -0
  223. package/src/schemas/themeConstants.ts +2 -0
  224. package/src/schemas/themeLibrary.ts +25 -19
  225. package/src/schemas/themeValidator.ts +1 -1
  226. package/src/spatial/Geohash.ts +0 -3
  227. package/src/storage/ContentContainer.ts +17 -9
  228. package/src/storage/ScopedContentContainer.ts +45 -15
  229. package/src/transform/applyTransform.ts +5 -5
  230. package/src/transform/index.ts +3 -1
  231. package/src/transform/registry.ts +169 -49
  232. package/src/transform/templateSelector.ts +21 -2
  233. package/src/transform/timingAllocator.ts +152 -21
  234. package/src/transform/types.ts +14 -1
  235. package/src/versions/gitignore.ts +34 -0
  236. package/src/versions/index.ts +2 -0
  237. package/src/versions/operations.ts +18 -104
  238. package/src/versions/paths.ts +13 -28
  239. package/src/versions/snapshotStore.ts +202 -0
  240. package/src/versions/types.ts +3 -1
  241. package/dist/chunk-5TYS5V3G.js.map +0 -1
  242. package/dist/chunk-A4O7GIWE.js.map +0 -1
  243. package/dist/chunk-BEQODJWV.js.map +0 -1
  244. package/dist/chunk-DLXZMS5K.js.map +0 -1
  245. package/dist/chunk-GPLTCSXO.js.map +0 -1
  246. package/dist/chunk-HWVFJAAH.js.map +0 -1
  247. package/dist/chunk-IMSCRLLV.js.map +0 -1
  248. package/dist/chunk-JELIRVDP.js +0 -24
  249. package/dist/chunk-JELIRVDP.js.map +0 -1
  250. package/dist/chunk-KKNUBQ6Y.js.map +0 -1
  251. package/dist/chunk-LDQ2HJIX.js.map +0 -1
  252. package/dist/chunk-LTN6I7QW.js.map +0 -1
  253. package/dist/chunk-P3K7NLHF.js.map +0 -1
  254. package/dist/chunk-T5UK6YOB.js.map +0 -1
  255. package/dist/chunk-UY7KGQ5R.js +0 -192
  256. package/dist/chunk-UY7KGQ5R.js.map +0 -1
  257. package/dist/chunk-VLJJHDAC.js +0 -218
  258. package/dist/chunk-VLJJHDAC.js.map +0 -1
  259. package/dist/chunk-VWUFZ6ZG.js.map +0 -1
  260. package/dist/story/index.d.ts +0 -1801
  261. package/dist/story/index.js +0 -279
  262. package/src/doc/getLayers.ts +0 -195
  263. /package/dist/{story → narration}/index.js.map +0 -0
@@ -11,56 +11,33 @@
11
11
  * This is shared code used by both site and efb-app doc renderers.
12
12
  */
13
13
 
14
- import type { Block, Layer } from '../../schemas/Doc.js';
14
+ import type { Block } from '../../schemas/Doc.js';
15
15
  import type {
16
16
  TemplateBlock,
17
- TemplateContext,
18
- TemplateRegistry,
19
17
  DocBlock,
20
18
  PersistentLayerConfig,
21
19
  } from '../../schemas/BlockTemplates.js';
22
20
  import type { Theme } from '../../schemas/Theme.js';
23
21
  import type { CustomTemplateDefinition } from '../../schemas/CustomTemplates.js';
24
- import { isTemplateBlock, createTemplateContext } from '../../schemas/BlockTemplates.js';
22
+ import { isTemplateBlock } from '../../schemas/BlockTemplates.js';
25
23
  import { DEFAULT_THEME as defaultTheme } from '../../schemas/themeLibrary.js';
26
- import { expandPersistentLayers, wrapWithPersistentLayers } from './persistentLayers.js';
27
- import { applyRenderStyleToLayers } from '../utils/applyRenderStyle.js';
24
+ import { expandPersistentLayers } from './persistentLayers.js';
28
25
  import { resolveBlockTransition } from '../../schemas/Transitions.js';
29
- import { makeCustomTemplateFn } from './customTemplate.js';
30
- // Coerces inline `{[…]}` string params into typed template input. inputDescriptors
31
- // imports `resolveTemplateName` from this module in return; the cycle is benign
32
- // because every cross-reference lives inside a function body, not module init.
33
- import { coerceTemplateParams } from './inputDescriptors.js';
26
+ import { resolveTemplateName } from './templateNames.js';
27
+ import { cloneData } from '../../internal/immutable.js';
34
28
  import type { ViewportConfig } from '../../schemas/Viewport.js';
35
29
  import { VIEWPORT_PRESETS } from '../../schemas/Viewport.js';
30
+ import { buildRegistry, templateRegistry, type RuntimeTemplateRegistry } from './registry.js';
31
+ import {
32
+ materializeBlockLayersWithRuntime,
33
+ type ExpandedPersistentLayerSet,
34
+ type LayerMaterializationDiagnostic,
35
+ type LayerMaterializationFailureMode,
36
+ } from '../materializeBlockLayers.js';
36
37
 
37
38
  export { TEMPLATE_METADATA } from './metadata.js';
38
39
  export type { TemplateMetadata } from './metadata.js';
39
40
 
40
- // Import all template functions
41
- import { titleBlock } from './titleBlock.js';
42
- import { sectionHeader } from './sectionHeader.js';
43
- import { statHighlight } from './statHighlight.js';
44
- import { quoteBlock } from './quoteBlock.js';
45
- import { factCard } from './factCard.js';
46
- import { twoColumn } from './twoColumn.js';
47
- import { dateEvent } from './dateEvent.js';
48
- import { imageWithCaption } from './imageWithCaption.js';
49
- import { leftFeature, rightFeature } from './featureBlock.js';
50
- import { mapBlock } from './mapBlock.js';
51
- import { fullBleedQuote } from './fullBleedQuote.js';
52
- import { listBlock } from './listBlock.js';
53
- import { photoGrid } from './photoGrid.js';
54
- import { definitionCard } from './definitionCard.js';
55
- import { comparisonBar } from './comparisonBar.js';
56
- import { pullQuote } from './pullQuote.js';
57
- import { videoWithCaption } from './videoWithCaption.js';
58
- import { videoPullQuote } from './videoPullQuote.js';
59
- import { dataTable } from './dataTable.js';
60
- import { diagramBlock } from './diagramBlock.js';
61
- import { drawingBlock } from './drawingBlock.js';
62
- import { layoutBlock } from './layoutBlock.js';
63
-
64
41
  /**
65
42
  * Registry mapping template ids (the strings that appear in
66
43
  * `{[name]}` markdown annotations) to their implementation functions.
@@ -68,61 +45,29 @@ import { layoutBlock } from './layoutBlock.js';
68
45
  * Some implementation functions keep historical "Block" suffixes
69
46
  * (`titleBlock`, `quoteBlock`, `mapBlock`, `listBlock`) for source
70
47
  * readability — the registry surfaces them under the cleaner short
71
- * ids (`title`, `quote`, `map`, `list`). {@link TEMPLATE_ALIASES} keeps
72
- * the legacy long ids resolvable for documents written before the
73
- * rename.
48
+ * ids (`title`, `quote`, `map`, `list`). Internal aliases keep legacy
49
+ * document names readable without expanding the public API surface.
74
50
  *
75
51
  * Note: coverBlock is not in the registry as it's used directly for
76
52
  * start blocks, not as a regular template in the block sequence.
77
53
  */
78
- export const templateRegistry: TemplateRegistry = {
79
- title: titleBlock,
80
- sectionHeader,
81
- statHighlight,
82
- quote: quoteBlock,
83
- factCard,
84
- twoColumn,
85
- dateEvent,
86
- imageWithCaption,
87
- leftFeature,
88
- rightFeature,
89
- map: mapBlock,
90
- fullBleedQuote,
91
- list: listBlock,
92
- photoGrid,
93
- definitionCard,
94
- comparisonBar,
95
- pullQuote,
96
- videoWithCaption,
97
- videoPullQuote,
98
- dataTable,
99
- diagram: diagramBlock,
100
- layout: layoutBlock,
101
- drawing: drawingBlock,
102
- };
54
+ export { buildRegistry, templateRegistry } from './registry.js';
55
+ export type { RuntimeTemplateRegistry } from './registry.js';
103
56
 
104
- /**
105
- * Back-compat: maps legacy template ids (kept around for documents
106
- * authored before the "Block" suffix was dropped) to the canonical
107
- * short ids. Resolved by {@link resolveTemplateName} so both the
108
- * registry lookup and `hasTemplate()` accept either form.
109
- */
110
- export const TEMPLATE_ALIASES: Readonly<Record<string, string>> = {
111
- titleBlock: 'title',
112
- quoteBlock: 'quote',
113
- mapBlock: 'map',
114
- listBlock: 'list',
115
- diagramBlock: 'diagram',
116
- diagramNode: 'diagram',
117
- };
57
+ export { materializeBlockLayers } from '../materializeBlockLayers.js';
58
+ export type {
59
+ BlockLayerMaterialization,
60
+ LayerMaterializationDiagnostic,
61
+ LayerMaterializationFailureMode,
62
+ LayerMaterializationSource,
63
+ MaterializeBlockLayersOptions,
64
+ } from '../materializeBlockLayers.js';
118
65
 
119
66
  /**
120
- * Resolve a template id through {@link TEMPLATE_ALIASES}. Returns the
121
- * input unchanged when no alias is registered.
67
+ * Resolve a template id through the internal compatibility table. Returns
68
+ * the input unchanged when no alias is registered.
122
69
  */
123
- export function resolveTemplateName(name: string): string {
124
- return TEMPLATE_ALIASES[name] ?? name;
125
- }
70
+ export { resolveTemplateName } from './templateNames.js';
126
71
 
127
72
  /**
128
73
  * Container templates render their parent block by consuming the block's
@@ -132,114 +77,10 @@ export function resolveTemplateName(name: string): string {
132
77
  * render paths use {@link isContainerTemplate} to skip descending into
133
78
  * them (see `flattenRenderableBlocks`).
134
79
  */
135
- export const CONTAINER_TEMPLATES: ReadonlySet<string> = new Set(['diagram', 'drawing', 'layout']);
80
+ export { CONTAINER_TEMPLATES } from './templateNames.js';
136
81
 
137
82
  /** True when `name` (or its alias) is a children-consuming container template. */
138
- export function isContainerTemplate(name: string | undefined): boolean {
139
- if (!name) return false;
140
- return CONTAINER_TEMPLATES.has(resolveTemplateName(name));
141
- }
142
-
143
- /**
144
- * Merge user-defined custom templates onto the built-in registry.
145
- * Built-in names take precedence on collision — a user can't shadow
146
- * `title` or `diagram` from a custom definition. Returns a frozen view
147
- * so callers don't accidentally mutate the global registry.
148
- */
149
- export type RuntimeTemplateRegistry = Record<
150
- string,
151
- (input: TemplateBlock, ctx: TemplateContext) => Layer[]
152
- >;
153
-
154
- export function buildRegistry(
155
- custom?: readonly CustomTemplateDefinition[],
156
- ): RuntimeTemplateRegistry {
157
- // The exported `templateRegistry` is a discriminated-union mapped type;
158
- // we cast through `unknown` because each entry's `input` type is the
159
- // template-specific variant, not the broad `TemplateBlock` union.
160
- // The runtime contract — "given any TemplateBlock that resolves to a
161
- // registered name, the function returns Layer[]" — holds because the
162
- // expansion path looks up by `template` string before calling.
163
- const merged: RuntimeTemplateRegistry = {
164
- ...(templateRegistry as unknown as RuntimeTemplateRegistry),
165
- };
166
- if (custom) {
167
- for (const def of custom) {
168
- if (merged[def.name]) continue; // built-in wins
169
- merged[def.name] = makeCustomTemplateFn(def) as unknown as RuntimeTemplateRegistry[string];
170
- }
171
- }
172
- return merged;
173
- }
174
-
175
- /**
176
- * Expand a template block into a full Block with layers.
177
- *
178
- * `registry` lets callers swap in a registry that includes user-defined
179
- * custom templates. Defaults to the built-in registry when omitted.
180
- */
181
- export function expandTemplateBlock(
182
- templateBlock: TemplateBlock,
183
- context: TemplateContext,
184
- registry: RuntimeTemplateRegistry = templateRegistry as unknown as RuntimeTemplateRegistry,
185
- ): Block {
186
- const resolved = resolveTemplateName(templateBlock.template);
187
- const templateFn = registry[resolved];
188
-
189
- if (!templateFn) {
190
- console.warn(`Unknown template: ${templateBlock.template}`);
191
- // Return block with no layers
192
- return {
193
- id: templateBlock.id,
194
- startTime: 0,
195
- duration: templateBlock.duration,
196
- audioSegment: templateBlock.audioSegment,
197
- transition: templateBlock.transition,
198
- template: templateBlock.template, // Preserve for debugging
199
- };
200
- }
201
-
202
- // Effective template input: the block's own fields, then structured
203
- // body data (```json data fences / derived auto-template inputs), then
204
- // `{[…]}` string overrides — the same merge order getLayers uses. Inline
205
- // overrides are coerced to their typed shape (e.g. `center="47.6,-122.3"` →
206
- // `{lat,lng}`); `templateOverrides` itself stays raw for round-tripping.
207
- const { templateData, templateOverrides } = templateBlock as Block;
208
- const input =
209
- templateData || templateOverrides
210
- ? ({
211
- ...templateBlock,
212
- ...templateData,
213
- ...coerceTemplateParams(templateBlock.template, templateOverrides ?? {}).input,
214
- } as TemplateBlock)
215
- : templateBlock;
216
-
217
- // Generate layers from template with error handling
218
- let layers: Layer[];
219
- try {
220
- layers = templateFn(input, context);
221
- if (!Array.isArray(layers)) {
222
- console.error(
223
- `Template ${templateBlock.template} did not return an array, got:`,
224
- typeof layers,
225
- );
226
- layers = [];
227
- }
228
- } catch (err: unknown) {
229
- console.error(`Error expanding template ${templateBlock.template}:`, err);
230
- layers = [];
231
- }
232
-
233
- return {
234
- id: templateBlock.id,
235
- startTime: 0, // Will be calculated later
236
- duration: templateBlock.duration,
237
- audioSegment: templateBlock.audioSegment,
238
- ...(layers.length > 0 ? { layers } : {}),
239
- transition: templateBlock.transition,
240
- template: templateBlock.template, // Preserve for debugging
241
- };
242
- }
83
+ export { isContainerTemplate } from './templateNames.js';
243
84
 
244
85
  /**
245
86
  * Audio segment timing info for aligning blocks with audio.
@@ -260,7 +101,7 @@ export interface ExpandDocBlocksOptions {
260
101
  /** Viewport configuration (defaults to 16:9 landscape) */
261
102
  viewport?: ViewportConfig;
262
103
  /** Persistent layers for visual consistency across blocks */
263
- persistentLayers?: PersistentLayerConfig;
104
+ persistentLayers?: PersistentLayerConfig | false;
264
105
  /**
265
106
  * Audio segment timing information.
266
107
  * When provided, blocks are timed relative to their audio segment's start time,
@@ -273,6 +114,14 @@ export interface ExpandDocBlocksOptions {
273
114
  * `Doc.customTemplates`. Built-in names take precedence on collision.
274
115
  */
275
116
  customTemplates?: readonly CustomTemplateDefinition[];
117
+ /** Failed templates render a visible fallback by default. */
118
+ failureMode?: LayerMaterializationFailureMode;
119
+ /** Receives structured template failures without hidden console output. */
120
+ onDiagnostic?: (
121
+ diagnostic: LayerMaterializationDiagnostic,
122
+ block: DocBlock,
123
+ blockIndex: number,
124
+ ) => void;
276
125
  }
277
126
 
278
127
  /**
@@ -287,38 +136,57 @@ export interface ExpandDocBlocksOptions {
287
136
  * @param options - Expansion options including theme, viewport, and persistent layers
288
137
  */
289
138
  /**
290
- * Finalize an expanded block in place: apply the theme's motion post-pass
291
- * to template-generated layers, wrap with persistent bottom/top layers,
292
- * and fill the theme's default transition (never on block 0, never over an
293
- * authored transition). Shared by every expansion path in this module.
139
+ * Materialize one block through the canonical layer contract, then add its
140
+ * timeline envelope and resolved transition.
294
141
  */
295
- function finalizeExpandedBlock(
296
- expandedBlock: Block,
142
+ function materializeScheduledBlock(
297
143
  sourceBlock: DocBlock,
298
144
  blockIndex: number,
145
+ totalBlocks: number,
299
146
  theme: Theme,
300
- bottomLayers: Layer[],
301
- topLayers: Layer[],
302
- ): void {
303
- // Theme motion defaults apply only to template-generated layers —
304
- // raw authored block.layers are never restyled.
305
- if (isTemplateBlock(sourceBlock) && expandedBlock.layers && expandedBlock.layers.length > 0) {
306
- expandedBlock.layers = applyRenderStyleToLayers(expandedBlock.layers, expandedBlock, theme);
147
+ viewport: ViewportConfig,
148
+ persistentLayers: PersistentLayerConfig | undefined,
149
+ expandedPersistentLayers: ExpandedPersistentLayerSet | undefined,
150
+ registry: RuntimeTemplateRegistry,
151
+ failureMode: LayerMaterializationFailureMode,
152
+ onDiagnostic?: ExpandDocBlocksOptions['onDiagnostic'],
153
+ ): Block {
154
+ const materialized = materializeBlockLayersWithRuntime(
155
+ sourceBlock,
156
+ {
157
+ theme,
158
+ viewport,
159
+ persistentLayers: persistentLayers ?? false,
160
+ blockIndex,
161
+ totalBlocks,
162
+ failureMode,
163
+ },
164
+ { registry, expandedPersistentLayers },
165
+ );
166
+ if (materialized.diagnostic) {
167
+ onDiagnostic?.(materialized.diagnostic, sourceBlock, blockIndex);
307
168
  }
308
169
 
309
- if (bottomLayers.length > 0 || topLayers.length > 0) {
310
- expandedBlock.layers = wrapWithPersistentLayers(
311
- expandedBlock.layers ?? [],
312
- sourceBlock as TemplateBlock,
313
- bottomLayers,
314
- topLayers,
315
- );
316
- }
170
+ const expandedBlock: Block = isTemplateBlock(sourceBlock)
171
+ ? {
172
+ id: sourceBlock.id,
173
+ startTime: 0,
174
+ duration: sourceBlock.duration,
175
+ audioSegment: sourceBlock.audioSegment,
176
+ ...(materialized.layers.length > 0 ? { layers: materialized.layers } : {}),
177
+ transition: sourceBlock.transition,
178
+ template: sourceBlock.template,
179
+ }
180
+ : {
181
+ ...cloneData(sourceBlock as Block),
182
+ ...(materialized.layers.length > 0 ? { layers: materialized.layers } : {}),
183
+ };
317
184
 
318
185
  const transition = resolveBlockTransition(expandedBlock, theme, blockIndex);
319
186
  if (transition !== expandedBlock.transition) {
320
187
  expandedBlock.transition = transition;
321
188
  }
189
+ return expandedBlock;
322
190
  }
323
191
 
324
192
  export function expandDocBlocks(blocks: DocBlock[], options: ExpandDocBlocksOptions = {}): Block[] {
@@ -326,49 +194,46 @@ export function expandDocBlocks(blocks: DocBlock[], options: ExpandDocBlocksOpti
326
194
 
327
195
  const theme = opts.theme ?? defaultTheme;
328
196
  const viewport = opts.viewport ?? VIEWPORT_PRESETS.landscape;
329
- const { persistentLayers, audioSegments, customTemplates } = opts;
197
+ const {
198
+ persistentLayers,
199
+ audioSegments,
200
+ customTemplates,
201
+ failureMode = 'fallback',
202
+ onDiagnostic,
203
+ } = opts;
330
204
  const totalBlocks = blocks.length;
331
- // Merge user-defined templates onto the built-in registry once per
332
- // call; passed into every `expandTemplateBlock` invocation below.
205
+ // Merge user-defined templates once, then share the immutable runtime view.
333
206
  const registry: RuntimeTemplateRegistry =
334
207
  customTemplates && customTemplates.length > 0
335
208
  ? buildRegistry(customTemplates)
336
209
  : (templateRegistry as unknown as RuntimeTemplateRegistry);
337
210
 
338
- // Pre-expand persistent layers once. Callers that pass no config inherit
339
- // the theme's atmosphere (docs with their own layers pass them in and win
340
- // wholesale see resolvePersistentLayers).
341
- const effectivePersistentLayers = persistentLayers ?? theme.persistentLayers;
342
- const bottomLayers = expandPersistentLayers(effectivePersistentLayers?.bottomLayers, theme);
343
- const topLayers = expandPersistentLayers(effectivePersistentLayers?.topLayers, theme);
211
+ // Callers that pass no config inherit the theme's atmosphere.
212
+ const effectivePersistentLayers =
213
+ persistentLayers === false ? undefined : (persistentLayers ?? theme.persistentLayers);
214
+ const expandedPersistentLayers = effectivePersistentLayers
215
+ ? {
216
+ bottomLayers: expandPersistentLayers(effectivePersistentLayers.bottomLayers, theme),
217
+ topLayers: expandPersistentLayers(effectivePersistentLayers.topLayers, theme),
218
+ }
219
+ : undefined;
344
220
 
345
221
  // If no audio segments provided, use simple cumulative timing
346
222
  if (!audioSegments || audioSegments.length === 0) {
347
223
  let currentTime = 0;
348
224
  return blocks.map((block, index) => {
349
- const context = createTemplateContext(theme, index, totalBlocks, viewport);
350
- const maybeChildren = (block as Block).children;
351
- if (maybeChildren && maybeChildren.length > 0) {
352
- context.children = maybeChildren;
353
- }
354
- // Expose the source block so custom templates' token resolver can
355
- // see the block's title / contents / children. Only set for
356
- // custom-templated blocks — built-ins ignore context.block but
357
- // exposing it changes object identity which we want to avoid for
358
- // the perf-sensitive built-in path.
359
- if (
360
- isTemplateBlock(block) &&
361
- customTemplates &&
362
- customTemplates.some((c) => c.name === block.template)
363
- ) {
364
- context.block = block as Block;
365
- }
366
- const expandedBlock = isTemplateBlock(block)
367
- ? expandTemplateBlock(block, context, registry)
368
- : (block as Block);
369
-
370
- finalizeExpandedBlock(expandedBlock, block, index, theme, bottomLayers, topLayers);
371
-
225
+ const expandedBlock = materializeScheduledBlock(
226
+ block,
227
+ index,
228
+ totalBlocks,
229
+ theme,
230
+ viewport,
231
+ effectivePersistentLayers,
232
+ expandedPersistentLayers,
233
+ registry,
234
+ failureMode,
235
+ onDiagnostic,
236
+ );
372
237
  expandedBlock.startTime = currentTime;
373
238
  currentTime += expandedBlock.duration;
374
239
  return expandedBlock;
@@ -394,23 +259,18 @@ export function expandDocBlocks(blocks: DocBlock[], options: ExpandDocBlocksOpti
394
259
  // No audio segment info - use simple sequential timing within the segment
395
260
  let offsetTime = 0;
396
261
  for (const { block, originalIndex } of segmentBlocks) {
397
- const context = createTemplateContext(theme, originalIndex, totalBlocks, viewport);
398
- const maybeChildren = (block as Block).children;
399
- if (maybeChildren && maybeChildren.length > 0) {
400
- context.children = maybeChildren;
401
- }
402
- if (
403
- isTemplateBlock(block) &&
404
- customTemplates &&
405
- customTemplates.some((c) => c.name === block.template)
406
- ) {
407
- context.block = block as Block;
408
- }
409
- const expandedBlock = isTemplateBlock(block)
410
- ? expandTemplateBlock(block, context, registry)
411
- : (block as Block);
412
-
413
- finalizeExpandedBlock(expandedBlock, block, originalIndex, theme, bottomLayers, topLayers);
262
+ const expandedBlock = materializeScheduledBlock(
263
+ block,
264
+ originalIndex,
265
+ totalBlocks,
266
+ theme,
267
+ viewport,
268
+ effectivePersistentLayers,
269
+ expandedPersistentLayers,
270
+ registry,
271
+ failureMode,
272
+ onDiagnostic,
273
+ );
414
274
 
415
275
  expandedBlock.startTime = offsetTime;
416
276
  offsetTime += expandedBlock.duration;
@@ -462,23 +322,18 @@ export function expandDocBlocks(blocks: DocBlock[], options: ExpandDocBlocksOpti
462
322
  const expandedInfos: ExpandedSlideInfo[] = [];
463
323
 
464
324
  for (const { block, originalIndex } of segmentBlocks) {
465
- const context = createTemplateContext(theme, originalIndex, totalBlocks, viewport);
466
- const maybeChildren = (block as Block).children;
467
- if (maybeChildren && maybeChildren.length > 0) {
468
- context.children = maybeChildren;
469
- }
470
- if (
471
- isTemplateBlock(block) &&
472
- customTemplates &&
473
- customTemplates.some((c) => c.name === block.template)
474
- ) {
475
- context.block = block as Block;
476
- }
477
- const expandedBlock = isTemplateBlock(block)
478
- ? expandTemplateBlock(block, context, registry)
479
- : (block as Block);
480
-
481
- finalizeExpandedBlock(expandedBlock, block, originalIndex, theme, bottomLayers, topLayers);
325
+ const expandedBlock = materializeScheduledBlock(
326
+ block,
327
+ originalIndex,
328
+ totalBlocks,
329
+ theme,
330
+ viewport,
331
+ effectivePersistentLayers,
332
+ expandedPersistentLayers,
333
+ registry,
334
+ failureMode,
335
+ onDiagnostic,
336
+ );
482
337
 
483
338
  const templateBlock = block as TemplateBlock;
484
339
  expandedInfos.push({
@@ -656,7 +511,7 @@ export function expandDocBlocks(blocks: DocBlock[], options: ExpandDocBlocksOpti
656
511
  duration: partDuration,
657
512
  audioSegment: block.audioSegment,
658
513
  layers: (block.layers ?? []).map((layer) => ({
659
- ...layer,
514
+ ...cloneData(layer),
660
515
  id: `${layer.id}-split-${p}`,
661
516
  })),
662
517
  transition: { type: 'dissolve', duration: 1.0 },
@@ -716,7 +571,6 @@ export type {
716
571
  ThemeStyle,
717
572
  RenderStyle,
718
573
  } from '../../schemas/Theme.js';
719
- export type { DocStylePreset } from './persistentLayers.js';
720
574
  // Re-export timing types (AudioSegmentTiming and ExpandDocBlocksOptions are already exported above)
721
575
  export { VIEWPORT_PRESETS, getViewport, getViewportOrientation } from '../../schemas/Viewport.js';
722
576
  export type {
@@ -728,7 +582,6 @@ export { getLayoutHints, getTwoColumnPositions } from '../../schemas/LayoutStrat
728
582
  export type { LayoutHints } from '../../schemas/LayoutStrategy.js';
729
583
  export {
730
584
  expandPersistentLayers,
731
- getDocStyleConfig,
732
585
  getPersistentLayersFromTheme,
733
586
  resolvePersistentLayers,
734
587
  wrapWithPersistentLayers,
@@ -757,6 +610,8 @@ export { videoWithCaption } from './videoWithCaption.js';
757
610
  export { videoPullQuote } from './videoPullQuote.js';
758
611
  export { dataTable } from './dataTable.js';
759
612
  export { diagramBlock } from './diagramBlock.js';
613
+ export { treeBlock } from './treeBlock.js';
614
+ export { timelineBlock } from './timelineBlock.js';
760
615
  export { computeDiagramLayout } from './diagramLayout.js';
761
616
  export type {
762
617
  DiagramLayout,
@@ -31,7 +31,7 @@
31
31
  */
32
32
 
33
33
  import { KNOWN_BLOCK_META_KEYS, parseNumber } from '../../markdown/annotationCoercion.js';
34
- import { resolveTemplateName } from './index.js';
34
+ import { resolveTemplateName } from './templateNames.js';
35
35
  import { nearestName } from '../utils/nearest.js';
36
36
 
37
37
  // ============================================
@@ -220,6 +220,25 @@ export const TEMPLATE_INPUT_DESCRIPTORS: Readonly<
220
220
  { key: 'align', description: 'Per-column alignment (supply via a data fence)' },
221
221
  { key: 'colorScheme', description: 'Header color scheme', valueHint: COLOR_SCHEME_HINT },
222
222
  ],
223
+ tree: [
224
+ { key: 'title', description: 'Optional title above the tree' },
225
+ { key: 'colorScheme', description: 'Folder-row color scheme', valueHint: COLOR_SCHEME_HINT },
226
+ // `items` is a nested structure derived from the tree fence, not an
227
+ // inline string — declared so it isn't flagged as unknown.
228
+ { key: 'items', description: 'Tree items (derived from the tree fence)' },
229
+ ],
230
+ timeline: [
231
+ { key: 'title', description: 'Optional title above the timeline' },
232
+ {
233
+ key: 'colorScheme',
234
+ description: 'Track and marker color scheme',
235
+ valueHint: COLOR_SCHEME_HINT,
236
+ },
237
+ // Tracks/events and branch links are nested structures derived from the
238
+ // timeline source, not values that can be expressed as one inline string.
239
+ { key: 'tracks', description: 'Timeline tracks and events (supply via template data)' },
240
+ { key: 'links', description: 'Branch links between event ids (supply via template data)' },
241
+ ],
223
242
  videoWithCaption: [
224
243
  { key: 'videoSrc', description: 'Path to the video file', required: true },
225
244
  { key: 'posterSrc', description: 'Poster frame path' },
@@ -108,6 +108,16 @@ export const TEMPLATE_METADATA: Record<string, TemplateMetadata> = {
108
108
  description:
109
109
  'Renders sub-headings as connected nodes — edit visually as a node-and-edge diagram.',
110
110
  },
111
+ tree: {
112
+ label: 'Tree',
113
+ description:
114
+ 'Renders an ASCII file-tree / outline fence as a filesystem-style treeview — edit as an outline.',
115
+ },
116
+ timeline: {
117
+ label: 'Timeline',
118
+ description:
119
+ 'Plots events across one or more horizontal tracks with callouts and optional branching links.',
120
+ },
111
121
  layout: {
112
122
  label: 'Layout',
113
123
  description: