@bendyline/squisq 1.5.2 → 2.0.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 (268) 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-T5UK6YOB.js → chunk-2PWOO4ZE.js} +60 -17
  6. package/dist/chunk-2PWOO4ZE.js.map +1 -0
  7. package/dist/{chunk-VWUFZ6ZG.js → chunk-3E5F2XMR.js} +2 -29
  8. package/dist/chunk-3E5F2XMR.js.map +1 -0
  9. package/dist/chunk-4X3JQXNM.js +35 -0
  10. package/dist/chunk-4X3JQXNM.js.map +1 -0
  11. package/dist/{chunk-P3K7NLHF.js → chunk-5DKBFXPY.js} +31 -121
  12. package/dist/chunk-5DKBFXPY.js.map +1 -0
  13. package/dist/{chunk-DLXZMS5K.js → chunk-5J4HKRJF.js} +51 -3
  14. package/dist/chunk-5J4HKRJF.js.map +1 -0
  15. package/dist/chunk-5UYI3RJB.js +2503 -0
  16. package/dist/chunk-5UYI3RJB.js.map +1 -0
  17. package/dist/{chunk-BEQODJWV.js → chunk-66QO7XFB.js} +11 -291
  18. package/dist/chunk-66QO7XFB.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-CY2R2VPR.js +884 -0
  22. package/dist/chunk-CY2R2VPR.js.map +1 -0
  23. package/dist/chunk-FVJUUTEM.js +74 -0
  24. package/dist/chunk-FVJUUTEM.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-MMPBOUK7.js} +10 -73
  30. package/dist/chunk-MMPBOUK7.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-GPLTCSXO.js → chunk-R2GC6RLD.js} +7 -7
  36. package/dist/chunk-R2GC6RLD.js.map +1 -0
  37. package/dist/chunk-R2YZTCLO.js +292 -0
  38. package/dist/chunk-R2YZTCLO.js.map +1 -0
  39. package/dist/chunk-S7A5D7ZJ.js +174 -0
  40. package/dist/chunk-S7A5D7ZJ.js.map +1 -0
  41. package/dist/chunk-TFJVXUHH.js +2358 -0
  42. package/dist/chunk-TFJVXUHH.js.map +1 -0
  43. package/dist/{chunk-LTN6I7QW.js → chunk-TUWMK62B.js} +55 -4
  44. package/dist/chunk-TUWMK62B.js.map +1 -0
  45. package/dist/{chunk-IMSCRLLV.js → chunk-VR7UPJ7C.js} +35 -25
  46. package/dist/chunk-VR7UPJ7C.js.map +1 -0
  47. package/dist/{chunk-KKNUBQ6Y.js → chunk-W4O2TX5I.js} +157 -92
  48. package/dist/chunk-W4O2TX5I.js.map +1 -0
  49. package/dist/{chunk-5TYS5V3G.js → chunk-X6DUONGT.js} +4204 -3560
  50. package/dist/chunk-X6DUONGT.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__/customThemesFrontmatter.test.ts +1 -0
  98. package/src/__tests__/dataTable.test.ts +25 -15
  99. package/src/__tests__/diagramBlock.test.ts +3 -3
  100. package/src/__tests__/diagramText.test.ts +26 -0
  101. package/src/__tests__/docToMarkdownMedia.test.ts +83 -0
  102. package/src/__tests__/drawingBlock.test.ts +3 -3
  103. package/src/__tests__/fixtures/asciiDiagramBattle.ts +512 -0
  104. package/src/__tests__/fixtures/asciiDiagrams.ts +287 -0
  105. package/src/__tests__/fixtures/asciiTimelines.ts +35 -0
  106. package/src/__tests__/fixtures/treeviewFixtures.ts +243 -0
  107. package/src/__tests__/getLayers.test.ts +32 -18
  108. package/src/__tests__/getLayersFallback.test.ts +8 -8
  109. package/src/__tests__/imageEditVersions.test.ts +2 -0
  110. package/src/__tests__/jsonForm.pointer.test.ts +47 -1
  111. package/src/__tests__/layoutBlock.test.ts +3 -3
  112. package/src/__tests__/majorApiTypes.test.ts +18 -0
  113. package/src/__tests__/markdown.test.ts +54 -4
  114. package/src/__tests__/markdownSanitize.test.ts +7 -5
  115. package/src/__tests__/matchFontFamily.test.ts +114 -0
  116. package/src/__tests__/materializeBlockLayers.test.ts +135 -0
  117. package/src/__tests__/materializeTestUtils.ts +17 -0
  118. package/src/__tests__/narrationAlign.test.ts +150 -0
  119. package/src/__tests__/narrationFeatures.test.ts +71 -0
  120. package/src/__tests__/narrationNuclei.test.ts +45 -0
  121. package/src/__tests__/narrationPacing.test.ts +189 -0
  122. package/src/__tests__/narrationScript.test.ts +149 -0
  123. package/src/__tests__/narrationSessionRepro.test.ts +62 -0
  124. package/src/__tests__/narrationSidecar.test.ts +88 -0
  125. package/src/__tests__/narrationSyllables.test.ts +65 -0
  126. package/src/__tests__/narrationTestSignals.ts +167 -0
  127. package/src/__tests__/narrationVad.test.ts +66 -0
  128. package/src/__tests__/resolveDocTheme.test.ts +16 -2
  129. package/src/__tests__/rootExports.test.ts +18 -0
  130. package/src/__tests__/scopedContentContainer.test.ts +20 -0
  131. package/src/__tests__/shapeGeometry.test.ts +12 -0
  132. package/src/__tests__/templateAnnotationParse.test.ts +10 -0
  133. package/src/__tests__/templateInputs.test.ts +69 -0
  134. package/src/__tests__/templateMetadata.test.ts +2 -1
  135. package/src/__tests__/templates.test.ts +143 -17
  136. package/src/__tests__/themeCompile.test.ts +49 -22
  137. package/src/__tests__/transformProvenance.test.ts +160 -0
  138. package/src/__tests__/transformV2.test.ts +40 -14
  139. package/src/__tests__/treeviewBattle.test.ts +76 -0
  140. package/src/__tests__/treeviewDetect.test.ts +168 -0
  141. package/src/__tests__/treeviewParse.test.ts +118 -0
  142. package/src/__tests__/treeviewPipeline.test.ts +183 -0
  143. package/src/__tests__/treeviewRender.test.ts +91 -0
  144. package/src/__tests__/versions.test.ts +44 -0
  145. package/src/doc/applyNarrationTiming.ts +244 -0
  146. package/src/doc/asciiDiagram/chars.ts +210 -0
  147. package/src/doc/asciiDiagram/detect.ts +175 -0
  148. package/src/doc/asciiDiagram/index.ts +38 -0
  149. package/src/doc/asciiDiagram/mapping.ts +225 -0
  150. package/src/doc/asciiDiagram/parse.ts +1095 -0
  151. package/src/doc/asciiDiagram/render.ts +963 -0
  152. package/src/doc/asciiDiagram/repair.ts +400 -0
  153. package/src/doc/asciiDiagram/types.ts +65 -0
  154. package/src/doc/asciiTimeline/detect.ts +138 -0
  155. package/src/doc/asciiTimeline/index.ts +26 -0
  156. package/src/doc/asciiTimeline/mapping.ts +43 -0
  157. package/src/doc/asciiTimeline/parse.ts +870 -0
  158. package/src/doc/asciiTimeline/render.ts +427 -0
  159. package/src/doc/asciiTimeline/types.ts +73 -0
  160. package/src/doc/asciiTimeline/wrappedFlow.ts +427 -0
  161. package/src/doc/audioMapping.ts +60 -46
  162. package/src/doc/customTemplatesFrontmatter.ts +0 -41
  163. package/src/doc/docToMarkdown.ts +70 -2
  164. package/src/doc/index.ts +76 -4
  165. package/src/doc/markdownToDoc.ts +138 -2
  166. package/src/doc/materializeBlockLayers.ts +304 -0
  167. package/src/doc/mediaAnnotations.ts +20 -0
  168. package/src/doc/resolveDocTheme.ts +8 -7
  169. package/src/doc/templateInputs.ts +185 -2
  170. package/src/doc/templates/__tests__/customTemplateRegistry.test.ts +22 -8
  171. package/src/doc/templates/__tests__/listBlock.test.ts +64 -0
  172. package/src/doc/templates/diagramBlock.ts +237 -38
  173. package/src/doc/templates/fallbackBlock.ts +1 -1
  174. package/src/doc/templates/index.ts +134 -279
  175. package/src/doc/templates/inputDescriptors.ts +20 -1
  176. package/src/doc/templates/listBlock.ts +88 -30
  177. package/src/doc/templates/metadata.ts +10 -0
  178. package/src/doc/templates/persistentLayers.ts +12 -126
  179. package/src/doc/templates/registry.ts +88 -0
  180. package/src/doc/templates/templateNames.ts +22 -0
  181. package/src/doc/templates/timelineBlock.ts +613 -0
  182. package/src/doc/templates/treeBlock.ts +131 -0
  183. package/src/doc/treeview/chars.ts +72 -0
  184. package/src/doc/treeview/detect.ts +145 -0
  185. package/src/doc/treeview/index.ts +30 -0
  186. package/src/doc/treeview/mapping.ts +95 -0
  187. package/src/doc/treeview/parse.ts +201 -0
  188. package/src/doc/treeview/render.ts +41 -0
  189. package/src/doc/treeview/types.ts +43 -0
  190. package/src/doc/utils/applyRenderStyle.ts +2 -9
  191. package/src/doc/utils/diagramText.ts +78 -0
  192. package/src/doc/utils/shapeGeometry.ts +67 -5
  193. package/src/doc/validate.ts +1 -1
  194. package/src/generate/templateMapper.ts +7 -0
  195. package/src/imageEdit/export.ts +6 -5
  196. package/src/imageEdit/versionPaths.ts +13 -26
  197. package/src/imageEdit/versions.ts +21 -100
  198. package/src/index.ts +1 -0
  199. package/src/internal/immutable.ts +58 -0
  200. package/src/jsonForm/index.ts +9 -1
  201. package/src/jsonForm/pointer.ts +68 -23
  202. package/src/markdown/attrTokens.ts +7 -5
  203. package/src/markdown/index.ts +1 -0
  204. package/src/markdown/stringify.ts +4 -9
  205. package/src/markdown/utils.ts +36 -14
  206. package/src/narration/align.ts +514 -0
  207. package/src/narration/features.ts +231 -0
  208. package/src/narration/index.ts +86 -0
  209. package/src/narration/nuclei.ts +118 -0
  210. package/src/narration/pacing.ts +211 -0
  211. package/src/narration/script.ts +239 -0
  212. package/src/narration/session.ts +100 -0
  213. package/src/narration/sidecar.ts +187 -0
  214. package/src/narration/syllables.ts +85 -0
  215. package/src/narration/trace.ts +50 -0
  216. package/src/narration/types.ts +299 -0
  217. package/src/narration/vad.ts +131 -0
  218. package/src/recommend/numberHighlight.ts +25 -0
  219. package/src/recommend/templates.ts +108 -12
  220. package/src/schemas/BlockTemplates.ts +156 -7
  221. package/src/schemas/Doc.ts +72 -9
  222. package/src/schemas/Media.ts +12 -2
  223. package/src/schemas/Theme.ts +45 -25
  224. package/src/schemas/Viewport.ts +2 -2
  225. package/src/schemas/fontStacks.ts +79 -0
  226. package/src/schemas/themeCompile.ts +15 -0
  227. package/src/schemas/themeConstants.ts +2 -0
  228. package/src/schemas/themeLibrary.ts +25 -19
  229. package/src/schemas/themeValidator.ts +1 -1
  230. package/src/schemas/themes/gezellig.json +1 -1
  231. package/src/spatial/Geohash.ts +0 -3
  232. package/src/storage/ContentContainer.ts +17 -9
  233. package/src/storage/ScopedContentContainer.ts +45 -15
  234. package/src/transform/applyTransform.ts +5 -5
  235. package/src/transform/index.ts +3 -1
  236. package/src/transform/registry.ts +169 -49
  237. package/src/transform/templateSelector.ts +21 -2
  238. package/src/transform/timingAllocator.ts +152 -21
  239. package/src/transform/types.ts +14 -1
  240. package/src/versions/gitignore.ts +34 -0
  241. package/src/versions/index.ts +2 -0
  242. package/src/versions/operations.ts +18 -104
  243. package/src/versions/paths.ts +13 -28
  244. package/src/versions/snapshotStore.ts +202 -0
  245. package/src/versions/types.ts +3 -1
  246. package/dist/chunk-5TYS5V3G.js.map +0 -1
  247. package/dist/chunk-A4O7GIWE.js.map +0 -1
  248. package/dist/chunk-BEQODJWV.js.map +0 -1
  249. package/dist/chunk-DLXZMS5K.js.map +0 -1
  250. package/dist/chunk-GPLTCSXO.js.map +0 -1
  251. package/dist/chunk-HWVFJAAH.js.map +0 -1
  252. package/dist/chunk-IMSCRLLV.js.map +0 -1
  253. package/dist/chunk-JELIRVDP.js +0 -24
  254. package/dist/chunk-JELIRVDP.js.map +0 -1
  255. package/dist/chunk-KKNUBQ6Y.js.map +0 -1
  256. package/dist/chunk-LDQ2HJIX.js.map +0 -1
  257. package/dist/chunk-LTN6I7QW.js.map +0 -1
  258. package/dist/chunk-P3K7NLHF.js.map +0 -1
  259. package/dist/chunk-T5UK6YOB.js.map +0 -1
  260. package/dist/chunk-UY7KGQ5R.js +0 -192
  261. package/dist/chunk-UY7KGQ5R.js.map +0 -1
  262. package/dist/chunk-VLJJHDAC.js +0 -218
  263. package/dist/chunk-VLJJHDAC.js.map +0 -1
  264. package/dist/chunk-VWUFZ6ZG.js.map +0 -1
  265. package/dist/story/index.d.ts +0 -1801
  266. package/dist/story/index.js +0 -279
  267. package/src/doc/getLayers.ts +0 -195
  268. /package/dist/{story → narration}/index.js.map +0 -0
