@bendyline/squisq 1.2.2 → 1.4.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 (427) hide show
  1. package/dist/{Doc-BfBhiCNr.d.ts → Doc-BeSeH2iX.d.ts} +141 -33
  2. package/dist/ImageEditDoc-BTIvw0oq.d.ts +84 -0
  3. package/dist/__tests__/colorUtils.test.d.ts +2 -0
  4. package/dist/__tests__/colorUtils.test.d.ts.map +1 -0
  5. package/dist/__tests__/colorUtils.test.js +94 -0
  6. package/dist/__tests__/colorUtils.test.js.map +1 -0
  7. package/dist/__tests__/getLayers.test.js +31 -7
  8. package/dist/__tests__/getLayers.test.js.map +1 -1
  9. package/dist/__tests__/iconResolve.test.d.ts +2 -0
  10. package/dist/__tests__/iconResolve.test.d.ts.map +1 -0
  11. package/dist/__tests__/iconResolve.test.js +99 -0
  12. package/dist/__tests__/iconResolve.test.js.map +1 -0
  13. package/dist/__tests__/imageEditState.test.d.ts +2 -0
  14. package/dist/__tests__/imageEditState.test.d.ts.map +1 -0
  15. package/dist/__tests__/imageEditState.test.js +61 -0
  16. package/dist/__tests__/imageEditState.test.js.map +1 -0
  17. package/dist/__tests__/imageEditVersions.test.d.ts +2 -0
  18. package/dist/__tests__/imageEditVersions.test.d.ts.map +1 -0
  19. package/dist/__tests__/imageEditVersions.test.js +141 -0
  20. package/dist/__tests__/imageEditVersions.test.js.map +1 -0
  21. package/dist/__tests__/inferDocumentTitle.test.d.ts +2 -0
  22. package/dist/__tests__/inferDocumentTitle.test.d.ts.map +1 -0
  23. package/dist/__tests__/inferDocumentTitle.test.js +42 -0
  24. package/dist/__tests__/inferDocumentTitle.test.js.map +1 -0
  25. package/dist/__tests__/jsonForm.chooseControl.test.d.ts +2 -0
  26. package/dist/__tests__/jsonForm.chooseControl.test.d.ts.map +1 -0
  27. package/dist/__tests__/jsonForm.chooseControl.test.js +74 -0
  28. package/dist/__tests__/jsonForm.chooseControl.test.js.map +1 -0
  29. package/dist/__tests__/jsonForm.evaluateWhen.test.d.ts +2 -0
  30. package/dist/__tests__/jsonForm.evaluateWhen.test.d.ts.map +1 -0
  31. package/dist/__tests__/jsonForm.evaluateWhen.test.js +51 -0
  32. package/dist/__tests__/jsonForm.evaluateWhen.test.js.map +1 -0
  33. package/dist/__tests__/jsonForm.inferSchema.test.d.ts +2 -0
  34. package/dist/__tests__/jsonForm.inferSchema.test.d.ts.map +1 -0
  35. package/dist/__tests__/jsonForm.inferSchema.test.js +23 -0
  36. package/dist/__tests__/jsonForm.inferSchema.test.js.map +1 -0
  37. package/dist/__tests__/jsonForm.pointer.test.d.ts +2 -0
  38. package/dist/__tests__/jsonForm.pointer.test.d.ts.map +1 -0
  39. package/dist/__tests__/jsonForm.pointer.test.js +94 -0
  40. package/dist/__tests__/jsonForm.pointer.test.js.map +1 -0
  41. package/dist/__tests__/markdown.test.js +40 -0
  42. package/dist/__tests__/markdown.test.js.map +1 -1
  43. package/dist/__tests__/markdownIcons.test.d.ts +2 -0
  44. package/dist/__tests__/markdownIcons.test.d.ts.map +1 -0
  45. package/dist/__tests__/markdownIcons.test.js +93 -0
  46. package/dist/__tests__/markdownIcons.test.js.map +1 -0
  47. package/dist/__tests__/markdownToDoc.test.js +15 -4
  48. package/dist/__tests__/markdownToDoc.test.js.map +1 -1
  49. package/dist/__tests__/recommendTemplates.test.d.ts +2 -0
  50. package/dist/__tests__/recommendTemplates.test.d.ts.map +1 -0
  51. package/dist/__tests__/recommendTemplates.test.js +158 -0
  52. package/dist/__tests__/recommendTemplates.test.js.map +1 -0
  53. package/dist/__tests__/scopedContentContainer.test.d.ts +2 -0
  54. package/dist/__tests__/scopedContentContainer.test.d.ts.map +1 -0
  55. package/dist/__tests__/scopedContentContainer.test.js +74 -0
  56. package/dist/__tests__/scopedContentContainer.test.js.map +1 -0
  57. package/dist/__tests__/slideshowGenerator.test.js +1 -1
  58. package/dist/__tests__/slideshowGenerator.test.js.map +1 -1
  59. package/dist/__tests__/surface.test.d.ts +2 -0
  60. package/dist/__tests__/surface.test.d.ts.map +1 -0
  61. package/dist/__tests__/surface.test.js +57 -0
  62. package/dist/__tests__/surface.test.js.map +1 -0
  63. package/dist/__tests__/templateAnnotationParse.test.d.ts +2 -0
  64. package/dist/__tests__/templateAnnotationParse.test.d.ts.map +1 -0
  65. package/dist/__tests__/templateAnnotationParse.test.js +42 -0
  66. package/dist/__tests__/templateAnnotationParse.test.js.map +1 -0
  67. package/dist/__tests__/templates.test.js +90 -9
  68. package/dist/__tests__/templates.test.js.map +1 -1
  69. package/dist/__tests__/themeCompile.test.d.ts +2 -0
  70. package/dist/__tests__/themeCompile.test.d.ts.map +1 -0
  71. package/dist/__tests__/themeCompile.test.js +134 -0
  72. package/dist/__tests__/themeCompile.test.js.map +1 -0
  73. package/dist/__tests__/themeValidator.test.d.ts +2 -0
  74. package/dist/__tests__/themeValidator.test.d.ts.map +1 -0
  75. package/dist/__tests__/themeValidator.test.js +107 -0
  76. package/dist/__tests__/themeValidator.test.js.map +1 -0
  77. package/dist/__tests__/versions.test.d.ts +2 -0
  78. package/dist/__tests__/versions.test.d.ts.map +1 -0
  79. package/dist/__tests__/versions.test.js +339 -0
  80. package/dist/__tests__/versions.test.js.map +1 -0
  81. package/dist/{chunk-CYQHG27J.js → chunk-2TMAWB4Y.js} +74 -1
  82. package/dist/chunk-2TMAWB4Y.js.map +1 -0
  83. package/dist/{chunk-LAZELBON.js → chunk-3K5OG6KI.js} +165 -17
  84. package/dist/chunk-3K5OG6KI.js.map +1 -0
  85. package/dist/chunk-57CVQCFX.js +193 -0
  86. package/dist/chunk-57CVQCFX.js.map +1 -0
  87. package/dist/{chunk-QM5PKNPW.js → chunk-5WFRKURX.js} +1 -1
  88. package/dist/chunk-5WFRKURX.js.map +1 -0
  89. package/dist/chunk-B4EEON3N.js +1317 -0
  90. package/dist/chunk-B4EEON3N.js.map +1 -0
  91. package/dist/chunk-DBS6RIFL.js +81 -0
  92. package/dist/chunk-DBS6RIFL.js.map +1 -0
  93. package/dist/{chunk-MIBRIRGJ.js → chunk-FR2RBTKO.js} +3 -3
  94. package/dist/{chunk-MIBRIRGJ.js.map → chunk-FR2RBTKO.js.map} +1 -1
  95. package/dist/chunk-H3AXU4MJ.js +179 -0
  96. package/dist/chunk-H3AXU4MJ.js.map +1 -0
  97. package/dist/chunk-JELIRVDP.js +24 -0
  98. package/dist/chunk-JELIRVDP.js.map +1 -0
  99. package/dist/chunk-K32VJONL.js +193 -0
  100. package/dist/chunk-K32VJONL.js.map +1 -0
  101. package/dist/{chunk-GAQLMORN.js → chunk-KSWJR66U.js} +248 -44
  102. package/dist/chunk-KSWJR66U.js.map +1 -0
  103. package/dist/{chunk-TSG2OSE6.js → chunk-MYIH7FWD.js} +3 -3
  104. package/dist/chunk-MYIH7FWD.js.map +1 -0
  105. package/dist/chunk-VLJJHDAC.js +218 -0
  106. package/dist/chunk-VLJJHDAC.js.map +1 -0
  107. package/dist/chunk-WMOI7FN3.js +182 -0
  108. package/dist/chunk-WMOI7FN3.js.map +1 -0
  109. package/dist/chunk-ZTX4PFFT.js +520 -0
  110. package/dist/chunk-ZTX4PFFT.js.map +1 -0
  111. package/dist/doc/audioMapping.d.ts.map +1 -1
  112. package/dist/doc/audioMapping.js +80 -9
  113. package/dist/doc/audioMapping.js.map +1 -1
  114. package/dist/doc/getLayers.d.ts.map +1 -1
  115. package/dist/doc/getLayers.js +29 -18
  116. package/dist/doc/getLayers.js.map +1 -1
  117. package/dist/doc/markdownToDoc.d.ts.map +1 -1
  118. package/dist/doc/markdownToDoc.js +40 -6
  119. package/dist/doc/markdownToDoc.js.map +1 -1
  120. package/dist/doc/templates/featureBlock.d.ts +18 -0
  121. package/dist/doc/templates/featureBlock.d.ts.map +1 -0
  122. package/dist/doc/templates/featureBlock.js +180 -0
  123. package/dist/doc/templates/featureBlock.js.map +1 -0
  124. package/dist/doc/templates/fullBleedQuote.d.ts +12 -0
  125. package/dist/doc/templates/fullBleedQuote.d.ts.map +1 -1
  126. package/dist/doc/templates/fullBleedQuote.js +12 -0
  127. package/dist/doc/templates/fullBleedQuote.js.map +1 -1
  128. package/dist/doc/templates/index.d.ts +28 -4
  129. package/dist/doc/templates/index.d.ts.map +1 -1
  130. package/dist/doc/templates/index.js +45 -12
  131. package/dist/doc/templates/index.js.map +1 -1
  132. package/dist/doc/templates/listBlock.d.ts.map +1 -1
  133. package/dist/doc/templates/listBlock.js +22 -5
  134. package/dist/doc/templates/listBlock.js.map +1 -1
  135. package/dist/doc/templates/statHighlight.d.ts +12 -0
  136. package/dist/doc/templates/statHighlight.d.ts.map +1 -1
  137. package/dist/doc/templates/statHighlight.js +12 -0
  138. package/dist/doc/templates/statHighlight.js.map +1 -1
  139. package/dist/doc/templates/titleBlock.d.ts +11 -0
  140. package/dist/doc/templates/titleBlock.d.ts.map +1 -1
  141. package/dist/doc/templates/titleBlock.js +8 -0
  142. package/dist/doc/templates/titleBlock.js.map +1 -1
  143. package/dist/doc/utils/themeUtils.d.ts +4 -4
  144. package/dist/doc/utils/themeUtils.d.ts.map +1 -1
  145. package/dist/doc/utils/themeUtils.js +8 -7
  146. package/dist/doc/utils/themeUtils.js.map +1 -1
  147. package/dist/generate/slideshowGenerator.js +1 -1
  148. package/dist/generate/slideshowGenerator.js.map +1 -1
  149. package/dist/generate/templateMapper.js +2 -2
  150. package/dist/generate/templateMapper.js.map +1 -1
  151. package/dist/icons/iconData.d.ts +26 -0
  152. package/dist/icons/iconData.d.ts.map +1 -0
  153. package/dist/icons/iconData.js +12 -0
  154. package/dist/icons/iconData.js.map +1 -0
  155. package/dist/icons/index.d.ts +12 -0
  156. package/dist/icons/index.d.ts.map +1 -0
  157. package/dist/icons/index.js +10 -0
  158. package/dist/icons/index.js.map +1 -0
  159. package/dist/icons/resolve.d.ts +61 -0
  160. package/dist/icons/resolve.d.ts.map +1 -0
  161. package/dist/icons/resolve.js +130 -0
  162. package/dist/icons/resolve.js.map +1 -0
  163. package/dist/imageEdit/ImageEditVersionManager.d.ts +31 -0
  164. package/dist/imageEdit/ImageEditVersionManager.d.ts.map +1 -0
  165. package/dist/imageEdit/ImageEditVersionManager.js +39 -0
  166. package/dist/imageEdit/ImageEditVersionManager.js.map +1 -0
  167. package/dist/imageEdit/export.d.ts +33 -0
  168. package/dist/imageEdit/export.d.ts.map +1 -0
  169. package/dist/imageEdit/export.js +253 -0
  170. package/dist/imageEdit/export.js.map +1 -0
  171. package/dist/imageEdit/index.d.ts +14 -0
  172. package/dist/imageEdit/index.d.ts.map +1 -0
  173. package/dist/imageEdit/index.js +22 -0
  174. package/dist/imageEdit/index.js.map +1 -0
  175. package/dist/imageEdit/persistence.d.ts +15 -0
  176. package/dist/imageEdit/persistence.d.ts.map +1 -0
  177. package/dist/imageEdit/persistence.js +49 -0
  178. package/dist/imageEdit/persistence.js.map +1 -0
  179. package/dist/imageEdit/state.d.ts +42 -0
  180. package/dist/imageEdit/state.d.ts.map +1 -0
  181. package/dist/imageEdit/state.js +98 -0
  182. package/dist/imageEdit/state.js.map +1 -0
  183. package/dist/imageEdit/versionPaths.d.ts +27 -0
  184. package/dist/imageEdit/versionPaths.d.ts.map +1 -0
  185. package/dist/imageEdit/versionPaths.js +57 -0
  186. package/dist/imageEdit/versionPaths.js.map +1 -0
  187. package/dist/imageEdit/versions.d.ts +67 -0
  188. package/dist/imageEdit/versions.d.ts.map +1 -0
  189. package/dist/imageEdit/versions.js +161 -0
  190. package/dist/imageEdit/versions.js.map +1 -0
  191. package/dist/index.d.ts +4 -0
  192. package/dist/index.d.ts.map +1 -1
  193. package/dist/index.js +4 -0
  194. package/dist/index.js.map +1 -1
  195. package/dist/jsonForm/chooseControl.d.ts +20 -0
  196. package/dist/jsonForm/chooseControl.d.ts.map +1 -0
  197. package/dist/jsonForm/chooseControl.js +82 -0
  198. package/dist/jsonForm/chooseControl.js.map +1 -0
  199. package/dist/jsonForm/evaluateWhen.d.ts +12 -0
  200. package/dist/jsonForm/evaluateWhen.d.ts.map +1 -0
  201. package/dist/jsonForm/evaluateWhen.js +65 -0
  202. package/dist/jsonForm/evaluateWhen.js.map +1 -0
  203. package/dist/jsonForm/index.d.ts +14 -0
  204. package/dist/jsonForm/index.d.ts.map +1 -0
  205. package/dist/jsonForm/index.js +12 -0
  206. package/dist/jsonForm/index.js.map +1 -0
  207. package/dist/jsonForm/inferSchema.d.ts +12 -0
  208. package/dist/jsonForm/inferSchema.d.ts.map +1 -0
  209. package/dist/jsonForm/inferSchema.js +15 -0
  210. package/dist/jsonForm/inferSchema.js.map +1 -0
  211. package/dist/jsonForm/pointer.d.ts +26 -0
  212. package/dist/jsonForm/pointer.d.ts.map +1 -0
  213. package/dist/jsonForm/pointer.js +102 -0
  214. package/dist/jsonForm/pointer.js.map +1 -0
  215. package/dist/jsonForm/types.d.ts +120 -0
  216. package/dist/jsonForm/types.d.ts.map +1 -0
  217. package/dist/jsonForm/types.js +10 -0
  218. package/dist/jsonForm/types.js.map +1 -0
  219. package/dist/markdown/convert.d.ts +11 -1
  220. package/dist/markdown/convert.d.ts.map +1 -1
  221. package/dist/markdown/convert.js +235 -15
  222. package/dist/markdown/convert.js.map +1 -1
  223. package/dist/markdown/index.d.ts +2 -2
  224. package/dist/markdown/index.d.ts.map +1 -1
  225. package/dist/markdown/index.js +1 -1
  226. package/dist/markdown/index.js.map +1 -1
  227. package/dist/markdown/types.d.ts +34 -1
  228. package/dist/markdown/types.d.ts.map +1 -1
  229. package/dist/markdown/utils.d.ts +40 -0
  230. package/dist/markdown/utils.d.ts.map +1 -1
  231. package/dist/markdown/utils.js +155 -0
  232. package/dist/markdown/utils.js.map +1 -1
  233. package/dist/recommend/index.d.ts +16 -0
  234. package/dist/recommend/index.d.ts.map +1 -0
  235. package/dist/recommend/index.js +15 -0
  236. package/dist/recommend/index.js.map +1 -0
  237. package/dist/recommend/templates.d.ts +39 -0
  238. package/dist/recommend/templates.d.ts.map +1 -0
  239. package/dist/recommend/templates.js +235 -0
  240. package/dist/recommend/templates.js.map +1 -0
  241. package/dist/schemas/BlockTemplates.d.ts +46 -8
  242. package/dist/schemas/BlockTemplates.d.ts.map +1 -1
  243. package/dist/schemas/BlockTemplates.js +3 -3
  244. package/dist/schemas/BlockTemplates.js.map +1 -1
  245. package/dist/schemas/ImageEditDoc.d.ts +81 -0
  246. package/dist/schemas/ImageEditDoc.d.ts.map +1 -0
  247. package/dist/schemas/ImageEditDoc.js +20 -0
  248. package/dist/schemas/ImageEditDoc.js.map +1 -0
  249. package/dist/schemas/Theme.d.ts +93 -23
  250. package/dist/schemas/Theme.d.ts.map +1 -1
  251. package/dist/schemas/Theme.js +80 -13
  252. package/dist/schemas/Theme.js.map +1 -1
  253. package/dist/schemas/colorUtils.d.ts +51 -0
  254. package/dist/schemas/colorUtils.d.ts.map +1 -0
  255. package/dist/schemas/colorUtils.js +154 -0
  256. package/dist/schemas/colorUtils.js.map +1 -0
  257. package/dist/schemas/fontStacks.d.ts +57 -0
  258. package/dist/schemas/fontStacks.d.ts.map +1 -0
  259. package/dist/schemas/fontStacks.js +227 -0
  260. package/dist/schemas/fontStacks.js.map +1 -0
  261. package/dist/schemas/index.d.ts +5 -0
  262. package/dist/schemas/index.d.ts.map +1 -1
  263. package/dist/schemas/index.js +4 -0
  264. package/dist/schemas/index.js.map +1 -1
  265. package/dist/schemas/themeCompile.d.ts +45 -0
  266. package/dist/schemas/themeCompile.d.ts.map +1 -0
  267. package/dist/schemas/themeCompile.js +160 -0
  268. package/dist/schemas/themeCompile.js.map +1 -0
  269. package/dist/schemas/themeLibrary.d.ts +11 -15
  270. package/dist/schemas/themeLibrary.d.ts.map +1 -1
  271. package/dist/schemas/themeLibrary.js +18 -499
  272. package/dist/schemas/themeLibrary.js.map +1 -1
  273. package/dist/schemas/themeValidator.d.ts +26 -0
  274. package/dist/schemas/themeValidator.d.ts.map +1 -0
  275. package/dist/schemas/themeValidator.js +352 -0
  276. package/dist/schemas/themeValidator.js.map +1 -0
  277. package/dist/schemas/themes/bold.json +48 -0
  278. package/dist/schemas/themes/cinematic.json +54 -0
  279. package/dist/schemas/themes/documentary.json +49 -0
  280. package/dist/schemas/themes/gezellig.json +44 -0
  281. package/dist/schemas/themes/index.d.ts +26 -0
  282. package/dist/schemas/themes/index.d.ts.map +1 -0
  283. package/dist/schemas/themes/index.js +49 -0
  284. package/dist/schemas/themes/index.js.map +1 -0
  285. package/dist/schemas/themes/magazine.json +44 -0
  286. package/dist/schemas/themes/minimalist.json +45 -0
  287. package/dist/schemas/themes/morning-light.json +42 -0
  288. package/dist/schemas/themes/standard-dark.json +50 -0
  289. package/dist/schemas/themes/standard.json +42 -0
  290. package/dist/schemas/themes/tech-dark.json +48 -0
  291. package/dist/schemas/themes/warm-earth.json +50 -0
  292. package/dist/storage/ScopedContentContainer.d.ts +44 -0
  293. package/dist/storage/ScopedContentContainer.d.ts.map +1 -0
  294. package/dist/storage/ScopedContentContainer.js +94 -0
  295. package/dist/storage/ScopedContentContainer.js.map +1 -0
  296. package/dist/storage/index.d.ts +1 -0
  297. package/dist/storage/index.d.ts.map +1 -1
  298. package/dist/storage/index.js +1 -0
  299. package/dist/storage/index.js.map +1 -1
  300. package/dist/story/index.d.ts +53 -13
  301. package/dist/story/index.js +12 -4
  302. package/dist/themeLibrary-BHjJ0Qx6.d.ts +40 -0
  303. package/dist/{types-DYAzisMG.d.ts → types-BdZkdJ3z.d.ts} +35 -2
  304. package/dist/types-D98li1gV.d.ts +93 -0
  305. package/dist/versions/DocumentVersionManager.d.ts +31 -0
  306. package/dist/versions/DocumentVersionManager.d.ts.map +1 -0
  307. package/dist/versions/DocumentVersionManager.js +33 -0
  308. package/dist/versions/DocumentVersionManager.js.map +1 -0
  309. package/dist/versions/index.d.ts +29 -0
  310. package/dist/versions/index.d.ts.map +1 -0
  311. package/dist/versions/index.js +28 -0
  312. package/dist/versions/index.js.map +1 -0
  313. package/dist/versions/operations.d.ts +44 -0
  314. package/dist/versions/operations.d.ts.map +1 -0
  315. package/dist/versions/operations.js +190 -0
  316. package/dist/versions/operations.js.map +1 -0
  317. package/dist/versions/paths.d.ts +29 -0
  318. package/dist/versions/paths.d.ts.map +1 -0
  319. package/dist/versions/paths.js +66 -0
  320. package/dist/versions/paths.js.map +1 -0
  321. package/dist/versions/timestamp.d.ts +22 -0
  322. package/dist/versions/timestamp.d.ts.map +1 -0
  323. package/dist/versions/timestamp.js +44 -0
  324. package/dist/versions/timestamp.js.map +1 -0
  325. package/dist/versions/types.d.ts +92 -0
  326. package/dist/versions/types.d.ts.map +1 -0
  327. package/dist/versions/types.js +11 -0
  328. package/dist/versions/types.js.map +1 -0
  329. package/package.json +28 -1
  330. package/src/__tests__/colorUtils.test.ts +112 -0
  331. package/src/__tests__/getLayers.test.ts +32 -7
  332. package/src/__tests__/iconResolve.test.ts +122 -0
  333. package/src/__tests__/imageEditState.test.ts +74 -0
  334. package/src/__tests__/imageEditVersions.test.ts +169 -0
  335. package/src/__tests__/inferDocumentTitle.test.ts +50 -0
  336. package/src/__tests__/jsonForm.chooseControl.test.ts +86 -0
  337. package/src/__tests__/jsonForm.evaluateWhen.test.ts +59 -0
  338. package/src/__tests__/jsonForm.inferSchema.test.ts +25 -0
  339. package/src/__tests__/jsonForm.pointer.test.ts +103 -0
  340. package/src/__tests__/markdown.test.ts +44 -0
  341. package/src/__tests__/markdownIcons.test.ts +104 -0
  342. package/src/__tests__/markdownToDoc.test.ts +19 -4
  343. package/src/__tests__/recommendTemplates.test.ts +183 -0
  344. package/src/__tests__/scopedContentContainer.test.ts +89 -0
  345. package/src/__tests__/slideshowGenerator.test.ts +1 -1
  346. package/src/__tests__/surface.test.ts +68 -0
  347. package/src/__tests__/templateAnnotationParse.test.ts +42 -0
  348. package/src/__tests__/templates.test.ts +93 -9
  349. package/src/__tests__/themeCompile.test.ts +156 -0
  350. package/src/__tests__/themeValidator.test.ts +125 -0
  351. package/src/__tests__/versions.test.ts +415 -0
  352. package/src/doc/audioMapping.ts +95 -13
  353. package/src/doc/getLayers.ts +32 -21
  354. package/src/doc/markdownToDoc.ts +45 -7
  355. package/src/doc/templates/featureBlock.ts +202 -0
  356. package/src/doc/templates/fullBleedQuote.ts +13 -0
  357. package/src/doc/templates/index.ts +50 -16
  358. package/src/doc/templates/listBlock.ts +24 -5
  359. package/src/doc/templates/statHighlight.ts +13 -0
  360. package/src/doc/templates/titleBlock.ts +9 -0
  361. package/src/doc/utils/themeUtils.ts +8 -7
  362. package/src/generate/slideshowGenerator.ts +1 -1
  363. package/src/generate/templateMapper.ts +2 -2
  364. package/src/icons/iconData.ts +31 -0
  365. package/src/icons/index.ts +18 -0
  366. package/src/icons/resolve.ts +139 -0
  367. package/src/imageEdit/ImageEditVersionManager.ts +73 -0
  368. package/src/imageEdit/export.ts +326 -0
  369. package/src/imageEdit/index.ts +71 -0
  370. package/src/imageEdit/persistence.ts +62 -0
  371. package/src/imageEdit/state.ts +112 -0
  372. package/src/imageEdit/versionPaths.ts +57 -0
  373. package/src/imageEdit/versions.ts +231 -0
  374. package/src/index.ts +4 -0
  375. package/src/jsonForm/chooseControl.ts +88 -0
  376. package/src/jsonForm/evaluateWhen.ts +59 -0
  377. package/src/jsonForm/index.ts +22 -0
  378. package/src/jsonForm/inferSchema.ts +25 -0
  379. package/src/jsonForm/pointer.ts +109 -0
  380. package/src/jsonForm/types.ts +167 -0
  381. package/src/markdown/convert.ts +248 -15
  382. package/src/markdown/index.ts +4 -0
  383. package/src/markdown/types.ts +38 -1
  384. package/src/markdown/utils.ts +159 -0
  385. package/src/recommend/index.ts +16 -0
  386. package/src/recommend/templates.ts +273 -0
  387. package/src/schemas/BlockTemplates.ts +50 -7
  388. package/src/schemas/ImageEditDoc.ts +95 -0
  389. package/src/schemas/Theme.ts +152 -23
  390. package/src/schemas/colorUtils.ts +186 -0
  391. package/src/schemas/fontStacks.ts +257 -0
  392. package/src/schemas/index.ts +12 -0
  393. package/src/schemas/themeCompile.ts +203 -0
  394. package/src/schemas/themeLibrary.ts +18 -524
  395. package/src/schemas/themeValidator.ts +388 -0
  396. package/src/schemas/themes/bold.json +48 -0
  397. package/src/schemas/themes/cinematic.json +54 -0
  398. package/src/schemas/themes/documentary.json +49 -0
  399. package/src/schemas/themes/gezellig.json +44 -0
  400. package/src/schemas/themes/index.ts +53 -0
  401. package/src/schemas/themes/magazine.json +44 -0
  402. package/src/schemas/themes/minimalist.json +45 -0
  403. package/src/schemas/themes/morning-light.json +42 -0
  404. package/src/schemas/themes/standard-dark.json +50 -0
  405. package/src/schemas/themes/standard.json +42 -0
  406. package/src/schemas/themes/tech-dark.json +48 -0
  407. package/src/schemas/themes/warm-earth.json +50 -0
  408. package/src/storage/ScopedContentContainer.ts +113 -0
  409. package/src/storage/index.ts +1 -0
  410. package/src/versions/DocumentVersionManager.ts +70 -0
  411. package/src/versions/index.ts +54 -0
  412. package/src/versions/operations.ts +230 -0
  413. package/src/versions/paths.ts +66 -0
  414. package/src/versions/timestamp.ts +48 -0
  415. package/src/versions/types.ts +91 -0
  416. package/dist/chunk-53TZ2UTL.js +0 -470
  417. package/dist/chunk-53TZ2UTL.js.map +0 -1
  418. package/dist/chunk-7GF6RDED.js +0 -82
  419. package/dist/chunk-7GF6RDED.js.map +0 -1
  420. package/dist/chunk-CYQHG27J.js.map +0 -1
  421. package/dist/chunk-GAQLMORN.js.map +0 -1
  422. package/dist/chunk-LAZELBON.js.map +0 -1
  423. package/dist/chunk-QM5PKNPW.js.map +0 -1
  424. package/dist/chunk-TSG2OSE6.js.map +0 -1
  425. package/dist/chunk-XM4WSOHB.js +0 -92
  426. package/dist/chunk-XM4WSOHB.js.map +0 -1
  427. package/dist/themeLibrary-IP7EdyRq.d.ts +0 -44
