@bendyline/squisq 2.1.0 → 2.3.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 (442) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +24 -0
  3. package/README.md +8 -8
  4. package/dist/{ContentContainer-BXUlIf18.d.ts → ContentContainer-B2w9sUoL.d.ts} +15 -0
  5. package/dist/{Doc-DynJleo-.d.ts → Doc-COe-rPQB.d.ts} +7 -5
  6. package/dist/{ImageEditDoc-Ca4Tr9uN.d.ts → ImageEditDoc-dtKahVyD.d.ts} +1 -1
  7. package/dist/{annotationCoercion-Dr_DY9Hx.d.ts → annotationCoercion-PtRQhk5V.d.ts} +1 -1
  8. package/dist/{chunk-VWCJ2WXC.js → chunk-2ZWIXGAC.js} +0 -1
  9. package/dist/{chunk-VYFRI7R6.js → chunk-32R2RZAO.js} +72 -3
  10. package/dist/chunk-3IGPAPQ7.js +645 -0
  11. package/dist/{chunk-QE7TWGVT.js → chunk-4VOD55SX.js} +0 -1
  12. package/dist/{chunk-3E5F2XMR.js → chunk-67L6WRJN.js} +0 -1
  13. package/dist/{chunk-5DKBFXPY.js → chunk-6ECMNUIP.js} +26 -18
  14. package/dist/{chunk-DBS6RIFL.js → chunk-7N4G32LG.js} +0 -1
  15. package/dist/{chunk-JOQYPRDZ.js → chunk-7YEZFHLS.js} +10 -8
  16. package/dist/{chunk-2UQOHYE2.js → chunk-7ZAAICW4.js} +0 -1
  17. package/dist/{chunk-66QO7XFB.js → chunk-ACZOVWX3.js} +117 -7
  18. package/dist/{chunk-5J4HKRJF.js → chunk-BAOV476U.js} +0 -1
  19. package/dist/chunk-BCCXTMN5.js +699 -0
  20. package/dist/{chunk-JHZWYRSW.js → chunk-C33GTPUZ.js} +11 -4
  21. package/dist/{chunk-XNLB57UD.js → chunk-EK446BUE.js} +18 -2
  22. package/dist/{chunk-VR7UPJ7C.js → chunk-FCG3BIZN.js} +12 -1
  23. package/dist/{chunk-JI77MGAK.js → chunk-GIIOY6XR.js} +2178 -1291
  24. package/dist/{chunk-Q7LROV2H.js → chunk-NKNMGIIN.js} +0 -1
  25. package/dist/chunk-OPO5K4DZ.js +241 -0
  26. package/dist/{chunk-TUWMK62B.js → chunk-PUS54YU6.js} +45 -2
  27. package/dist/{chunk-OTKVFVL6.js → chunk-QNTQIEIV.js} +130 -47
  28. package/dist/{chunk-5UYI3RJB.js → chunk-QWCFK5FN.js} +55 -40
  29. package/dist/{chunk-GZY66SDN.js → chunk-R3KTTOMS.js} +0 -1
  30. package/dist/{chunk-ILWDXLUK.js → chunk-RS5AP3J4.js} +49 -10
  31. package/dist/{chunk-QUUPYOSP.js → chunk-SPTY4C6F.js} +136 -5
  32. package/dist/{chunk-4X3JQXNM.js → chunk-XOFT65EX.js} +1 -2
  33. package/dist/{chunk-FVTQSNR7.js → chunk-XRW6P7IF.js} +13 -5
  34. package/dist/{chunk-R2YZTCLO.js → chunk-ZQKZSJAX.js} +1 -2
  35. package/dist/doc/index.d.ts +311 -13
  36. package/dist/doc/index.js +25 -17
  37. package/dist/generate/index.d.ts +2 -2
  38. package/dist/generate/index.js +1 -3
  39. package/dist/icons/index.js +2 -4
  40. package/dist/icons/inlineIconMarker.js +1 -2
  41. package/dist/imageEdit/index.d.ts +48 -17
  42. package/dist/imageEdit/index.js +3 -4
  43. package/dist/index.d.ts +13 -13
  44. package/dist/index.js +67 -33
  45. package/dist/jsonForm/index.d.ts +10 -3
  46. package/dist/jsonForm/index.js +8 -7
  47. package/dist/markdown/index.d.ts +75 -11
  48. package/dist/markdown/index.js +32 -7
  49. package/dist/{materializePageSection-DDzubQXo.d.ts → materializePageSection-BSMOocve.d.ts} +2 -2
  50. package/dist/narration/index.d.ts +16 -3
  51. package/dist/narration/index.js +12 -14
  52. package/dist/random/index.d.ts +11 -2
  53. package/dist/random/index.js +1 -3
  54. package/dist/recommend/index.d.ts +1 -1
  55. package/dist/recommend/index.js +2 -4
  56. package/dist/schemas/index.d.ts +18 -6
  57. package/dist/schemas/index.js +7 -6
  58. package/dist/spatial/index.d.ts +2 -1
  59. package/dist/spatial/index.js +1 -2
  60. package/dist/storage/index.d.ts +4 -2
  61. package/dist/storage/index.js +1 -2
  62. package/dist/{themeLibrary-B72m0W1y.d.ts → themeLibrary-BUTWjufL.d.ts} +1 -1
  63. package/dist/timing/index.js +2 -4
  64. package/dist/transform/index.d.ts +3 -3
  65. package/dist/transform/index.js +6 -7
  66. package/dist/{types-C4kiRn_k.d.ts → types-8QjefM9J.d.ts} +38 -2
  67. package/dist/{types-BwMPl2CB.d.ts → types-CUNc9biN.d.ts} +24 -1
  68. package/dist/versions/index.d.ts +14 -11
  69. package/dist/versions/index.js +2 -3
  70. package/package.json +8 -3
  71. package/dist/chunk-2UQOHYE2.js.map +0 -1
  72. package/dist/chunk-3E5F2XMR.js.map +0 -1
  73. package/dist/chunk-4X3JQXNM.js.map +0 -1
  74. package/dist/chunk-5DKBFXPY.js.map +0 -1
  75. package/dist/chunk-5J4HKRJF.js.map +0 -1
  76. package/dist/chunk-5UYI3RJB.js.map +0 -1
  77. package/dist/chunk-66QO7XFB.js.map +0 -1
  78. package/dist/chunk-AMIR72JP.js +0 -380
  79. package/dist/chunk-AMIR72JP.js.map +0 -1
  80. package/dist/chunk-DBS6RIFL.js.map +0 -1
  81. package/dist/chunk-DNJKBAI7.js +0 -1
  82. package/dist/chunk-DNJKBAI7.js.map +0 -1
  83. package/dist/chunk-ET53IIEP.js +0 -1
  84. package/dist/chunk-ET53IIEP.js.map +0 -1
  85. package/dist/chunk-FVTQSNR7.js.map +0 -1
  86. package/dist/chunk-GZY66SDN.js.map +0 -1
  87. package/dist/chunk-ILWDXLUK.js.map +0 -1
  88. package/dist/chunk-JHZWYRSW.js.map +0 -1
  89. package/dist/chunk-JI77MGAK.js.map +0 -1
  90. package/dist/chunk-JOQYPRDZ.js.map +0 -1
  91. package/dist/chunk-OTKVFVL6.js.map +0 -1
  92. package/dist/chunk-PGGGKYWG.js +0 -1
  93. package/dist/chunk-PGGGKYWG.js.map +0 -1
  94. package/dist/chunk-Q7LROV2H.js.map +0 -1
  95. package/dist/chunk-QE7TWGVT.js.map +0 -1
  96. package/dist/chunk-QUUPYOSP.js.map +0 -1
  97. package/dist/chunk-R2GC6RLD.js +0 -344
  98. package/dist/chunk-R2GC6RLD.js.map +0 -1
  99. package/dist/chunk-R2YZTCLO.js.map +0 -1
  100. package/dist/chunk-S7A5D7ZJ.js +0 -174
  101. package/dist/chunk-S7A5D7ZJ.js.map +0 -1
  102. package/dist/chunk-TUWMK62B.js.map +0 -1
  103. package/dist/chunk-V543AI2F.js +0 -1
  104. package/dist/chunk-V543AI2F.js.map +0 -1
  105. package/dist/chunk-VR7UPJ7C.js.map +0 -1
  106. package/dist/chunk-VWCJ2WXC.js.map +0 -1
  107. package/dist/chunk-VYFRI7R6.js.map +0 -1
  108. package/dist/chunk-XNLB57UD.js.map +0 -1
  109. package/dist/chunk-ZLQKNOZQ.js +0 -1
  110. package/dist/chunk-ZLQKNOZQ.js.map +0 -1
  111. package/dist/doc/index.js.map +0 -1
  112. package/dist/generate/index.js.map +0 -1
  113. package/dist/icons/index.js.map +0 -1
  114. package/dist/icons/inlineIconMarker.js.map +0 -1
  115. package/dist/imageEdit/index.js.map +0 -1
  116. package/dist/index.js.map +0 -1
  117. package/dist/jsonForm/index.js.map +0 -1
  118. package/dist/markdown/index.js.map +0 -1
  119. package/dist/narration/index.js.map +0 -1
  120. package/dist/random/index.js.map +0 -1
  121. package/dist/recommend/index.js.map +0 -1
  122. package/dist/schemas/index.js.map +0 -1
  123. package/dist/spatial/index.js.map +0 -1
  124. package/dist/storage/index.js.map +0 -1
  125. package/dist/timing/index.js.map +0 -1
  126. package/dist/transform/index.js.map +0 -1
  127. package/dist/versions/index.js.map +0 -1
  128. package/src/__tests__/animationUtils.test.ts +0 -117
  129. package/src/__tests__/annotationCoercion.test.ts +0 -243
  130. package/src/__tests__/applyNarrationTiming.test.ts +0 -190
  131. package/src/__tests__/applyRenderStyle.test.ts +0 -146
  132. package/src/__tests__/asciiDiagramBattle.test.ts +0 -246
  133. package/src/__tests__/asciiDiagramDetect.test.ts +0 -162
  134. package/src/__tests__/asciiDiagramMapping.test.ts +0 -38
  135. package/src/__tests__/asciiDiagramParse.test.ts +0 -233
  136. package/src/__tests__/asciiDiagramPipeline.test.ts +0 -273
  137. package/src/__tests__/asciiDiagramRender.test.ts +0 -162
  138. package/src/__tests__/asciiDiagramRepair.test.ts +0 -84
  139. package/src/__tests__/asciiTimelineDetect.test.ts +0 -157
  140. package/src/__tests__/asciiTimelineParse.test.ts +0 -205
  141. package/src/__tests__/asciiTimelinePipeline.test.ts +0 -349
  142. package/src/__tests__/asciiTimelineRender.test.ts +0 -376
  143. package/src/__tests__/atmosphereLayers.test.ts +0 -108
  144. package/src/__tests__/attrTokens.test.ts +0 -167
  145. package/src/__tests__/colorUtils.test.ts +0 -112
  146. package/src/__tests__/contentContainer.test.ts +0 -325
  147. package/src/__tests__/contentExtractor.test.ts +0 -243
  148. package/src/__tests__/coverBlock.test.ts +0 -310
  149. package/src/__tests__/customTemplateSample.test.ts +0 -144
  150. package/src/__tests__/customTemplatesFrontmatter.test.ts +0 -221
  151. package/src/__tests__/customThemesFrontmatter.test.ts +0 -146
  152. package/src/__tests__/dataTable.test.ts +0 -145
  153. package/src/__tests__/diagramBlock.test.ts +0 -122
  154. package/src/__tests__/diagramLayout.test.ts +0 -134
  155. package/src/__tests__/diagramText.test.ts +0 -26
  156. package/src/__tests__/docToMarkdownMedia.test.ts +0 -83
  157. package/src/__tests__/drawing.test.ts +0 -109
  158. package/src/__tests__/drawingBlock.test.ts +0 -124
  159. package/src/__tests__/drawingLayout.test.ts +0 -171
  160. package/src/__tests__/fixtures/asciiDiagramBattle.ts +0 -512
  161. package/src/__tests__/fixtures/asciiDiagrams.ts +0 -287
  162. package/src/__tests__/fixtures/asciiTimelines.ts +0 -35
  163. package/src/__tests__/fixtures/treeviewFixtures.ts +0 -243
  164. package/src/__tests__/geohash.test.ts +0 -110
  165. package/src/__tests__/getLayers.test.ts +0 -243
  166. package/src/__tests__/getLayersFallback.test.ts +0 -103
  167. package/src/__tests__/haversine.test.ts +0 -63
  168. package/src/__tests__/iconResolve.test.ts +0 -122
  169. package/src/__tests__/imageEditExportSvg.test.ts +0 -70
  170. package/src/__tests__/imageEditState.test.ts +0 -74
  171. package/src/__tests__/imageEditVersions.test.ts +0 -243
  172. package/src/__tests__/inferDocumentTitle.test.ts +0 -50
  173. package/src/__tests__/inlineIconMarker.test.ts +0 -36
  174. package/src/__tests__/inputDescriptors.test.ts +0 -162
  175. package/src/__tests__/jsonForm.chooseControl.test.ts +0 -86
  176. package/src/__tests__/jsonForm.evaluateWhen.test.ts +0 -59
  177. package/src/__tests__/jsonForm.inferSchema.test.ts +0 -25
  178. package/src/__tests__/jsonForm.pointer.test.ts +0 -149
  179. package/src/__tests__/jsonForm.tokens.test.ts +0 -84
  180. package/src/__tests__/layoutBlock.test.ts +0 -83
  181. package/src/__tests__/layoutLayout.test.ts +0 -169
  182. package/src/__tests__/localForageAdapter.test.ts +0 -155
  183. package/src/__tests__/majorApiTypes.test.ts +0 -18
  184. package/src/__tests__/markdown.test.ts +0 -1009
  185. package/src/__tests__/markdownIcons.test.ts +0 -104
  186. package/src/__tests__/markdownSanitize.test.ts +0 -81
  187. package/src/__tests__/markdownToDoc.test.ts +0 -957
  188. package/src/__tests__/matchFontFamily.test.ts +0 -114
  189. package/src/__tests__/materializeBlockLayers.test.ts +0 -167
  190. package/src/__tests__/materializePageSections.test.ts +0 -404
  191. package/src/__tests__/materializeTestUtils.ts +0 -17
  192. package/src/__tests__/mediaAnnotations.test.ts +0 -91
  193. package/src/__tests__/mediaSchedule.test.ts +0 -118
  194. package/src/__tests__/narrationAlign.test.ts +0 -150
  195. package/src/__tests__/narrationFeatures.test.ts +0 -71
  196. package/src/__tests__/narrationNuclei.test.ts +0 -45
  197. package/src/__tests__/narrationPacing.test.ts +0 -189
  198. package/src/__tests__/narrationScript.test.ts +0 -149
  199. package/src/__tests__/narrationSessionRepro.test.ts +0 -62
  200. package/src/__tests__/narrationSidecar.test.ts +0 -88
  201. package/src/__tests__/narrationSyllables.test.ts +0 -65
  202. package/src/__tests__/narrationTestSignals.ts +0 -167
  203. package/src/__tests__/narrationVad.test.ts +0 -66
  204. package/src/__tests__/pageStyle.test.ts +0 -258
  205. package/src/__tests__/pandocAttrParse.test.ts +0 -187
  206. package/src/__tests__/recommendTemplates.test.ts +0 -183
  207. package/src/__tests__/resolveDocTheme.test.ts +0 -69
  208. package/src/__tests__/rootExports.test.ts +0 -61
  209. package/src/__tests__/scopedContentContainer.test.ts +0 -109
  210. package/src/__tests__/seededRandom.test.ts +0 -142
  211. package/src/__tests__/shapeGeometry.test.ts +0 -138
  212. package/src/__tests__/shapeVocabulary.test.ts +0 -133
  213. package/src/__tests__/storage.test.ts +0 -60
  214. package/src/__tests__/structuredData.test.ts +0 -249
  215. package/src/__tests__/surface.test.ts +0 -68
  216. package/src/__tests__/templateAnnotationParse.test.ts +0 -173
  217. package/src/__tests__/templateInputs.test.ts +0 -69
  218. package/src/__tests__/templateMetadata.test.ts +0 -32
  219. package/src/__tests__/templates.test.ts +0 -710
  220. package/src/__tests__/themeCompile.test.ts +0 -225
  221. package/src/__tests__/themeValidator.test.ts +0 -131
  222. package/src/__tests__/timing.test.ts +0 -167
  223. package/src/__tests__/transformProvenance.test.ts +0 -160
  224. package/src/__tests__/transformV2.test.ts +0 -163
  225. package/src/__tests__/transitionNormalize.test.ts +0 -61
  226. package/src/__tests__/treeviewBattle.test.ts +0 -76
  227. package/src/__tests__/treeviewDetect.test.ts +0 -168
  228. package/src/__tests__/treeviewParse.test.ts +0 -118
  229. package/src/__tests__/treeviewPipeline.test.ts +0 -183
  230. package/src/__tests__/treeviewRender.test.ts +0 -91
  231. package/src/__tests__/validateDoc.test.ts +0 -260
  232. package/src/__tests__/versions.test.ts +0 -459
  233. package/src/doc/annotationBlocks.ts +0 -117
  234. package/src/doc/applyNarrationTiming.ts +0 -244
  235. package/src/doc/asciiDiagram/chars.ts +0 -210
  236. package/src/doc/asciiDiagram/detect.ts +0 -175
  237. package/src/doc/asciiDiagram/index.ts +0 -38
  238. package/src/doc/asciiDiagram/mapping.ts +0 -225
  239. package/src/doc/asciiDiagram/parse.ts +0 -1095
  240. package/src/doc/asciiDiagram/render.ts +0 -963
  241. package/src/doc/asciiDiagram/repair.ts +0 -400
  242. package/src/doc/asciiDiagram/types.ts +0 -65
  243. package/src/doc/asciiTimeline/detect.ts +0 -138
  244. package/src/doc/asciiTimeline/index.ts +0 -26
  245. package/src/doc/asciiTimeline/mapping.ts +0 -43
  246. package/src/doc/asciiTimeline/parse.ts +0 -870
  247. package/src/doc/asciiTimeline/render.ts +0 -427
  248. package/src/doc/asciiTimeline/types.ts +0 -73
  249. package/src/doc/asciiTimeline/wrappedFlow.ts +0 -427
  250. package/src/doc/audioMapping.ts +0 -442
  251. package/src/doc/customTemplatesFrontmatter.ts +0 -273
  252. package/src/doc/customThemesFrontmatter.ts +0 -95
  253. package/src/doc/docToMarkdown.ts +0 -284
  254. package/src/doc/index.ts +0 -161
  255. package/src/doc/markdownToDoc.ts +0 -1054
  256. package/src/doc/materializeBlockLayers.ts +0 -465
  257. package/src/doc/mediaAnnotations.ts +0 -120
  258. package/src/doc/page/PageSection.ts +0 -174
  259. package/src/doc/page/materializePageSection.ts +0 -616
  260. package/src/doc/page/resolvePageBlock.ts +0 -108
  261. package/src/doc/page/sectionExtractors.ts +0 -466
  262. package/src/doc/pageCss.ts +0 -628
  263. package/src/doc/resolveDocTheme.ts +0 -56
  264. package/src/doc/standaloneAnnotation.ts +0 -58
  265. package/src/doc/structuredData.ts +0 -301
  266. package/src/doc/templateInputs.ts +0 -424
  267. package/src/doc/templates/__tests__/customTemplate.test.ts +0 -170
  268. package/src/doc/templates/__tests__/customTemplateRegistry.test.ts +0 -124
  269. package/src/doc/templates/__tests__/listBlock.test.ts +0 -64
  270. package/src/doc/templates/accentImage.ts +0 -362
  271. package/src/doc/templates/captionUtils.ts +0 -56
  272. package/src/doc/templates/comparisonBar.ts +0 -173
  273. package/src/doc/templates/coverBlock.ts +0 -255
  274. package/src/doc/templates/customTemplate.ts +0 -51
  275. package/src/doc/templates/dataTable.ts +0 -102
  276. package/src/doc/templates/dateEvent.ts +0 -147
  277. package/src/doc/templates/definitionCard.ts +0 -140
  278. package/src/doc/templates/diagramBlock.ts +0 -403
  279. package/src/doc/templates/diagramLayout.ts +0 -159
  280. package/src/doc/templates/drawingBlock.ts +0 -325
  281. package/src/doc/templates/drawingLayout.ts +0 -539
  282. package/src/doc/templates/factCard.ts +0 -146
  283. package/src/doc/templates/fallbackBlock.ts +0 -123
  284. package/src/doc/templates/featureBlock.ts +0 -209
  285. package/src/doc/templates/fullBleedQuote.ts +0 -83
  286. package/src/doc/templates/imageWithCaption.ts +0 -186
  287. package/src/doc/templates/index.ts +0 -642
  288. package/src/doc/templates/inputDescriptors.ts +0 -451
  289. package/src/doc/templates/layoutBlock.ts +0 -43
  290. package/src/doc/templates/layoutLayout.ts +0 -243
  291. package/src/doc/templates/listBlock.ts +0 -207
  292. package/src/doc/templates/mapBlock.ts +0 -138
  293. package/src/doc/templates/metadata.ts +0 -130
  294. package/src/doc/templates/persistentLayers.ts +0 -526
  295. package/src/doc/templates/photoGrid.ts +0 -190
  296. package/src/doc/templates/pullQuote.ts +0 -141
  297. package/src/doc/templates/quoteBlock.ts +0 -141
  298. package/src/doc/templates/registry.ts +0 -88
  299. package/src/doc/templates/sectionHeader.ts +0 -120
  300. package/src/doc/templates/statHighlight.ts +0 -146
  301. package/src/doc/templates/templateNames.ts +0 -22
  302. package/src/doc/templates/timelineBlock.ts +0 -613
  303. package/src/doc/templates/titleBlock.ts +0 -150
  304. package/src/doc/templates/tokens/__tests__/resolveTokens.test.ts +0 -487
  305. package/src/doc/templates/tokens/resolveTokens.ts +0 -453
  306. package/src/doc/templates/treeBlock.ts +0 -131
  307. package/src/doc/templates/twoColumn.ts +0 -263
  308. package/src/doc/templates/videoPullQuote.ts +0 -142
  309. package/src/doc/templates/videoWithCaption.ts +0 -135
  310. package/src/doc/treeview/chars.ts +0 -72
  311. package/src/doc/treeview/detect.ts +0 -145
  312. package/src/doc/treeview/index.ts +0 -30
  313. package/src/doc/treeview/mapping.ts +0 -95
  314. package/src/doc/treeview/parse.ts +0 -201
  315. package/src/doc/treeview/render.ts +0 -41
  316. package/src/doc/treeview/types.ts +0 -43
  317. package/src/doc/utils/animationUtils.ts +0 -399
  318. package/src/doc/utils/applyRenderStyle.ts +0 -106
  319. package/src/doc/utils/diagramText.ts +0 -78
  320. package/src/doc/utils/imageTreatment.ts +0 -70
  321. package/src/doc/utils/nearest.ts +0 -59
  322. package/src/doc/utils/shapeGeometry.ts +0 -614
  323. package/src/doc/utils/themeUtils.ts +0 -243
  324. package/src/doc/validate.ts +0 -575
  325. package/src/generate/contentExtractor.ts +0 -921
  326. package/src/generate/index.ts +0 -18
  327. package/src/generate/templateMapper.ts +0 -177
  328. package/src/icons/iconData.ts +0 -31
  329. package/src/icons/index.ts +0 -26
  330. package/src/icons/inlineIconMarker.ts +0 -73
  331. package/src/icons/resolve.ts +0 -139
  332. package/src/imageEdit/ImageEditVersionManager.ts +0 -73
  333. package/src/imageEdit/export.ts +0 -410
  334. package/src/imageEdit/index.ts +0 -72
  335. package/src/imageEdit/persistence.ts +0 -113
  336. package/src/imageEdit/state.ts +0 -112
  337. package/src/imageEdit/versionPaths.ts +0 -44
  338. package/src/imageEdit/versions.ts +0 -152
  339. package/src/index.ts +0 -15
  340. package/src/internal/immutable.ts +0 -58
  341. package/src/jsonForm/chooseControl.ts +0 -88
  342. package/src/jsonForm/evaluateWhen.ts +0 -59
  343. package/src/jsonForm/index.ts +0 -32
  344. package/src/jsonForm/inferSchema.ts +0 -25
  345. package/src/jsonForm/pointer.ts +0 -154
  346. package/src/jsonForm/tokens.ts +0 -78
  347. package/src/jsonForm/types.ts +0 -167
  348. package/src/markdown/annotationCoercion.ts +0 -295
  349. package/src/markdown/attrTokens.ts +0 -243
  350. package/src/markdown/convert.ts +0 -1353
  351. package/src/markdown/htmlParse.ts +0 -212
  352. package/src/markdown/index.ts +0 -152
  353. package/src/markdown/parse.ts +0 -103
  354. package/src/markdown/sanitize.ts +0 -318
  355. package/src/markdown/stringify.ts +0 -207
  356. package/src/markdown/types.ts +0 -710
  357. package/src/markdown/utils.ts +0 -459
  358. package/src/narration/align.ts +0 -514
  359. package/src/narration/features.ts +0 -231
  360. package/src/narration/index.ts +0 -86
  361. package/src/narration/nuclei.ts +0 -118
  362. package/src/narration/pacing.ts +0 -211
  363. package/src/narration/script.ts +0 -239
  364. package/src/narration/session.ts +0 -100
  365. package/src/narration/sidecar.ts +0 -187
  366. package/src/narration/syllables.ts +0 -85
  367. package/src/narration/trace.ts +0 -50
  368. package/src/narration/types.ts +0 -299
  369. package/src/narration/vad.ts +0 -131
  370. package/src/ngeohash.d.ts +0 -11
  371. package/src/random/SeededRandom.ts +0 -127
  372. package/src/random/index.ts +0 -1
  373. package/src/recommend/index.ts +0 -16
  374. package/src/recommend/numberHighlight.ts +0 -25
  375. package/src/recommend/templates.ts +0 -393
  376. package/src/schemas/BlockTemplates.ts +0 -1046
  377. package/src/schemas/CustomTemplates.ts +0 -126
  378. package/src/schemas/Doc.ts +0 -1073
  379. package/src/schemas/ImageEditDoc.ts +0 -102
  380. package/src/schemas/LayoutStrategy.ts +0 -178
  381. package/src/schemas/Media.ts +0 -179
  382. package/src/schemas/MediaProvider.ts +0 -70
  383. package/src/schemas/PageStyle.ts +0 -213
  384. package/src/schemas/Theme.ts +0 -413
  385. package/src/schemas/Transitions.ts +0 -224
  386. package/src/schemas/Types.ts +0 -15
  387. package/src/schemas/Viewport.ts +0 -91
  388. package/src/schemas/colorUtils.ts +0 -219
  389. package/src/schemas/fontStacks.ts +0 -339
  390. package/src/schemas/index.ts +0 -31
  391. package/src/schemas/pageStyleDefaults.ts +0 -204
  392. package/src/schemas/themeCompile.ts +0 -244
  393. package/src/schemas/themeConstants.ts +0 -2
  394. package/src/schemas/themeLibrary.ts +0 -60
  395. package/src/schemas/themeValidator.ts +0 -609
  396. package/src/schemas/themes/bold.json +0 -77
  397. package/src/schemas/themes/cinematic.json +0 -143
  398. package/src/schemas/themes/documentary.json +0 -125
  399. package/src/schemas/themes/gezellig.json +0 -123
  400. package/src/schemas/themes/index.ts +0 -53
  401. package/src/schemas/themes/magazine.json +0 -126
  402. package/src/schemas/themes/minimalist.json +0 -74
  403. package/src/schemas/themes/morning-light.json +0 -117
  404. package/src/schemas/themes/standard-dark.json +0 -109
  405. package/src/schemas/themes/standard.json +0 -98
  406. package/src/schemas/themes/tech-dark.json +0 -143
  407. package/src/schemas/themes/warm-earth.json +0 -74
  408. package/src/spatial/Geohash.ts +0 -196
  409. package/src/spatial/Haversine.ts +0 -59
  410. package/src/spatial/index.ts +0 -2
  411. package/src/storage/ContentContainer.ts +0 -220
  412. package/src/storage/LocalForageAdapter.ts +0 -103
  413. package/src/storage/LocalStorageAdapter.ts +0 -57
  414. package/src/storage/MediaProviderFromContainer.ts +0 -92
  415. package/src/storage/MemoryStorageAdapter.ts +0 -37
  416. package/src/storage/ScopedContentContainer.ts +0 -143
  417. package/src/storage/Storage.ts +0 -28
  418. package/src/storage/index.ts +0 -9
  419. package/src/timing/index.ts +0 -17
  420. package/src/timing/narrationTiming.ts +0 -220
  421. package/src/timing/readingTime.ts +0 -94
  422. package/src/transform/applyTransform.ts +0 -122
  423. package/src/transform/blockAnalyzer.ts +0 -181
  424. package/src/transform/index.ts +0 -23
  425. package/src/transform/registry.ts +0 -217
  426. package/src/transform/styles/dataDriven.ts +0 -18
  427. package/src/transform/styles/documentary.ts +0 -18
  428. package/src/transform/styles/index.ts +0 -5
  429. package/src/transform/styles/magazine.ts +0 -19
  430. package/src/transform/styles/minimal.ts +0 -18
  431. package/src/transform/styles/narrative.ts +0 -19
  432. package/src/transform/templateSelector.ts +0 -389
  433. package/src/transform/timingAllocator.ts +0 -257
  434. package/src/transform/types.ts +0 -158
  435. package/src/versions/DocumentVersionManager.ts +0 -70
  436. package/src/versions/gitignore.ts +0 -34
  437. package/src/versions/index.ts +0 -56
  438. package/src/versions/operations.ts +0 -144
  439. package/src/versions/paths.ts +0 -51
  440. package/src/versions/snapshotStore.ts +0 -202
  441. package/src/versions/timestamp.ts +0 -48
  442. package/src/versions/types.ts +0 -93