@@ -110,6 +110,19 @@ interface BaseTemplateBlock {
110
110
  * Used with sourceStartTime to precisely position the block.
111
111
  */
112
112
  sourceDuration?: number;
113
+ /**
114
+ * Transform provenance: the id of the source Block this template block
115
+ * was derived from. Lets narration timing (and any per-source styling)
116
+ * be remapped onto transformed/summarized docs.
117
+ */
118
+ sourceBlockId?: string;
119
+ /** All source block ids when the block summarizes more than one. */
120
+ sourceBlockIds?: string[];
121
+ /**
122
+ * Char offset of the extraction inside the source block's plain text.
123
+ * Provenance only — NOT a time (see sourceStartTime for seconds).
124
+ */
125
+ sourceCharOffset?: number;
113
126
  /**
114
127
  * Per-block override for the theme's photographic image grade
115
128
  * (`theme.style.imageTreatment`). `'none'` opts this block's imagery out;
@@ -357,6 +370,92 @@ export interface ListBlockInput extends BaseTemplateBlock {
357
370
  accentImage?: AccentImage;
358
371
  }
359
372
 
373
+ /**
374
+ * Tree block — renders a hierarchical filesystem-style treeview from an
375
+ * ASCII tree fence (file trees, dependency trees, outlines). The nested
376
+ * `items` arrive via `templateData`, derived from the fence by the pipeline
377
+ * (peer to how `diagram` receives `nodes`/`edges`).
378
+ */
379
+ export interface TreeBlockInput extends BaseTemplateBlock {
380
+ template: 'tree';
381
+ /** Nested tree items (usually via `templateData.items`). */
382
+ items?: TreeTemplateItem[];
383
+ /** Optional title above the tree. */
384
+ title?: string;
385
+ /** Color scheme for folder rows / icons. */
386
+ colorScheme?: ColorScheme;
387
+ }
388
+
389
+ /** One node supplied via `templateData.items` (mirrors `TreeLayerItem`). */
390
+ export interface TreeTemplateItem {
391
+ id: string;
392
+ label: string;
393
+ isDir?: boolean;
394
+ comment?: string;
395
+ children: TreeTemplateItem[];
396
+ }
397
+
398
+ /**
399
+ * One point on a timeline track. Event positions are normalized across the
400
+ * whole timeline (0 = left edge, 1 = right edge) so events on separate tracks
401
+ * remain horizontally comparable.
402
+ */
403
+ export interface TimelineTemplateEvent {
404
+ /** Stable id used by cross-track branch links. */
405
+ id: string;
406
+ /** Short label displayed next to the event dot. */
407
+ label: string;
408
+ /** Optional longer callout text. */
409
+ description?: string;
410
+ /** Normalized horizontal position; renderers clamp values to 0..1. */
411
+ position: number;
412
+ /** Preferred callout placement. Defaults to alternating above/below. */
413
+ side?: 'above' | 'below';
414
+ /** Description placement when it should sit opposite the event label. */
415
+ descriptionSide?: 'above' | 'below';
416
+ /** False renders the point only (useful for dense cadence tracks). */
417
+ callout?: boolean;
418
+ /** Dot treatment. Defaults to `filled`. */
419
+ marker?: 'filled' | 'hollow' | 'diamond';
420
+ }
421
+
422
+ /** A horizontal timeline lane with zero or more positioned events. */
423
+ export interface TimelineTemplateTrack {
424
+ id: string;
425
+ /** Optional label displayed to the left of the track. */
426
+ label?: string;
427
+ /** Optional label displayed after the track's terminal arrow. */
428
+ endLabel?: string;
429
+ events: TimelineTemplateEvent[];
430
+ }
431
+
432
+ /**
433
+ * A branch or relationship between two events. Source and target resolve
434
+ * against event ids globally, so links may cross tracks.
435
+ */
436
+ export interface TimelineTemplateLink {
437
+ source: string;
438
+ target: string;
439
+ /** Optional text displayed near the branch path. */
440
+ label?: string;
441
+ }
442
+
443
+ /**
444
+ * Timeline block - renders one or more horizontal tracks with event dots,
445
+ * text callouts, and optional branches between events.
446
+ */
447
+ export interface TimelineBlockInput extends BaseTemplateBlock {
448
+ template: 'timeline';
449
+ /** Tracks and their events (usually supplied through `templateData`). */
450
+ tracks?: TimelineTemplateTrack[];
451
+ /** Optional branch/cross-track links between event ids. */
452
+ links?: TimelineTemplateLink[];
453
+ /** Optional title above the timeline. */
454
+ title?: string;
455
+ /** Color scheme for tracks, markers, and branch links. */
456
+ colorScheme?: ColorScheme;
457
+ }
458
+
360
459
  /**
361
460
  * Photo grid - 2-4 images in a tiled layout.
362
461
  * Used when multiple images are available for visual variety.
@@ -477,13 +576,57 @@ export interface VideoPullQuoteInput extends BaseTemplateBlock {
477
576
  }
478
577
 
479
578
  /**
480
- * Diagram block - renders child headings as a node-and-edge diagram.
579
+ * One diagram node supplied via `templateData.nodes` (canvas units, the
580
+ * same coordinate space as `Block.x`/`Block.y`). This is the data-driven
581
+ * alternative to child headings — used when a diagram is derived from an
582
+ * ASCII-art fence.
583
+ */
584
+ export interface DiagramTemplateNode {
585
+ id: string;
586
+ /** Display label; may contain `\n` for multi-line labels. */
587
+ label: string;
588
+ x: number;
589
+ y: number;
590
+ /** Per-node width override in canvas units (defaults to the standard card width). */
591
+ w?: number;
592
+ /** Per-node height override in canvas units. */
593
+ h?: number;
594
+ /** Id of the node visually containing this one; containers draw behind their children. */
595
+ container?: string;
596
+ }
597
+
598
+ /** A position along one side of a diagram node, normalized from 0 to 1. */
599
+ export interface DiagramEdgeAnchor {
600
+ side: 'top' | 'right' | 'bottom' | 'left';
601
+ /** Left→right on horizontal sides; top→bottom on vertical sides. */
602
+ offset: number;
603
+ }
604
+
605
+ /** An edge for `DiagramTemplateNode` lists. `directed` defaults to true. */
606
+ export interface DiagramTemplateEdge {
607
+ source: string;
608
+ target: string;
609
+ /** Edge label rendered at the midpoint. */
610
+ label?: string;
611
+ /** False suppresses the end arrowhead (a plain connecting line). */
612
+ directed?: boolean;
613
+ /** Optional authored attachment on the source node. */
614
+ sourceAnchor?: DiagramEdgeAnchor;
615
+ /** Optional authored attachment on the target node. */
616
+ targetAnchor?: DiagramEdgeAnchor;
617
+ /** Per-edge path routing; ASCII-derived edges use orthogonal routing. */
618
+ routing?: 'straight' | 'orthogonal' | 'curved';
619
+ }
620
+
621
+ /**
622
+ * Diagram block - renders a node-and-edge diagram.
481
623
  *
482
- * Unlike other templates, the diagram template reads its content from
483
- * the parent block's `children` (passed via `context.children`) — each
484
- * child heading becomes a node, positioned by its `x`/`y` and connected
485
- * by its `connectsTo`. Per-diagram options on this input control overall
486
- * appearance; per-node data lives on the child blocks themselves.
624
+ * Two data sources, in precedence order:
625
+ * 1. Child headings (`context.children`) — each child is a node positioned
626
+ * by its `x`/`y` and connected by its `connectsTo` (legacy authored form).
627
+ * 2. `nodes`/`edges` on this input (usually via `templateData`, derived
628
+ * from an ASCII-art diagram fence in the block body).
629
+ * Per-diagram options on this input control overall appearance.
487
630
  */
488
631
  export interface DiagramBlockInput extends BaseTemplateBlock {
489
632
  template: 'diagram';
@@ -501,6 +644,10 @@ export interface DiagramBlockInput extends BaseTemplateBlock {
501
644
  endStyle?: MarkerStyle;
502
645
  /** Edge line style (default: 'solid'). */
503
646
  lineStyle?: 'solid' | 'dashed' | 'dotted';
647
+ /** Data-driven node list; used only when the block has no children. */
648
+ nodes?: DiagramTemplateNode[];
649
+ /** Edges for `nodes`. */
650
+ edges?: DiagramTemplateEdge[];
504
651
  }
505
652
 
506
653
  /**
@@ -591,6 +738,8 @@ export type TemplateBlock =
591
738
  | VideoPullQuoteInput
592
739
  | DataTableInput
593
740
  | DiagramBlockInput
741
+ | TreeBlockInput
742
+ | TimelineBlockInput
594
743
  | DrawingBlockInput
595
744
  | RawLayersInput;
596
745
 
@@ -630,7 +779,7 @@ export interface TemplateContext {
630
779
  /** Layout hints for this orientation */
631
780
  layout: LayoutHints;
632
781
  /**
633
- * The block's direct children (set by `getLayers` when the block has
782
+ * The block's direct children (set by `materializeBlockLayers` when the block has
634
783
  * `children`). Most templates ignore this; aggregate templates like
635
784
  * `diagram` consume it to render each child as part of their output.
636
785
  */
@@ -215,11 +215,23 @@ export interface Block {
215
215
  /** Which audio segment this block belongs to (0-indexed) */
216
216
  audioSegment: number;
217
217
 
218
+ /**
219
+ * Transform provenance: the source block id this block was derived
220
+ * from (see `BaseTemplateBlock.sourceBlockId`). Present on blocks a
221
+ * transform produced; lets a persisted transformed Doc keep its typed
222
+ * link back to the source content.
223
+ */
224
+ sourceBlockId?: string;
225
+ /** All source block ids when this block summarizes several. */
226
+ sourceBlockIds?: string[];
227
+ /** Char offset of the extraction inside the source block's plain text. */
228
+ sourceCharOffset?: number;
229
+
218
230
  /**
219
231
  * Pre-computed visual layers, rendered back-to-front.
220
232
  *
221
233
  * Optional — template-derived blocks typically omit this and compute
222
- * layers on demand via `getLayers()` from `@bendyline/squisq/doc`.
234
+ * layers on demand via `materializeBlockLayers()` from `@bendyline/squisq/doc`.
223
235
  * Raw blocks (e.g., hand-crafted by AI) may provide layers directly.
224
236
  */
225
237
  layers?: Layer[];
@@ -368,7 +380,8 @@ export type Layer =
368
380
  | PathLayer
369
381
  | MapLayer
370
382
  | VideoLayer
371
- | TableLayer;
383
+ | TableLayer
384
+ | TreeLayer;
372
385
 
373
386
  interface BaseLayer {
374
387
  /** Unique identifier for this layer */
@@ -573,14 +586,9 @@ export interface PathLayer extends BaseLayer {
573
586
  borderStyle?: BorderStyle;
574
587
  /** Optional stroke dash pattern (SVG `stroke-dasharray` syntax). */
575
588
  dasharray?: string;
576
- /**
577
- * Legacy arrowhead flag. Prefer `startMarker`/`endMarker`. When those are
578
- * unset, `'end'`/`'start'`/`'both'` render a filled-triangle arrowhead.
579
- */
580
- arrow?: 'none' | 'end' | 'start' | 'both';
581
- /** Marker at the path start (overrides `arrow`). Default: derived from `arrow`. */
589
+ /** Marker at the path start. */
582
590
  startMarker?: MarkerStyle;
583
- /** Marker at the path end (overrides `arrow`). Default: derived from `arrow`. */
591
+ /** Marker at the path end. */
584
592
  endMarker?: MarkerStyle;
585
593
  };
586
594
  }
@@ -696,6 +704,61 @@ export interface TableLayerStyle {
696
704
  borderRadius?: number;
697
705
  }
698
706
 
707
+ /**
708
+ * One node in a `TreeLayer` — a JSON-serializable hierarchy item (mirrors
709
+ * the treeview codec's `TreeItem`). Rendered as an indented filesystem-style
710
+ * row with a folder/file icon and connector rails.
711
+ */
712
+ export interface TreeLayerItem {
713
+ id: string;
714
+ label: string;
715
+ /** True renders a folder (container) icon; else a file (leaf) icon. */
716
+ isDir?: boolean;
717
+ /** Trailing comment shown muted after the label. */
718
+ comment?: string;
719
+ children: TreeLayerItem[];
720
+ }
721
+
722
+ /**
723
+ * A hierarchical treeview rendered as interactive HTML inside a
724
+ * `<foreignObject>` (like `TableLayer`): folder/file icons, connector
725
+ * rails, and collapse chevrons. Interactive (collapse/expand) in the live
726
+ * React player; captured fully-expanded and static in frame/PDF export.
727
+ */
728
+ export interface TreeLayer extends BaseLayer {
729
+ type: 'tree';
730
+ content: {
731
+ items: TreeLayerItem[];
732
+ style: TreeLayerStyle;
733
+ };
734
+ }
735
+
736
+ /** Styling options for a TreeLayer. */
737
+ export interface TreeLayerStyle {
738
+ /** Leaf (file) row text color. */
739
+ rowColor: string;
740
+ /** Container (folder) row text color. */
741
+ dirColor: string;
742
+ /** Connector rail / branch color. */
743
+ connectorColor: string;
744
+ /** Icon color. */
745
+ iconColor: string;
746
+ /** Muted comment color. */
747
+ commentColor: string;
748
+ /** Font size in pixels. */
749
+ fontSize: number;
750
+ /** Row label font family. */
751
+ fontFamily?: string;
752
+ /** Monospace font for the connector rails. */
753
+ monoFontFamily?: string;
754
+ /** Indent step per depth level, in pixels. */
755
+ indentPx: number;
756
+ /** FontAwesome icon token for containers (default `folder`). */
757
+ folderIcon?: string;
758
+ /** FontAwesome icon token for leaves (default `file`). */
759
+ fileIcon?: string;
760
+ }
761
+
699
762
  /**
700
763
  * Available map tile styles from free/open-source providers.
701
764
  */
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Media-clip timing model.
3
3
  *
4
- * An additive layer on top of the legacy sequential `Doc.audio.segments[]`
5
- * narration track. A {@link MediaClip} is a piece of audio or video whose
4
+ * An additive layer on top of the simple sequential `Doc.audio.segments[]`
5
+ * narration track, which remains first-class. A {@link MediaClip} is a piece
6
+ * of audio or video whose
6
7
  * timing is expressed *relative to its parent block* (or the whole document),
7
8
  * with an optional `startAt` offset and an optional `spillover` past the
8
9
  * block's end. {@link resolveMediaSchedule} flattens these into absolute,
@@ -51,6 +52,15 @@ export interface MediaClip {
51
52
  * `startAt`) back to the exact line. Absent for programmatically built clips.
52
53
  */
53
54
  sourceLine?: number;
55
+ /**
56
+ * Round-trip home of the authoring annotation: the block whose contents
57
+ * held the paragraph, its position in that block's ORIGINAL contents
58
+ * array (before extraction), and the paragraph's exact source text.
59
+ * `docToMarkdown` re-inserts the annotation from this — `raw` verbatim
60
+ * when present, a re-serialized annotation otherwise. Absent for
61
+ * programmatically built clips (those emit at the document top).
62
+ */
63
+ origin?: { blockId: string; index: number; raw?: string };
54
64
  }
55
65
 
56
66
  /**
@@ -23,13 +23,15 @@
23
23
  import type { LayoutHints } from './LayoutStrategy.js';
24
24
  import type { AnimationType, ImageTreatment, TransitionType } from './Doc.js';
25
25
  import type { PersistentLayerConfig } from './BlockTemplates.js';
26
+ import { THEME_SCHEMA_VERSION } from './themeConstants.js';
27
+ import { validateTheme } from './themeValidator.js';
28
+ import { cloneAndFreezeData } from '../internal/immutable.js';
26
29
 
27
30
  // ============================================
28
31
  // Schema version
29
32
  // ============================================
30
33
 
31
- /** Current Theme schema version. Bump on breaking changes; loader migrates. */
32
- export const THEME_SCHEMA_VERSION = '1' as const;
34
+ export { THEME_SCHEMA_VERSION } from './themeConstants.js';
33
35
  export type ThemeSchemaVersion = typeof THEME_SCHEMA_VERSION;
34
36
 
35
37
  /**
@@ -354,33 +356,51 @@ export function applySurface(theme: Theme, surface: SurfaceScheme): Theme {
354
356
  }
355
357
 
356
358
  // ============================================
357
- // Runtime registry — for custom themes
359
+ // Caller-owned registry — for custom themes
358
360
  // ============================================
359
361
 
360
- const CUSTOM_THEME_REGISTRY = new Map<string, Theme>();
361
-
362
- /**
363
- * Register a Theme so it can be looked up by id via `resolveTheme(id)`.
364
- * Lets `Doc.themeId` round-trip through Doc serialization for custom themes.
365
- *
366
- * Registered themes take precedence over built-ins with the same id.
367
- */
368
- export function registerTheme(theme: Theme): void {
369
- CUSTOM_THEME_REGISTRY.set(theme.id, theme);
362
+ /** A caller-owned custom-theme registry for SSR tenants, tests, and embedders. */
363
+ export interface ThemeRegistry {
364
+ register(theme: Theme): void;
365
+ unregister(id: string): boolean;
366
+ get(id: string): Theme | undefined;
367
+ list(): Theme[];
370
368
  }
371
369
 
372
- /** Remove a previously registered theme. */
373
- export function unregisterTheme(id: string): void {
374
- CUSTOM_THEME_REGISTRY.delete(id);
375
- }
376
-
377
- /** Snapshot of all currently registered (non-built-in) themes. */
378
- export function getRegisteredThemes(): Theme[] {
379
- return Array.from(CUSTOM_THEME_REGISTRY.values());
370
+ function validatedThemeSnapshot(theme: Theme): Theme {
371
+ const result = validateTheme(theme);
372
+ if (!result.valid || !result.theme) {
373
+ const details = result.errors
374
+ .slice(0, 5)
375
+ .map((error) => `${error.path || '<root>'}: ${error.message}`)
376
+ .join('; ');
377
+ throw new TypeError(`Cannot register invalid theme: ${details}`);
378
+ }
379
+ return cloneAndFreezeData(result.theme);
380
380
  }
381
381
 
382
- /** @internal — used by themeLibrary's `resolveTheme` to check the registry first. */
383
- export function lookupRegisteredTheme(id: string | undefined): Theme | undefined {
384
- if (!id) return undefined;
385
- return CUSTOM_THEME_REGISTRY.get(id);
382
+ /**
383
+ * Create a registry whose state is owned by the caller rather than the
384
+ * process. Registered values are validated and stored as deeply frozen
385
+ * snapshots, so later caller mutation cannot alter resolution behavior.
386
+ */
387
+ export function createThemeRegistry(initialThemes: readonly Theme[] = []): ThemeRegistry {
388
+ const themes = new Map<string, Theme>();
389
+ const registry: ThemeRegistry = {
390
+ register(theme): void {
391
+ const snapshot = validatedThemeSnapshot(theme);
392
+ themes.set(snapshot.id, snapshot);
393
+ },
394
+ unregister(id): boolean {
395
+ return themes.delete(id);
396
+ },
397
+ get(id): Theme | undefined {
398
+ return themes.get(id);
399
+ },
400
+ list(): Theme[] {
401
+ return Array.from(themes.values());
402
+ },
403
+ };
404
+ for (const theme of initialThemes) registry.register(theme);
405
+ return Object.freeze(registry);
386
406
  }
@@ -8,7 +8,7 @@
8
8
  * - landscape (16:9) - Default, standard video/presentation format
9
9
  * - portrait (9:16) - Vertical video, mobile stories
10
10
  * - square (1:1) - Social media posts
11
- * - standard (4:3) - Legacy presentation format
11
+ * - standard (4:3) - Classic presentation format
12
12
  */
13
13
 
14
14
  /**
@@ -33,7 +33,7 @@ export const VIEWPORT_PRESETS = {
33
33
  portrait: { width: 1080, height: 1920, name: '9:16 Portrait' },
34
34
  /** 1:1 square (social media) */
35
35
  square: { width: 1080, height: 1080, name: '1:1 Square' },
36
- /** 4:3 standard (legacy) */
36
+ /** 4:3 standard */
37
37
  standard: { width: 1440, height: 1080, name: '4:3 Standard' },
38
38
  } as const;
39
39
 
@@ -258,3 +258,82 @@ export function buildGoogleFontsUrl(families: Array<FontFamily | undefined>): st
258
258
 
259
259
  /** Default fallback families exposed for tools that need them. */
260
260
  export const FONT_FALLBACKS = FALLBACK_BY_KIND;
261
+
262
+ // ── Arbitrary font-name matching ─────────────────────────────────────
263
+ //
264
+ // Used by file-import theme inference (OOXML fontScheme → FontFamily):
265
+ // a typeface name from an office document either matches a curated stack
266
+ // or becomes a `{ custom }` reference that preserves the original face.
267
+
268
+ /** Normalize a raw typeface name for lookup: trim, unquote, lowercase, collapse whitespace. */
269
+ function normalizeFontName(name: string): string {
270
+ return name
271
+ .trim()
272
+ .replace(/^["']+|["']+$/g, '')
273
+ .replace(/\s+/g, ' ')
274
+ .toLowerCase();
275
+ }
276
+
277
+ /**
278
+ * Normalized name → stackId. Seeded from every stack's label and Google
279
+ * Fonts family name, plus explicit aliases for common variants and the
280
+ * web-safe faces that already anchor the system stacks' CSS families.
281
+ */
282
+ const STACK_NAME_ALIASES: Record<string, string> = (() => {
283
+ const aliases: Record<string, string> = {};
284
+ for (const stack of AVAILABLE_FONT_STACKS) {
285
+ aliases[normalizeFontName(stack.label)] = stack.id;
286
+ if (stack.googleFontFamily) aliases[normalizeFontName(stack.googleFontFamily)] = stack.id;
287
+ }
288
+ Object.assign(aliases, {
289
+ playfair: 'playfair',
290
+ 'source serif': 'source-serif',
291
+ 'source serif pro': 'source-serif',
292
+ 'crimson pro': 'crimson',
293
+ cormorant: 'cormorant',
294
+ georgia: 'system-serif',
295
+ 'times new roman': 'system-serif',
296
+ times: 'system-serif',
297
+ consolas: 'system-mono',
298
+ 'courier new': 'system-mono',
299
+ courier: 'system-mono',
300
+ 'segoe ui': 'system-sans',
301
+ 'system-ui': 'system-sans',
302
+ });
303
+ return aliases;
304
+ })();
305
+
306
+ const MONO_NAME_RE =
307
+ /\b(mono|monospace|consolas|courier|menlo|inconsolata|cascadia|fira code|source code|code)\b/;
308
+ const SERIF_NAME_RE =
309
+ /\b(georgia|times|garamond|cambria|caslon|baskerville|bodoni|didot|palatino|book antiqua|goudy|minion|charter|constantia|century schoolbook|antiqua|roman)\b/;
310
+
311
+ /**
312
+ * Guess the structured fallback bucket for an arbitrary typeface name.
313
+ * Deliberately never guesses `system-ui` — a wrong serif/sans guess is
314
+ * recoverable in the customizer; `system-ui` erases the signal entirely.
315
+ */
316
+ export function guessFontFallback(
317
+ name: string,
318
+ ): 'serif' | 'sans-serif' | 'monospace' | 'system-ui' {
319
+ const n = normalizeFontName(name);
320
+ if (MONO_NAME_RE.test(n)) return 'monospace';
321
+ if ((n.includes('serif') && !n.includes('sans')) || SERIF_NAME_RE.test(n)) return 'serif';
322
+ return 'sans-serif';
323
+ }
324
+
325
+ /**
326
+ * Map an arbitrary typeface name (e.g. from an imported office document's
327
+ * fontScheme) to a `FontFamily`: a curated `{ stackId }` when the name
328
+ * matches a known stack, else `{ custom }` preserving the original name
329
+ * (original casing, quotes stripped) with a heuristic fallback bucket.
330
+ * Empty/blank names resolve to the system sans stack.
331
+ */
332
+ export function matchFontFamily(name: string): FontFamily {
333
+ const normalized = normalizeFontName(name);
334
+ if (!normalized) return { stackId: 'system-sans' };
335
+ const stackId = STACK_NAME_ALIASES[normalized];
336
+ if (stackId) return { stackId };
337
+ const displayName = name.trim().replace(/^["']+|["']+$/g, '');
338
+ return { custom: { name: displayName, fallback: guessFontFallback(name) } };
339
+ }
@@ -28,6 +28,8 @@ import {
28
28
  oklchSetChroma,
29
29
  pickContrastingText,
30
30
  relativeLuminance,
31
+ deriveScale,
32
+ isHex,
31
33
  } from './colorUtils.js';
32
34
 
33
35
  /** Internal default theme used to fill in fields the customizer doesn't expose. */
@@ -134,6 +136,19 @@ export function deriveColorPalette(
134
136
  };
135
137
  }
136
138
 
139
+ /**
140
+ * Expand one accent color into a full `{bg, text, accent}` color scheme via
141
+ * the OKLCh scale — dark saturated bg, light readable text, the picked accent.
142
+ * Falls back to a neutral scheme when the input isn't a valid hex. Shared by
143
+ * the editor's accent list and file-import theme inference so both derive
144
+ * identical schemes from the same accent.
145
+ */
146
+ export function accentToColorScheme(accent: string): ThemeColorScheme {
147
+ if (!isHex(accent)) return { bg: '#1a202c', text: '#e2e8f0', accent: '#63b3ed' };
148
+ const scale = deriveScale(accent, 0.3);
149
+ return { bg: scale.darker2, text: scale.lighter2, accent: scale.base };
150
+ }
151
+
137
152
  /** Map a contrast preset to numeric spread used by `deriveColorPalette`. */
138
153
  export type ContrastPreset = 'subtle' | 'balanced' | 'high';
139
154
 
@@ -0,0 +1,2 @@
1
+ /** Current Theme schema version. Bump on breaking changes; loader migrates. */
2
+ export const THEME_SCHEMA_VERSION = '1' as const;
@@ -9,17 +9,21 @@
9
9
  * ```ts
10
10
  * import { THEMES, resolveTheme } from '@bendyline/squisq/schemas';
11
11
  *
12
- * const theme = resolveTheme('minimalist'); // by id, with registry override
12
+ * const theme = resolveTheme('minimalist'); // built-in lookup by id
13
13
  * const theme = THEMES.documentary; // direct access to a built-in
14
14
  * ```
15
15
  */
16
16
 
17
- import type { Theme } from './Theme.js';
18
- import { lookupRegisteredTheme } from './Theme.js';
17
+ import type { Theme, ThemeRegistry } from './Theme.js';
19
18
  import { BUILTIN_THEMES } from './themes/index.js';
19
+ import { cloneAndFreezeData } from '../internal/immutable.js';
20
20
 
21
21
  /** All built-in themes, keyed by id. */
22
- export const THEMES: Record<string, Theme> = BUILTIN_THEMES;
22
+ export const THEMES: Readonly<Record<string, Theme>> = Object.freeze(
23
+ Object.fromEntries(
24
+ Object.entries(BUILTIN_THEMES).map(([id, theme]) => [id, cloneAndFreezeData(theme)]),
25
+ ),
26
+ );
23
27
 
24
28
  /** The default theme id. */
25
29
  export const DEFAULT_THEME_ID = 'standard';
@@ -28,27 +32,29 @@ export const DEFAULT_THEME_ID = 'standard';
28
32
  export const DEFAULT_THEME: Theme = THEMES[DEFAULT_THEME_ID];
29
33
 
30
34
  /**
31
- * Resolve a theme by id. Custom themes registered via `registerTheme()`
32
- * take precedence over built-ins; otherwise built-ins are searched and
33
- * the default theme is returned for unknown ids.
35
+ * Resolve a theme by id. When a caller-owned registry is supplied its themes
36
+ * take precedence over built-ins; unknown ids fall back to the default.
34
37
  */
35
- export function resolveTheme(id: string | undefined): Theme {
36
- const registered = lookupRegisteredTheme(id);
38
+ export function resolveTheme(id: string | undefined, registry?: ThemeRegistry): Theme {
39
+ const registered = id ? registry?.get(id) : undefined;
37
40
  if (registered) return registered;
38
41
  if (id && id in THEMES) return THEMES[id];
39
42
  return DEFAULT_THEME;
40
43
  }
41
44
 
42
- /** Get all built-in theme ids. */
43
- export function getAvailableThemes(): string[] {
44
- return Object.keys(THEMES);
45
+ /** Get built-in ids plus ids from an optional caller-owned registry. */
46
+ export function getAvailableThemes(registry?: ThemeRegistry): string[] {
47
+ return Array.from(
48
+ new Set([...Object.keys(THEMES), ...(registry?.list().map((theme) => theme.id) ?? [])]),
49
+ );
45
50
  }
46
51
 
47
- /** Get a summary of all built-in themes (id + name + description) for theme pickers. */
48
- export function getThemeSummaries(): Array<{ id: string; name: string; description?: string }> {
49
- return Object.values(THEMES).map((t) => ({
50
- id: t.id,
51
- name: t.name,
52
- description: t.description,
53
- }));
52
+ /** Get summaries for built-ins plus an optional caller-owned registry. */
53
+ export function getThemeSummaries(
54
+ registry?: ThemeRegistry,
55
+ ): Array<{ id: string; name: string; description?: string }> {
56
+ return getAvailableThemes(registry).map((id) => {
57
+ const theme = resolveTheme(id, registry);
58
+ return { id, name: theme.name, description: theme.description };
59
+ });
54
60
  }
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import type { Theme, ThemeColorPalette } from './Theme.js';
14
- import { THEME_SCHEMA_VERSION } from './Theme.js';
14
+ import { THEME_SCHEMA_VERSION } from './themeConstants.js';
15
15
  import { isHex } from './colorUtils.js';
16
16
  import { isTransitionType } from './Transitions.js';
17
17
 
@@ -2,7 +2,7 @@
2
2
  "schemaVersion": "1",
3
3
  "id": "gezellig",
4
4
  "name": "Gezellig",
5
- "description": "Warm sage + terracotta palette inspired by the Gezel desktop app. Subtle orange-tinted dark background, cream text, sage and terracotta accents.",
5
+ "description": "Cozy, warm-hued palette with a subtle orange-tinted dark background, cream text, and sage and terracotta accents.",
6
6
  "colors": {
7
7
  "primary": "#b0724c",
8
8
  "secondary": "#667f62",