@@ -0,0 +1,42 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { parseMarkdown } from '../markdown/parse.js';
3
+ import { markdownToDoc } from '../doc/markdownToDoc.js';
4
+
5
+ describe('Template annotation in Heading parse path', () => {
6
+ it('parseMarkdown extracts {[template]} from a heading', () => {
7
+ const md = '## Getting Started {[comparisonBar]}';
8
+ const doc = parseMarkdown(md);
9
+ const heading = doc.children[0];
10
+ expect(heading.type).toBe('heading');
11
+ if (heading.type !== 'heading') return;
12
+ expect(heading.templateAnnotation).toEqual({ template: 'comparisonBar' });
13
+ });
14
+
15
+ it('markdownToDoc preserves templateAnnotation on the resulting block', () => {
16
+ const md = '# Title\n\n## Getting Started {[comparisonBar]}\n\nBody.';
17
+ const parsed = parseMarkdown(md);
18
+ const doc = markdownToDoc(parsed, { articleId: 'test' });
19
+ // The h1 is the root; first child is the h2 with annotation
20
+ const h1 = doc.blocks[0];
21
+ expect(h1.sourceHeading?.depth).toBe(1);
22
+ const h2 = h1.children?.[0];
23
+ expect(h2?.sourceHeading?.depth).toBe(2);
24
+ expect(h2?.sourceHeading?.templateAnnotation).toEqual({ template: 'comparisonBar' });
25
+ });
26
+
27
+ it('tolerates accidentally-doubled trailing `]}` (user typo)', () => {
28
+ const md = '## Getting Started {[comparisonBar]}]}';
29
+ const doc = parseMarkdown(md);
30
+ const heading = doc.children[0];
31
+ if (heading.type !== 'heading') throw new Error('expected heading');
32
+ expect(heading.templateAnnotation).toEqual({ template: 'comparisonBar' });
33
+ });
34
+
35
+ it('still rejects non-trailing annotations (regex stays anchored)', () => {
36
+ const md = '## The {[chart]} section'; // word continues after — not an annotation
37
+ const doc = parseMarkdown(md);
38
+ const heading = doc.children[0];
39
+ if (heading.type !== 'heading') throw new Error('expected heading');
40
+ expect(heading.templateAnnotation).toBeUndefined();
41
+ });
42
+ });
@@ -14,17 +14,19 @@ import type { TemplateBlock } from '../schemas/BlockTemplates';
14
14
  describe('templateRegistry', () => {
15
15
  it('contains all expected templates', () => {
16
16
  const expected = [
17
- 'titleBlock',
17
+ 'title',
18
18
  'sectionHeader',
19
19
  'statHighlight',
20
- 'quoteBlock',
20
+ 'quote',
21
21
  'factCard',
22
22
  'twoColumn',
23
23
  'dateEvent',
24
24
  'imageWithCaption',
25
- 'mapBlock',
25
+ 'leftFeature',
26
+ 'rightFeature',
27
+ 'map',
26
28
  'fullBleedQuote',
27
- 'listBlock',
29
+ 'list',
28
30
  'photoGrid',
29
31
  'definitionCard',
30
32
  'comparisonBar',
@@ -43,7 +45,7 @@ describe('templateRegistry', () => {
43
45
  describe('getAvailableTemplates', () => {
44
46
  it('returns array of template names', () => {
45
47
  const templates = getAvailableTemplates();
46
- expect(templates).toContain('titleBlock');
48
+ expect(templates).toContain('title');
47
49
  expect(templates).toContain('sectionHeader');
48
50
  expect(templates.length).toBeGreaterThanOrEqual(15);
49
51
  });
@@ -51,7 +53,7 @@ describe('getAvailableTemplates', () => {
51
53
 
52
54
  describe('hasTemplate', () => {
53
55
  it('returns true for existing templates', () => {
54
- expect(hasTemplate('titleBlock')).toBe(true);
56
+ expect(hasTemplate('title')).toBe(true);
55
57
  expect(hasTemplate('statHighlight')).toBe(true);
56
58
  });
57
59
 
@@ -63,7 +65,7 @@ describe('hasTemplate', () => {
63
65
  describe('expandTemplateBlock', () => {
64
66
  it('expands titleSlide template into layers', () => {
65
67
  const block: TemplateBlock = {
66
- template: 'titleBlock',
68
+ template: 'title',
67
69
  id: 'title-1',
68
70
  duration: 10,
69
71
  audioSegment: 0,
@@ -79,6 +81,88 @@ describe('expandTemplateBlock', () => {
79
81
  expect((result.layers ?? []).length).toBeGreaterThan(0);
80
82
  });
81
83
 
84
+ it('expands leftFeature template with image on the left half and left-aligned text right', () => {
85
+ const block: TemplateBlock = {
86
+ template: 'leftFeature',
87
+ id: 'lf-1',
88
+ duration: 6,
89
+ audioSegment: 0,
90
+ imageSrc: 'hero.jpg',
91
+ imageAlt: 'hero',
92
+ title: 'Product Builder',
93
+ body: 'I love building software platforms.',
94
+ };
95
+ const context = createTemplateContext(DEFAULT_THEME, 0, 1, VIEWPORT_PRESETS.landscape);
96
+ const result = expandTemplateBlock(block, context);
97
+ const layers = result.layers ?? [];
98
+ const imageLayer = layers.find((l) => l.type === 'image');
99
+ expect(imageLayer).toBeDefined();
100
+ expect(imageLayer!.position.x).toBe('0');
101
+ expect(imageLayer!.position.width).toBe('50%');
102
+ // Text column sits just past the 50% divider, top-left anchored,
103
+ // so the title and body share a left edge with comfortable padding.
104
+ const textLayers = layers.filter((l) => l.type === 'text');
105
+ expect(textLayers.length).toBeGreaterThanOrEqual(1);
106
+ for (const t of textLayers) {
107
+ expect(t.position.x).toBe('54%');
108
+ expect(t.position.anchor).toBe('top-left');
109
+ expect((t.content as { style: { textAlign?: string } }).style.textAlign).toBe('left');
110
+ }
111
+ });
112
+
113
+ it('respects explicit image dimensions: contained + padded inside the half', () => {
114
+ // When the user resizes an image in the WYSIWYG editor it round-
115
+ // trips as `<img width …>`. Feature blocks should treat that as a
116
+ // hint to NOT stretch the image — render it centered with padding
117
+ // around it.
118
+ const block: TemplateBlock = {
119
+ template: 'leftFeature',
120
+ id: 'lf-sized',
121
+ duration: 6,
122
+ audioSegment: 0,
123
+ imageSrc: 'hero.jpg',
124
+ imageAlt: 'hero',
125
+ imageWidth: 194,
126
+ title: 'Sized',
127
+ };
128
+ const context = createTemplateContext(DEFAULT_THEME, 0, 1, VIEWPORT_PRESETS.landscape);
129
+ const result = expandTemplateBlock(block, context);
130
+ const imageLayer = (result.layers ?? []).find((l) => l.type === 'image');
131
+ expect(imageLayer).toBeDefined();
132
+ expect((imageLayer!.content as { fit?: string }).fit).toBe('contain');
133
+ // Image is inset from the half's edges rather than filling them.
134
+ expect(imageLayer!.position.x).toBe('5%');
135
+ expect(imageLayer!.position.width).toBe('40%');
136
+ expect(imageLayer!.position.y).toBe('5%');
137
+ expect(imageLayer!.position.height).toBe('90%');
138
+ });
139
+
140
+ it('expands rightFeature template with image on the right half and right-aligned text left', () => {
141
+ const block: TemplateBlock = {
142
+ template: 'rightFeature',
143
+ id: 'rf-1',
144
+ duration: 6,
145
+ audioSegment: 0,
146
+ imageSrc: 'hero.jpg',
147
+ imageAlt: 'hero',
148
+ title: 'Projects',
149
+ body: 'A list of things.',
150
+ };
151
+ const context = createTemplateContext(DEFAULT_THEME, 0, 1, VIEWPORT_PRESETS.landscape);
152
+ const result = expandTemplateBlock(block, context);
153
+ const layers = result.layers ?? [];
154
+ const imageLayer = layers.find((l) => l.type === 'image');
155
+ expect(imageLayer).toBeDefined();
156
+ expect(imageLayer!.position.x).toBe('50%');
157
+ expect(imageLayer!.position.width).toBe('50%');
158
+ const textLayers = layers.filter((l) => l.type === 'text');
159
+ for (const t of textLayers) {
160
+ expect(t.position.x).toBe('46%');
161
+ expect(t.position.anchor).toBe('top-right');
162
+ expect((t.content as { style: { textAlign?: string } }).style.textAlign).toBe('right');
163
+ }
164
+ });
165
+
82
166
  it('expands statHighlight template', () => {
83
167
  const block: TemplateBlock = {
84
168
  template: 'statHighlight',
@@ -163,7 +247,7 @@ describe('expandTemplateBlock', () => {
163
247
  describe('expandDocBlocks', () => {
164
248
  it('expands array of template blocks with cumulative timing', () => {
165
249
  const blocks: TemplateBlock[] = [
166
- { template: 'titleBlock', id: 'slide-1', duration: 5, audioSegment: 0, title: 'Hello' },
250
+ { template: 'title', id: 'slide-1', duration: 5, audioSegment: 0, title: 'Hello' },
167
251
  {
168
252
  template: 'factCard',
169
253
  id: 'slide-2',
@@ -213,7 +297,7 @@ describe('expandDocBlocks', () => {
213
297
 
214
298
  it('supports landscape and portrait viewports', () => {
215
299
  const blocks: TemplateBlock[] = [
216
- { template: 'titleBlock', id: 'title-1', duration: 5, audioSegment: 0, title: 'Test' },
300
+ { template: 'title', id: 'title-1', duration: 5, audioSegment: 0, title: 'Test' },
217
301
  ];
218
302
 
219
303
  const landscape = expandDocBlocks(blocks, { viewport: VIEWPORT_PRESETS.landscape });
@@ -0,0 +1,156 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import {
3
+ compileTheme,
4
+ parseTheme,
5
+ serializeTheme,
6
+ STARTER_THEME,
7
+ deriveColorPalette,
8
+ } from '../schemas/themeCompile.js';
9
+ import { registerTheme, unregisterTheme, getRegisteredThemes } from '../schemas/Theme.js';
10
+ import { resolveTheme, DEFAULT_THEME, THEMES } from '../schemas/themeLibrary.js';
11
+ import { resolveFontFamily } from '../schemas/fontStacks.js';
12
+
13
+ describe('deriveColorPalette', () => {
14
+ it('fills all 8 palette slots from a single primary seed', () => {
15
+ const palette = deriveColorPalette({ primary: '#3182ce' });
16
+ expect(palette.primary).toBe('#3182ce');
17
+ expect(palette.background).toMatch(/^#[0-9a-f]{6}$/);
18
+ expect(palette.text).toMatch(/^#[0-9a-f]{6}$/);
19
+ expect(palette.warning).toMatch(/^#[0-9a-f]{6}$/);
20
+ });
21
+
22
+ it('lets explicit partial colors win over derived values', () => {
23
+ const palette = deriveColorPalette({ primary: '#3182ce' }, { warning: '#ff00ff' });
24
+ expect(palette.warning).toBe('#ff00ff');
25
+ });
26
+
27
+ it('high contrast spread > balanced spread > subtle spread (in lightness range)', () => {
28
+ const subtle = deriveColorPalette({ primary: '#3182ce' }, {}, { contrast: 'subtle' });
29
+ const balanced = deriveColorPalette({ primary: '#3182ce' }, {}, { contrast: 'balanced' });
30
+ const high = deriveColorPalette({ primary: '#3182ce' }, {}, { contrast: 'high' });
31
+ // All should be valid
32
+ for (const p of [subtle, balanced, high]) {
33
+ expect(p.background).toMatch(/^#[0-9a-f]{6}$/);
34
+ }
35
+ });
36
+ });
37
+
38
+ describe('compileTheme', () => {
39
+ it('compiles a minimal partial into a complete validated Theme', () => {
40
+ const theme = compileTheme({
41
+ name: 'My Theme',
42
+ seedColors: { primary: '#3182ce' },
43
+ });
44
+ expect(theme.id).toMatch(/^custom/);
45
+ expect(theme.name).toBe('My Theme');
46
+ expect(theme.colors.primary).toBe('#3182ce');
47
+ expect(theme.colors.background).toMatch(/^#[0-9a-f]{6}$/);
48
+ expect(theme.typography.bodyFont).toEqual(STARTER_THEME.typography.bodyFont);
49
+ expect(theme.colorSchemes).toEqual(STARTER_THEME.colorSchemes);
50
+ expect(theme.schemaVersion).toBe('1');
51
+ });
52
+
53
+ it('passes through fully-specified themes (built-ins should re-compile to identical Theme)', () => {
54
+ const reCompiled = compileTheme(DEFAULT_THEME);
55
+ expect(reCompiled.colors).toEqual(DEFAULT_THEME.colors);
56
+ expect(reCompiled.typography).toEqual(DEFAULT_THEME.typography);
57
+ });
58
+
59
+ it('honors typography overrides', () => {
60
+ const theme = compileTheme({
61
+ seedColors: { primary: '#3182ce' },
62
+ typography: {
63
+ titleFont: { stackId: 'playfair' },
64
+ bodyFont: { custom: { name: 'Georgia', fallback: 'serif' } },
65
+ },
66
+ });
67
+ expect(theme.typography.titleFont).toEqual({ stackId: 'playfair' });
68
+ expect(theme.typography.bodyFont).toEqual({
69
+ custom: { name: 'Georgia', fallback: 'serif' },
70
+ });
71
+ });
72
+ });
73
+
74
+ describe('parseTheme / serializeTheme round-trip', () => {
75
+ it('serialize → parse is lossless for built-ins', () => {
76
+ for (const [id, theme] of Object.entries(THEMES)) {
77
+ const json = serializeTheme(theme);
78
+ const parsed = parseTheme(json);
79
+ expect(parsed).toEqual(theme);
80
+ expect(parsed.id).toBe(id);
81
+ }
82
+ });
83
+
84
+ it('parseTheme throws on invalid JSON', () => {
85
+ expect(() => parseTheme('not json')).toThrow(/Invalid theme JSON/);
86
+ });
87
+
88
+ it('parseTheme throws on JSON that fails validation', () => {
89
+ expect(() => parseTheme('{}')).toThrow(/Invalid parsed theme/);
90
+ });
91
+ });
92
+
93
+ describe('registerTheme / resolveTheme', () => {
94
+ it('registered theme takes precedence over built-in with same id', () => {
95
+ const overlay = compileTheme({
96
+ id: 'standard',
97
+ name: 'Standard Override',
98
+ seedColors: { primary: '#ff0000' },
99
+ });
100
+ registerTheme(overlay);
101
+ try {
102
+ const resolved = resolveTheme('standard');
103
+ expect(resolved.name).toBe('Standard Override');
104
+ expect(resolved.colors.primary).toBe('#ff0000');
105
+ } finally {
106
+ unregisterTheme('standard');
107
+ }
108
+ // After cleanup, built-in is restored
109
+ expect(resolveTheme('standard').name).toBe('Standard Light');
110
+ });
111
+
112
+ it('registered custom-id theme is resolvable via Doc.themeId', () => {
113
+ const custom = compileTheme({
114
+ id: 'custom-test-1',
115
+ name: 'Custom Test One',
116
+ seedColors: { primary: '#00aabb' },
117
+ });
118
+ registerTheme(custom);
119
+ try {
120
+ const resolved = resolveTheme('custom-test-1');
121
+ expect(resolved.id).toBe('custom-test-1');
122
+ expect(resolved.name).toBe('Custom Test One');
123
+ expect(getRegisteredThemes().some((t) => t.id === 'custom-test-1')).toBe(true);
124
+ } finally {
125
+ unregisterTheme('custom-test-1');
126
+ }
127
+ });
128
+
129
+ it('resolveTheme falls back to default for unknown ids', () => {
130
+ expect(resolveTheme('nonexistent-theme')).toBe(DEFAULT_THEME);
131
+ expect(resolveTheme(undefined)).toBe(DEFAULT_THEME);
132
+ });
133
+ });
134
+
135
+ describe('FontFamily resolution', () => {
136
+ it('resolves stackId to a CSS family string', () => {
137
+ expect(resolveFontFamily({ stackId: 'inter' }, 'fallback')).toContain('Inter');
138
+ });
139
+
140
+ it('resolves { custom } to a quoted CSS family with fallback', () => {
141
+ const css = resolveFontFamily(
142
+ { custom: { name: 'My Font', fallback: 'sans-serif' } },
143
+ 'fallback',
144
+ );
145
+ expect(css).toContain('"My Font"');
146
+ expect(css).toContain('sans-serif');
147
+ });
148
+
149
+ it('returns roleFallback for unknown stackId', () => {
150
+ expect(resolveFontFamily({ stackId: 'no-such-stack' }, 'my-fallback')).toBe('my-fallback');
151
+ });
152
+
153
+ it('accepts legacy raw strings for backward compat', () => {
154
+ expect(resolveFontFamily('Georgia, serif', 'fallback')).toBe('Georgia, serif');
155
+ });
156
+ });
@@ -0,0 +1,125 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { validateTheme, assertTheme } from '../schemas/themeValidator.js';
3
+ import { THEMES, DEFAULT_THEME } from '../schemas/themeLibrary.js';
4
+ import type { Theme } from '../schemas/Theme.js';
5
+
6
+ function clone<T>(v: T): T {
7
+ return JSON.parse(JSON.stringify(v)) as T;
8
+ }
9
+
10
+ describe('validateTheme', () => {
11
+ it('accepts every built-in theme', () => {
12
+ for (const [id, theme] of Object.entries(THEMES)) {
13
+ const result = validateTheme(theme);
14
+ if (!result.valid) {
15
+ throw new Error(
16
+ `Built-in theme "${id}" failed validation:\n${result.errors
17
+ .map((e) => ` ${e.path}: ${e.message}`)
18
+ .join('\n')}`,
19
+ );
20
+ }
21
+ expect(result.valid).toBe(true);
22
+ }
23
+ });
24
+
25
+ it('rejects non-objects', () => {
26
+ expect(validateTheme(null).valid).toBe(false);
27
+ expect(validateTheme('a string').valid).toBe(false);
28
+ expect(validateTheme(42).valid).toBe(false);
29
+ });
30
+
31
+ it('flags missing required fields', () => {
32
+ const result = validateTheme({ schemaVersion: '1', id: 'x', name: 'X' });
33
+ expect(result.valid).toBe(false);
34
+ const paths = result.errors.map((e) => e.path);
35
+ expect(paths).toContain('colors');
36
+ expect(paths).toContain('typography');
37
+ expect(paths).toContain('renderStyle');
38
+ expect(paths).toContain('colorSchemes');
39
+ });
40
+
41
+ it('flags raw font strings (legacy shape)', () => {
42
+ const bad = clone(DEFAULT_THEME) as unknown as Record<string, unknown>;
43
+ (bad.typography as Record<string, unknown>).bodyFont = 'Inter, sans-serif';
44
+ const result = validateTheme(bad);
45
+ expect(result.valid).toBe(false);
46
+ expect(result.errors.some((e) => e.path === 'typography.bodyFont')).toBe(true);
47
+ });
48
+
49
+ it('flags raw CSS gradient strings in persistentLayers', () => {
50
+ const bad = clone(DEFAULT_THEME) as Theme;
51
+ bad.persistentLayers = {
52
+ bottomLayers: [
53
+ // intentionally invalid: raw `gradient` string is not allowed
54
+ {
55
+ template: 'gradientBackground',
56
+ config: {
57
+ type: 'gradientBackground',
58
+ gradient: 'linear-gradient(...)' as never,
59
+ },
60
+ },
61
+ ],
62
+ };
63
+ const result = validateTheme(bad);
64
+ expect(result.valid).toBe(false);
65
+ expect(
66
+ result.errors.some((e) => e.path.endsWith('config.gradient') && /raw CSS/i.test(e.message)),
67
+ ).toBe(true);
68
+ });
69
+
70
+ it('flags malformed hex colors', () => {
71
+ const bad = clone(DEFAULT_THEME);
72
+ bad.colors.primary = 'rgb(0,0,0)';
73
+ const result = validateTheme(bad);
74
+ expect(result.valid).toBe(false);
75
+ expect(result.errors.some((e) => e.path === 'colors.primary')).toBe(true);
76
+ });
77
+
78
+ it('flags wrong schemaVersion', () => {
79
+ const bad = clone(DEFAULT_THEME) as unknown as Record<string, unknown>;
80
+ bad.schemaVersion = '2';
81
+ const result = validateTheme(bad);
82
+ expect(result.valid).toBe(false);
83
+ expect(result.errors.some((e) => e.path === 'schemaVersion')).toBe(true);
84
+ });
85
+
86
+ it('accepts FontFamily { custom } shape', () => {
87
+ const t = clone(DEFAULT_THEME);
88
+ t.typography.bodyFont = { custom: { name: 'Georgia', fallback: 'serif' } };
89
+ expect(validateTheme(t).valid).toBe(true);
90
+ });
91
+
92
+ it('rejects FontFamily.custom with bad fallback', () => {
93
+ const bad = clone(DEFAULT_THEME) as unknown as Record<string, unknown>;
94
+ (bad.typography as Record<string, unknown>).bodyFont = {
95
+ custom: { name: 'Georgia', fallback: 'invalid' },
96
+ };
97
+ const result = validateTheme(bad);
98
+ expect(result.valid).toBe(false);
99
+ expect(result.errors.some((e) => e.path === 'typography.bodyFont.custom.fallback')).toBe(true);
100
+ });
101
+
102
+ it('accepts theme with seedColors', () => {
103
+ const t = clone(DEFAULT_THEME);
104
+ t.seedColors = { primary: '#3182ce', accent: '#63b3ed' };
105
+ expect(validateTheme(t).valid).toBe(true);
106
+ });
107
+
108
+ it('rejects seedColors without primary', () => {
109
+ const bad = clone(DEFAULT_THEME);
110
+ bad.seedColors = { accent: '#63b3ed' } as never;
111
+ const result = validateTheme(bad);
112
+ expect(result.valid).toBe(false);
113
+ expect(result.errors.some((e) => e.path === 'seedColors.primary')).toBe(true);
114
+ });
115
+ });
116
+
117
+ describe('assertTheme', () => {
118
+ it('returns the theme unchanged when valid', () => {
119
+ expect(assertTheme(DEFAULT_THEME)).toBe(DEFAULT_THEME);
120
+ });
121
+
122
+ it('throws with a readable error summary on invalid input', () => {
123
+ expect(() => assertTheme({}, 'test theme')).toThrow(/Invalid test theme/);
124
+ });
125
+ });