@@ -1,17 +0,0 @@
1
- import type { Layer } from '../schemas/Doc.js';
2
- import type { DocBlock } from '../schemas/BlockTemplates.js';
3
- import {
4
- materializeBlockLayers,
5
- type MaterializeBlockLayersOptions,
6
- } from '../doc/materializeBlockLayers.js';
7
-
8
- /** Materialize isolated test fixtures without inheriting theme atmosphere. */
9
- export function materializeLayers(
10
- block: DocBlock,
11
- options: MaterializeBlockLayersOptions = {},
12
- ): Layer[] {
13
- return materializeBlockLayers(block, {
14
- persistentLayers: false,
15
- ...options,
16
- }).layers;
17
- }
@@ -1,91 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { parseMarkdown } from '../markdown/parse.js';
3
- import { markdownToDoc } from '../doc/markdownToDoc.js';
4
- import { resolveMediaSchedule } from '../schemas/Media.js';
5
-
6
- function toDoc(md: string) {
7
- return markdownToDoc(parseMarkdown(md), { articleId: 't' });
8
- }
9
-
10
- describe('markdownToDoc media annotations', () => {
11
- it('lifts a block-level audio annotation into block.media', () => {
12
- const doc = toDoc(
13
- '# One {duration=10}\n\nIntro.\n\n{[audio src=narration.mp3 startAt=5 clipEnd=8 spillover=true]}\n',
14
- );
15
- const block = doc.blocks[0];
16
- expect(block.media).toHaveLength(1);
17
- expect(block.media![0]).toMatchObject({
18
- src: 'narration.mp3',
19
- kind: 'audio',
20
- startAt: 5,
21
- clipEnd: 8,
22
- spillover: true,
23
- anchor: 'block',
24
- });
25
- // The annotation paragraph is removed from the visible contents.
26
- expect(JSON.stringify(block.contents)).not.toContain('narration.mp3');
27
- });
28
-
29
- it('routes anchor=document annotations to doc.documentMedia', () => {
30
- const doc = toDoc('{[audio src=voice.mp3 anchor=document]}\n\n# First\n\nBody.\n');
31
- expect(doc.documentMedia).toHaveLength(1);
32
- expect(doc.documentMedia![0]).toMatchObject({ src: 'voice.mp3', anchor: 'document' });
33
- // It is not also attached to the preamble block's media.
34
- expect(doc.blocks[0].media).toBeUndefined();
35
- });
36
-
37
- it('recognizes a video annotation as kind video', () => {
38
- const doc = toDoc('# B {duration=20}\n\n{[video src=clip.mp4 clipStart=2 clipEnd=8]}\n');
39
- expect(doc.blocks[0].media![0]).toMatchObject({
40
- src: 'clip.mp4',
41
- kind: 'video',
42
- clipStart: 2,
43
- clipEnd: 8,
44
- });
45
- });
46
-
47
- it('accepts startTime as an alias of startAt (startAt wins when both given)', () => {
48
- const alias = toDoc('# B {duration=20}\n\n{[audio src=a.mp3 startTime=5]}\n');
49
- expect(alias.blocks[0].media![0].startAt).toBe(5);
50
-
51
- const both = toDoc('# B {duration=20}\n\n{[audio src=a.mp3 startAt=3 startTime=5]}\n');
52
- expect(both.blocks[0].media![0].startAt).toBe(3);
53
- });
54
-
55
- it('media annotation text does not inflate block duration', () => {
56
- // No explicit duration; body is only the annotation → default (5s), not reading-time of the annotation text.
57
- const doc = toDoc('# B\n\n{[audio src=a.mp3]}\n');
58
- expect(doc.blocks[0].duration).toBe(5);
59
- });
60
-
61
- it('feeds resolveMediaSchedule end to end', () => {
62
- const doc = toDoc(
63
- '{[audio src=voice.mp3 anchor=document]}\n\n# One {duration=30}\n\n# Two {duration=20}\n\n{[audio src=a.mp3 startAt=5 clipEnd=8]}\n',
64
- );
65
- const sched = resolveMediaSchedule(doc);
66
- const doc1 = sched.find((c) => c.anchor === 'document')!;
67
- const block = sched.find((c) => c.anchor === 'block')!;
68
- expect(doc1.absoluteStart).toBe(0);
69
- expect(doc1.absoluteEnd).toBe(50); // spans both blocks
70
- expect(block.absoluteStart).toBe(35); // block Two at 30 + startAt 5
71
- });
72
- });
73
-
74
- describe('media annotations vs standalone template blocks', () => {
75
- it('extracts media annotations as clips, not standalone blocks (media names win)', () => {
76
- const doc = toDoc('# One\n\nIntro.\n\n{[audio src=narration.mp3]}\n\n{[video src=clip.mp4]}\n');
77
- const block = doc.blocks[0];
78
- expect(block.media?.map((m) => m.kind)).toEqual(['audio', 'video']);
79
- // No sibling standalone blocks were produced for the media names.
80
- expect(doc.blocks).toHaveLength(1);
81
- expect(doc.blocks.some((b) => b.standaloneAnnotation)).toBe(false);
82
- });
83
-
84
- it('mixes a media clip and a standalone template block in one section', () => {
85
- const doc = toDoc('# One\n\n{[audio src=n.mp3]}\n\n{[quote]}\n\nquoted\n');
86
- expect(doc.blocks[0].media?.map((m) => m.kind)).toEqual(['audio']);
87
- const q = doc.blocks[1];
88
- expect(q.template).toBe('quote');
89
- expect(q.standaloneAnnotation).toBe(true);
90
- });
91
- });
@@ -1,118 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { resolveMediaSchedule, getDocPlaybackDuration } from '../schemas/Media.js';
3
- import type { Doc, Block } from '../schemas/Doc.js';
4
-
5
- function block(partial: Partial<Block>): Block {
6
- return {
7
- id: partial.id ?? 'b',
8
- startTime: partial.startTime ?? 0,
9
- duration: partial.duration ?? 10,
10
- audioSegment: 0,
11
- ...partial,
12
- };
13
- }
14
-
15
- function doc(blocks: Block[], extra?: Partial<Doc>): Doc {
16
- const lastEnd = blocks.reduce((m, b) => Math.max(m, b.startTime + b.duration), 0);
17
- return { articleId: 't', duration: lastEnd, blocks, audio: { segments: [] }, ...extra };
18
- }
19
-
20
- describe('resolveMediaSchedule', () => {
21
- it('offsets a block clip by startAt relative to the block start', () => {
22
- // Block #2 starts at 30s; a clip with startAt=5 plays at 35s.
23
- const d = doc([
24
- block({ id: 'one', startTime: 0, duration: 30 }),
25
- block({
26
- id: 'two',
27
- startTime: 30,
28
- duration: 20,
29
- media: [{ id: 'm1', src: 'a.mp3', kind: 'audio', startAt: 5, clipEnd: 8, anchor: 'block' }],
30
- }),
31
- ]);
32
- const [clip] = resolveMediaSchedule(d);
33
- expect(clip.absoluteStart).toBe(35);
34
- expect(clip.absoluteEnd).toBe(43); // 35 + (clipEnd 8 - clipStart 0)
35
- expect(clip.sourceIn).toBe(0);
36
- expect(clip.blockId).toBe('two');
37
- });
38
-
39
- it('clamps a non-spillover clip to the block end', () => {
40
- const d = doc([
41
- block({
42
- id: 'b',
43
- startTime: 0,
44
- duration: 10,
45
- media: [
46
- { id: 'm', src: 'a.mp3', kind: 'audio', startAt: 2, clipEnd: 100, anchor: 'block' },
47
- ],
48
- }),
49
- ]);
50
- const [clip] = resolveMediaSchedule(d);
51
- expect(clip.absoluteEnd).toBe(10); // clamped to block end, not 2 + 100
52
- });
53
-
54
- it('lets a spillover clip run past the block end', () => {
55
- const d = doc([
56
- block({
57
- id: 'b',
58
- startTime: 0,
59
- duration: 10,
60
- media: [
61
- {
62
- id: 'm',
63
- src: 'a.mp3',
64
- kind: 'audio',
65
- startAt: 2,
66
- clipEnd: 100,
67
- spillover: true,
68
- anchor: 'block',
69
- },
70
- ],
71
- }),
72
- block({ id: 'b2', startTime: 10, duration: 10 }),
73
- ]);
74
- const [clip] = resolveMediaSchedule(d);
75
- expect(clip.absoluteEnd).toBe(102); // 2 + 100, past the block end
76
- });
77
-
78
- it('spans the whole document for an anchor=document clip', () => {
79
- const d = doc([block({ id: 'b', startTime: 0, duration: 40 })], {
80
- documentMedia: [
81
- { id: 'narr', src: 'voice.mp3', kind: 'audio', startAt: 0, anchor: 'document' },
82
- ],
83
- });
84
- const [clip] = resolveMediaSchedule(d);
85
- expect(clip.absoluteStart).toBe(0);
86
- expect(clip.absoluteEnd).toBe(40);
87
- expect(clip.anchor).toBe('document');
88
- });
89
- });
90
-
91
- describe('getDocPlaybackDuration', () => {
92
- it('extends total duration to include media spillover', () => {
93
- const d = doc([
94
- block({
95
- id: 'b',
96
- startTime: 0,
97
- duration: 10,
98
- media: [
99
- {
100
- id: 'm',
101
- src: 'a.mp3',
102
- kind: 'audio',
103
- startAt: 0,
104
- clipEnd: 25,
105
- spillover: true,
106
- anchor: 'block',
107
- },
108
- ],
109
- }),
110
- ]);
111
- expect(getDocPlaybackDuration(d)).toBe(25);
112
- });
113
-
114
- it('falls back to the block timeline when there is no media', () => {
115
- const d = doc([block({ id: 'b', startTime: 0, duration: 12 })]);
116
- expect(getDocPlaybackDuration(d)).toBe(12);
117
- });
118
- });
@@ -1,150 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { alignNarration } from '../narration/align';
3
- import type { NarrationAlignment, NarrationScript } from '../narration/types';
4
- import type { NarrationTrace } from '../narration/trace';
5
- import { scriptFromMarkdown, takeFromScript, type SyntheticTake } from './narrationTestSignals';
6
-
7
- const SR = 48000;
8
-
9
- const MD = `# The River Walk
10
-
11
- Every morning we follow the path along the river and count the boats heading out.
12
- The bakery opens early and the smell of bread carries across the water to the far bank.
13
-
14
- By noon the light changes and the whole town seems to slow down for an hour or two.
15
-
16
- ## The Market
17
-
18
- Stalls line the square with fruit and flowers and a man who fixes watches while you wait.
19
- We always stop for coffee before carrying everything home up the long hill past the church.
20
-
21
- ## Evening
22
-
23
- The lamps come on one street at a time and the river turns the color of old coins.
24
- `;
25
-
26
- const script = scriptFromMarkdown(MD);
27
-
28
- /** Burst onsets are at word start; envelope peaks ~half a burst later. */
29
- const PEAK_OFFSET = 0.08;
30
-
31
- function timingErrors(alignment: NarrationAlignment, take: SyntheticTake): number[] {
32
- return alignment.words.map((w, k) => Math.abs(w.tSec - (take.trueWordTimes[k] + PEAK_OFFSET)));
33
- }
34
-
35
- function median(values: number[]): number {
36
- const sorted = [...values].sort((a, b) => a - b);
37
- return sorted[Math.floor(sorted.length / 2)];
38
- }
39
-
40
- function p95(values: number[]): number {
41
- const sorted = [...values].sort((a, b) => a - b);
42
- return sorted[Math.min(sorted.length - 1, Math.floor(sorted.length * 0.95))];
43
- }
44
-
45
- /** A live trace with a deterministic ±wobble, as a recorded prompter would produce. */
46
- function traceFromTake(
47
- take: SyntheticTake,
48
- scriptRef: NarrationScript,
49
- wobbleSec: number,
50
- ): NarrationTrace {
51
- const samples: { tMs: number; wordPos: number }[] = [];
52
- for (let t = 0; t <= take.durationSec; t += 0.25) {
53
- const shifted = t + wobbleSec * Math.sin(t * 1.3);
54
- // wordPos convention: floor(wordPos) is the ACTIVE token — the word
55
- // currently being spoken — not the count of words started.
56
- let started = 0;
57
- while (started < take.trueWordTimes.length && take.trueWordTimes[started] <= shifted) started++;
58
- const wordPos = Math.max(0, started - 1);
59
- samples.push({ tMs: t * 1000, wordPos: Math.min(wordPos, scriptRef.tokens.length) });
60
- }
61
- return { samples };
62
- }
63
-
64
- describe('alignNarration', () => {
65
- const take = takeFromScript(script, 150, SR, 21);
66
-
67
- it('clean take without a trace: median error < 0.25 s, p95 < 1 s', () => {
68
- const alignment = alignNarration({ pcm: take.pcm, sampleRate: SR, script });
69
- expect(alignment.words.length).toBe(script.tokens.length);
70
- const errors = timingErrors(alignment, take);
71
- expect(median(errors)).toBeLessThan(0.25);
72
- expect(p95(errors)).toBeLessThan(1.0);
73
- expect(alignment.detectedSyllables).toBeGreaterThan(script.totalSyllables * 0.7);
74
- });
75
-
76
- it('clean take with a wobbly trace: median error < 0.15 s, p95 < 0.5 s', () => {
77
- const trace = traceFromTake(take, script, 0.8);
78
- const alignment = alignNarration({ pcm: take.pcm, sampleRate: SR, script, trace });
79
- const errors = timingErrors(alignment, take);
80
- expect(median(errors)).toBeLessThan(0.15);
81
- expect(p95(errors)).toBeLessThan(0.5);
82
- });
83
-
84
- it('survives filler insertions and a dropped syllable', () => {
85
- const messy = takeFromScript(script, 150, SR, 22, {
86
- extraOnsets: [4.1, 12.4],
87
- dropTokenSyllable: [8 * 1000 + 0],
88
- });
89
- const trace = traceFromTake(messy, script, 0.5);
90
- const alignment = alignNarration({ pcm: messy.pcm, sampleRate: SR, script, trace });
91
- const errors = timingErrors(alignment, messy);
92
- expect(median(errors)).toBeLessThan(0.25);
93
- });
94
-
95
- it('block ranges are contiguous, monotonic, and near the true block starts', () => {
96
- const trace = traceFromTake(take, script, 0.4);
97
- const alignment = alignNarration({ pcm: take.pcm, sampleRate: SR, script, trace });
98
- const blocks = alignment.blocks;
99
- expect(blocks.length).toBe(script.blocks.length);
100
- expect(blocks[0].startSec).toBe(0);
101
- for (let b = 0; b < blocks.length - 1; b++) {
102
- expect(blocks[b].endSec).toBeCloseTo(blocks[b + 1].startSec, 9);
103
- expect(blocks[b].endSec).toBeGreaterThanOrEqual(blocks[b].startSec);
104
- }
105
- const last = blocks[blocks.length - 1];
106
- expect(last.endSec).toBeGreaterThan(last.startSec);
107
- expect(last.endSec).toBeLessThanOrEqual(take.durationSec + 1e-6);
108
- for (let b = 1; b < blocks.length; b++) {
109
- expect(Math.abs(blocks[b].startSec - take.trueBlockStarts[b])).toBeLessThan(0.7);
110
- }
111
- });
112
-
113
- it('word times are monotonic', () => {
114
- const alignment = alignNarration({ pcm: take.pcm, sampleRate: SR, script });
115
- for (let i = 1; i < alignment.words.length; i++) {
116
- expect(alignment.words[i].tSec).toBeGreaterThanOrEqual(alignment.words[i - 1].tSec);
117
- }
118
- });
119
-
120
- it('falls back proportionally when nothing is detected', () => {
121
- const quiet = new Float32Array(SR * 5);
122
- const alignment = alignNarration({ pcm: quiet, sampleRate: SR, script });
123
- expect(alignment.detectedSyllables).toBe(0);
124
- expect(alignment.words.length).toBe(script.tokens.length);
125
- expect(alignment.words.every((w) => w.interpolated)).toBe(true);
126
- expect(alignment.blocks.length).toBe(script.blocks.length);
127
- });
128
-
129
- it('handles an empty script', () => {
130
- const alignment = alignNarration({
131
- pcm: new Float32Array(SR),
132
- sampleRate: SR,
133
- script: {
134
- sourceText: '',
135
- tokens: [],
136
- blocks: [],
137
- totalSyllables: 0,
138
- cumulativeSyllables: [0],
139
- },
140
- });
141
- expect(alignment.words).toEqual([]);
142
- expect(alignment.blocks).toEqual([]);
143
- });
144
-
145
- it('is deterministic', () => {
146
- const a = alignNarration({ pcm: take.pcm, sampleRate: SR, script });
147
- const b = alignNarration({ pcm: take.pcm, sampleRate: SR, script });
148
- expect(a).toEqual(b);
149
- });
150
- });
@@ -1,71 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { createFeatureState, extractFrameFeatures, featureStep } from '../narration/features';
3
- import { tone, noise } from './narrationTestSignals';
4
-
5
- const RATES = [48000, 44100];
6
-
7
- describe('extractFrameFeatures', () => {
8
- it.each(RATES)('band energy passes speech band and rejects hum/hiss @ %d Hz', (sr) => {
9
- const inBand = extractFrameFeatures(tone(1000, 0.5, sr), sr);
10
- const hum = extractFrameFeatures(tone(60, 0.5, sr), sr);
11
- const hiss = extractFrameFeatures(tone(8000, 0.5, sr), sr);
12
-
13
- // Skip filter warm-up frames; compare steady-state medians.
14
- const median = (frames: { bandEnergy: number }[]) => {
15
- const vals = frames
16
- .slice(5)
17
- .map((f) => f.bandEnergy)
18
- .sort((a, b) => a - b);
19
- return vals[Math.floor(vals.length / 2)];
20
- };
21
- expect(median(inBand)).toBeGreaterThan(median(hum) * 10);
22
- expect(median(inBand)).toBeGreaterThan(median(hiss) * 10);
23
- });
24
-
25
- it.each(RATES)('zcr orders noise above tone @ %d Hz', (sr) => {
26
- const noisy = extractFrameFeatures(noise(0.3, sr, 0.2, 7), sr);
27
- const tonal = extractFrameFeatures(tone(300, 0.3, sr), sr);
28
- expect(noisy[5].zcr).toBeGreaterThan(tonal[5].zcr);
29
- });
30
-
31
- it('rms measures a known-amplitude tone (~amp/√2)', () => {
32
- const sr = 48000;
33
- const frames = extractFrameFeatures(tone(440, 0.3, sr, 0.5), sr);
34
- expect(frames[5].rms).toBeGreaterThan(0.3);
35
- expect(frames[5].rms).toBeLessThan(0.4);
36
- });
37
-
38
- it.each(RATES)('streaming chunks produce the same frames as one batch @ %d Hz', (sr) => {
39
- const pcm = noise(0.5, sr, 0.1, 42);
40
- const batch = extractFrameFeatures(pcm, sr);
41
-
42
- let state = createFeatureState(sr);
43
- const streamed: ReturnType<typeof extractFrameFeatures> = [];
44
- // Uneven chunk sizes exercise the carry logic.
45
- const chunks = [100, 1024, 333, 4096, 57];
46
- let offset = 0;
47
- let pick = 0;
48
- while (offset < pcm.length) {
49
- const size = Math.min(chunks[pick++ % chunks.length], pcm.length - offset);
50
- const step = featureStep(state, pcm.subarray(offset, offset + size));
51
- state = step.state;
52
- streamed.push(...step.frames);
53
- offset += size;
54
- }
55
-
56
- expect(streamed.length).toBe(batch.length);
57
- for (let i = 0; i < batch.length; i++) {
58
- expect(streamed[i].tSec).toBeCloseTo(batch[i].tSec, 9);
59
- expect(streamed[i].rms).toBeCloseTo(batch[i].rms, 9);
60
- expect(streamed[i].bandEnergy).toBeCloseTo(batch[i].bandEnergy, 9);
61
- expect(streamed[i].zcr).toBeCloseTo(batch[i].zcr, 9);
62
- }
63
- });
64
-
65
- it('frame centers advance by the hop', () => {
66
- const sr = 48000;
67
- const frames = extractFrameFeatures(noise(0.3, sr, 0.1, 3), sr, { hopSec: 0.02 });
68
- expect(frames.length).toBeGreaterThan(5);
69
- expect(frames[1].tSec - frames[0].tSec).toBeCloseTo(0.02, 3);
70
- });
71
- });
@@ -1,45 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { extractFrameFeatures } from '../narration/features';
3
- import { createVadState, vadStep } from '../narration/vad';
4
- import { detectSyllableOnsets } from '../narration/nuclei';
5
- import { syllableTrain } from './narrationTestSignals';
6
-
7
- const SR = 48000;
8
-
9
- function detect(pcm: Float32Array): number[] {
10
- const frames = extractFrameFeatures(pcm, SR, { hopSec: 0.01 });
11
- let vad = createVadState();
12
- const flags = frames.map((f) => {
13
- vad = vadStep(vad, f);
14
- return vad.speaking;
15
- });
16
- return detectSyllableOnsets(frames, flags);
17
- }
18
-
19
- describe('detectSyllableOnsets', () => {
20
- it('finds each burst in a syllable train (±1 count, ±60 ms timing)', () => {
21
- const truth = [0.6, 0.9, 1.2, 1.5, 2.2, 2.5, 2.8, 3.6, 3.9, 4.2, 4.5, 4.8];
22
- const onsets = detect(syllableTrain(truth, SR, { seed: 11 }));
23
- expect(Math.abs(onsets.length - truth.length)).toBeLessThanOrEqual(1);
24
-
25
- // Each detected onset lies near some true burst (peak ≈ burst center,
26
- // so allow the 80 ms half-burst plus 60 ms tolerance).
27
- for (const t of onsets) {
28
- const nearest = Math.min(...truth.map((x) => Math.abs(t - (x + 0.08))));
29
- expect(nearest).toBeLessThanOrEqual(0.06 + 0.04);
30
- }
31
- });
32
-
33
- it('suppresses double-bursts closer than the minimum spacing', () => {
34
- // Two bursts 60 ms apart → their envelopes merge / spacing gate fires:
35
- // at most one onset in that neighborhood.
36
- const onsets = detect(syllableTrain([1.0, 1.06, 2.0], SR, { seed: 12 }));
37
- const nearOne = onsets.filter((t) => t > 0.8 && t < 1.5);
38
- expect(nearOne.length).toBeLessThanOrEqual(1);
39
- });
40
-
41
- it('reports nothing on silence/noise', () => {
42
- const onsets = detect(syllableTrain([], SR, { totalSec: 2, seed: 13 }));
43
- expect(onsets.length).toBe(0);
44
- });
45
- });
@@ -1,189 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import {
3
- createPacingState,
4
- pacingStep,
5
- reanchorPacing,
6
- type PacingState,
7
- type PacingTick,
8
- } from '../narration/pacing';
9
- import { expectedSyllablesAt } from '../narration/script';
10
- import { DEFAULT_PACING_CONFIG, type PacingConfig } from '../narration/types';
11
- import { scriptFromMarkdown } from './narrationTestSignals';
12
-
13
- const MD = `# Cadence
14
-
15
- The quick brown fox jumps over the lazy dog while we keep a steady pace for this test.
16
- It continues with more plain words that carry roughly one or two syllables each one.
17
-
18
- Another paragraph starts after a pause and keeps going with the same easy words for a while.
19
-
20
- ## Second Section
21
-
22
- More text lives here so the script is long enough that the prompter never runs off the end.
23
- The prompter should track the reader through all of it without drifting away from the voice.
24
- `;
25
-
26
- const script = scriptFromMarkdown(MD);
27
- const BASE_WPS = DEFAULT_PACING_CONFIG.baseWpm / 60;
28
- const DT = 0.02;
29
-
30
- /** Drive the controller with speaking ticks emitting onsets at `sylRate` (syl/s). */
31
- function drive(
32
- state: PacingState,
33
- fromSec: number,
34
- durSec: number,
35
- opts: { speaking: boolean; sylRate?: number },
36
- config?: Partial<PacingConfig>,
37
- ): PacingState {
38
- let acc = 0;
39
- let next = state;
40
- for (let t = fromSec; t < fromSec + durSec; t += DT) {
41
- let onsets = 0;
42
- if (opts.speaking && opts.sylRate) {
43
- acc += opts.sylRate * DT;
44
- while (acc >= 1) {
45
- onsets += 1;
46
- acc -= 1;
47
- }
48
- }
49
- const tick: PacingTick = { tSec: t, speaking: opts.speaking, onsets };
50
- next = pacingStep(next, tick, script, config);
51
- }
52
- return next;
53
- }
54
-
55
- const meanSylPerWord = script.totalSyllables / script.tokens.length;
56
-
57
- describe('pacingStep', () => {
58
- it('halts within 250 ms of silence', () => {
59
- let state = drive(createPacingState(), 0, 3, {
60
- speaking: true,
61
- sylRate: BASE_WPS * meanSylPerWord,
62
- });
63
- expect(state.velocityWps).toBeGreaterThan(BASE_WPS * 0.5);
64
- const velocityAtSilence = state.velocityWps;
65
- state = drive(state, 3, 0.25, { speaking: false });
66
- expect(state.velocityWps).toBeLessThan(velocityAtSilence * 0.05);
67
- state = drive(state, 3.25, 0.3, { speaking: false });
68
- expect(state.velocityWps).toBe(0);
69
- expect(state.halted).toBe(true);
70
- });
71
-
72
- it('tracks the reader rate monotonically', () => {
73
- // Faster reading → faster prompter is the whole point of voice pacing.
74
- let prevSlope = 0;
75
- for (const mult of [0.6, 1.0, 1.8]) {
76
- const sylRate = mult * BASE_WPS * meanSylPerWord;
77
- const state = drive(createPacingState(), 0, 8, { speaking: true, sylRate });
78
- const slope = state.wordPos / 8;
79
- expect(slope).toBeGreaterThan(prevSlope);
80
- prevSlope = slope;
81
- }
82
- });
83
-
84
- it('stays word-aligned when the reader matches the set pace', () => {
85
- const sylRate = BASE_WPS * meanSylPerWord;
86
- const state = drive(createPacingState(), 0, 8, { speaking: true, sylRate });
87
- const drift =
88
- expectedSyllablesAt(script, state.wordPos) - (state.anchorExpectedSyl + state.cumDetectedSyl);
89
- expect(Math.abs(drift)).toBeLessThan(6);
90
- });
91
-
92
- it('the WPM setting is a live lever: higher baseWpm → faster cruise', () => {
93
- // Same modest voice rate, different set pace. Because the set pace is a
94
- // real feedforward term (not cancelled), doubling baseWpm must visibly
95
- // speed the prompter up — the bug the user reported.
96
- const sylRate = 0.8 * BASE_WPS * meanSylPerWord;
97
- const slow = drive(createPacingState(), 0, 8, { speaking: true, sylRate });
98
- const fast = drive(createPacingState(), 0, 8, { speaking: true, sylRate }, { baseWpm: 260 });
99
- expect(fast.wordPos).toBeGreaterThan(slow.wordPos * 1.3);
100
- });
101
-
102
- it('never crawls below the set pace floor while speaking', () => {
103
- // A detector that reports almost nothing must still cruise near the set
104
- // pace (blend keeps baseWps in play) — so the prompter can't stall out
105
- // when syllable detection under-counts.
106
- const state = drive(createPacingState(), 0, 6, { speaking: true, sylRate: 0.05 });
107
- const slope = state.wordPos / 6;
108
- expect(slope).toBeGreaterThan(BASE_WPS * DEFAULT_PACING_CONFIG.minRateMult * 0.8);
109
- });
110
-
111
- it('clamps velocity at the cruise-band bounds', () => {
112
- // Absurdly fast reader: velocity respects the maxRateMult × set-pace
113
- // clamp (plus the bounded PI correction).
114
- let state = createPacingState();
115
- let acc = 0;
116
- const sylRate = 6 * BASE_WPS * meanSylPerWord;
117
- for (let t = 0; t < 5; t += DT) {
118
- acc += sylRate * DT;
119
- let onsets = 0;
120
- while (acc >= 1) {
121
- onsets += 1;
122
- acc -= 1;
123
- }
124
- state = pacingStep(state, { tSec: t, speaking: true, onsets }, script);
125
- expect(state.velocityWps).toBeLessThanOrEqual(
126
- BASE_WPS * DEFAULT_PACING_CONFIG.maxRateMult * (1 + DEFAULT_PACING_CONFIG.maxCorrection) +
127
- 1e-9,
128
- );
129
- }
130
- });
131
-
132
- it('silence halts the prompter and never drags it backward', () => {
133
- // Park mid-script ahead of the detected syllables, then go silent. An
134
- // under-counting detector makes err > 0 ("prompter ahead"); the
135
- // forward-only correction must NOT slow or rewind — the prompter simply
136
- // holds position (velocity halts) rather than snapping back.
137
- const startPos = Math.min(20, script.tokens.length - 1);
138
- const anchored = reanchorPacing(createPacingState(), startPos, script);
139
- const state = drive({ ...anchored, wordPos: startPos + 3 }, 0, 2.5, { speaking: false });
140
- expect(state.velocityWps).toBe(0);
141
- expect(state.wordPos).toBeGreaterThanOrEqual(startPos + 3 - 1e-9);
142
- });
143
-
144
- it('catches up forward when the reader is ahead of the prompter', () => {
145
- // Feed a strong syllable stream while the prompter starts behind: the
146
- // forward-only boost (and, past the threshold, a forward resync) must
147
- // pull the highlight forward to meet the voice.
148
- const state = drive(createPacingState(), 0, 6, {
149
- speaking: true,
150
- sylRate: 2 * BASE_WPS * meanSylPerWord,
151
- });
152
- const detected = state.cumDetectedSyl;
153
- // The prompter should have advanced to roughly where those syllables land.
154
- const expectedPos = expectedSyllablesAt(script, state.wordPos);
155
- expect(expectedPos).toBeGreaterThan(detected * 0.6);
156
- });
157
-
158
- it('reanchorPacing resets error tracking but keeps the learned rate', () => {
159
- const state = drive(createPacingState(), 0, 4, {
160
- speaking: true,
161
- sylRate: BASE_WPS * meanSylPerWord,
162
- });
163
- const ema = state.emaSylPerSec;
164
- expect(ema).toBeGreaterThan(0);
165
- const anchored = reanchorPacing(state, 5, script);
166
- expect(anchored.wordPos).toBe(5);
167
- expect(anchored.cumDetectedSyl).toBe(0);
168
- expect(anchored.errIntegral).toBe(0);
169
- expect(anchored.emaSylPerSec).toBe(ema);
170
- });
171
-
172
- it('is pure: identical tick sequences yield identical states', () => {
173
- const run = () => {
174
- let state = createPacingState();
175
- state = drive(state, 0, 2, { speaking: true, sylRate: 4 });
176
- state = drive(state, 2, 1, { speaking: false });
177
- state = drive(state, 3, 2, { speaking: true, sylRate: 6 });
178
- return state;
179
- };
180
- expect(JSON.stringify(run())).toBe(JSON.stringify(run()));
181
- });
182
-
183
- it('never leaves the script bounds', () => {
184
- let state = createPacingState();
185
- state = drive(state, 0, 60, { speaking: true, sylRate: 40 });
186
- expect(state.wordPos).toBeLessThanOrEqual(script.tokens.length);
187
- expect(state.wordPos).toBeGreaterThanOrEqual(0);
188
- });
189
- });