@bendyline/squisq 1.5.2 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. package/README.md +63 -10
  2. package/dist/{Doc-BBVGq1_9.d.ts → Doc-ylqWKDc_.d.ts} +248 -37
  3. package/dist/{ImageEditDoc-FuyGtt6o.d.ts → ImageEditDoc-D828IXJ1.d.ts} +1 -1
  4. package/dist/annotationCoercion-Dr_DY9Hx.d.ts +86 -0
  5. package/dist/{chunk-T5UK6YOB.js → chunk-2PWOO4ZE.js} +60 -17
  6. package/dist/chunk-2PWOO4ZE.js.map +1 -0
  7. package/dist/{chunk-VWUFZ6ZG.js → chunk-3E5F2XMR.js} +2 -29
  8. package/dist/chunk-3E5F2XMR.js.map +1 -0
  9. package/dist/chunk-4X3JQXNM.js +35 -0
  10. package/dist/chunk-4X3JQXNM.js.map +1 -0
  11. package/dist/{chunk-P3K7NLHF.js → chunk-5DKBFXPY.js} +31 -121
  12. package/dist/chunk-5DKBFXPY.js.map +1 -0
  13. package/dist/{chunk-DLXZMS5K.js → chunk-5J4HKRJF.js} +51 -3
  14. package/dist/chunk-5J4HKRJF.js.map +1 -0
  15. package/dist/chunk-5UYI3RJB.js +2503 -0
  16. package/dist/chunk-5UYI3RJB.js.map +1 -0
  17. package/dist/{chunk-BEQODJWV.js → chunk-66QO7XFB.js} +11 -291
  18. package/dist/chunk-66QO7XFB.js.map +1 -0
  19. package/dist/{chunk-HWVFJAAH.js → chunk-AMIR72JP.js} +28 -10
  20. package/dist/chunk-AMIR72JP.js.map +1 -0
  21. package/dist/chunk-CY2R2VPR.js +884 -0
  22. package/dist/chunk-CY2R2VPR.js.map +1 -0
  23. package/dist/chunk-FVJUUTEM.js +74 -0
  24. package/dist/chunk-FVJUUTEM.js.map +1 -0
  25. package/dist/{chunk-Q3ROPT5H.js → chunk-GZY66SDN.js} +1 -1
  26. package/dist/{chunk-Q3ROPT5H.js.map → chunk-GZY66SDN.js.map} +1 -1
  27. package/dist/chunk-JOQYPRDZ.js +127 -0
  28. package/dist/chunk-JOQYPRDZ.js.map +1 -0
  29. package/dist/{chunk-A4O7GIWE.js → chunk-MMPBOUK7.js} +10 -73
  30. package/dist/chunk-MMPBOUK7.js.map +1 -0
  31. package/dist/{chunk-LDQ2HJIX.js → chunk-PXLVXTLW.js} +252 -54
  32. package/dist/chunk-PXLVXTLW.js.map +1 -0
  33. package/dist/{chunk-TQWLI6S2.js → chunk-Q7LROV2H.js} +4 -2
  34. package/dist/{chunk-TQWLI6S2.js.map → chunk-Q7LROV2H.js.map} +1 -1
  35. package/dist/{chunk-GPLTCSXO.js → chunk-R2GC6RLD.js} +7 -7
  36. package/dist/chunk-R2GC6RLD.js.map +1 -0
  37. package/dist/chunk-R2YZTCLO.js +292 -0
  38. package/dist/chunk-R2YZTCLO.js.map +1 -0
  39. package/dist/chunk-S7A5D7ZJ.js +174 -0
  40. package/dist/chunk-S7A5D7ZJ.js.map +1 -0
  41. package/dist/chunk-TFJVXUHH.js +2358 -0
  42. package/dist/chunk-TFJVXUHH.js.map +1 -0
  43. package/dist/{chunk-LTN6I7QW.js → chunk-TUWMK62B.js} +55 -4
  44. package/dist/chunk-TUWMK62B.js.map +1 -0
  45. package/dist/{chunk-IMSCRLLV.js → chunk-VR7UPJ7C.js} +35 -25
  46. package/dist/chunk-VR7UPJ7C.js.map +1 -0
  47. package/dist/{chunk-KKNUBQ6Y.js → chunk-W4O2TX5I.js} +157 -92
  48. package/dist/chunk-W4O2TX5I.js.map +1 -0
  49. package/dist/{chunk-5TYS5V3G.js → chunk-X6DUONGT.js} +4204 -3560
  50. package/dist/chunk-X6DUONGT.js.map +1 -0
  51. package/dist/doc/index.d.ts +2334 -5
  52. package/dist/doc/index.js +127 -33
  53. package/dist/generate/index.d.ts +7 -2
  54. package/dist/generate/index.js +1 -1
  55. package/dist/imageEdit/index.d.ts +9 -6
  56. package/dist/imageEdit/index.js +3 -3
  57. package/dist/index.d.ts +12 -10
  58. package/dist/index.js +246 -80
  59. package/dist/jsonForm/index.d.ts +7 -3
  60. package/dist/jsonForm/index.js +9 -4
  61. package/dist/markdown/index.d.ts +6 -70
  62. package/dist/markdown/index.js +13 -9
  63. package/dist/narration/index.d.ts +618 -0
  64. package/dist/narration/index.js +82 -0
  65. package/dist/recommend/index.d.ts +15 -1
  66. package/dist/recommend/index.js +2 -2
  67. package/dist/schemas/index.d.ts +28 -6
  68. package/dist/schemas/index.js +15 -13
  69. package/dist/spatial/index.d.ts +0 -1
  70. package/dist/spatial/index.js +1 -1
  71. package/dist/storage/index.js +1 -1
  72. package/dist/{themeLibrary-CehcJhzz.d.ts → themeLibrary-RjVgLpUi.d.ts} +10 -11
  73. package/dist/timing/index.js +5 -3
  74. package/dist/transform/index.d.ts +35 -24
  75. package/dist/transform/index.js +9 -10
  76. package/dist/{types-DlAZ7MW4.d.ts → types-BwMPl2CB.d.ts} +1 -1
  77. package/dist/{types-D98li1gV.d.ts → types-C4kiRn_k.d.ts} +3 -1
  78. package/dist/versions/index.d.ts +2 -1
  79. package/dist/versions/index.js +2 -2
  80. package/package.json +6 -6
  81. package/src/__tests__/applyNarrationTiming.test.ts +190 -0
  82. package/src/__tests__/applyRenderStyle.test.ts +3 -3
  83. package/src/__tests__/asciiDiagramBattle.test.ts +246 -0
  84. package/src/__tests__/asciiDiagramDetect.test.ts +162 -0
  85. package/src/__tests__/asciiDiagramMapping.test.ts +38 -0
  86. package/src/__tests__/asciiDiagramParse.test.ts +233 -0
  87. package/src/__tests__/asciiDiagramPipeline.test.ts +273 -0
  88. package/src/__tests__/asciiDiagramRender.test.ts +162 -0
  89. package/src/__tests__/asciiDiagramRepair.test.ts +84 -0
  90. package/src/__tests__/asciiTimelineDetect.test.ts +157 -0
  91. package/src/__tests__/asciiTimelineParse.test.ts +205 -0
  92. package/src/__tests__/asciiTimelinePipeline.test.ts +349 -0
  93. package/src/__tests__/asciiTimelineRender.test.ts +376 -0
  94. package/src/__tests__/attrTokens.test.ts +5 -0
  95. package/src/__tests__/contentContainer.test.ts +43 -0
  96. package/src/__tests__/customTemplatesFrontmatter.test.ts +0 -15
  97. package/src/__tests__/customThemesFrontmatter.test.ts +1 -0
  98. package/src/__tests__/dataTable.test.ts +25 -15
  99. package/src/__tests__/diagramBlock.test.ts +3 -3
  100. package/src/__tests__/diagramText.test.ts +26 -0
  101. package/src/__tests__/docToMarkdownMedia.test.ts +83 -0
  102. package/src/__tests__/drawingBlock.test.ts +3 -3
  103. package/src/__tests__/fixtures/asciiDiagramBattle.ts +512 -0
  104. package/src/__tests__/fixtures/asciiDiagrams.ts +287 -0
  105. package/src/__tests__/fixtures/asciiTimelines.ts +35 -0
  106. package/src/__tests__/fixtures/treeviewFixtures.ts +243 -0
  107. package/src/__tests__/getLayers.test.ts +32 -18
  108. package/src/__tests__/getLayersFallback.test.ts +8 -8
  109. package/src/__tests__/imageEditVersions.test.ts +2 -0
  110. package/src/__tests__/jsonForm.pointer.test.ts +47 -1
  111. package/src/__tests__/layoutBlock.test.ts +3 -3
  112. package/src/__tests__/majorApiTypes.test.ts +18 -0
  113. package/src/__tests__/markdown.test.ts +54 -4
  114. package/src/__tests__/markdownSanitize.test.ts +7 -5
  115. package/src/__tests__/matchFontFamily.test.ts +114 -0
  116. package/src/__tests__/materializeBlockLayers.test.ts +135 -0
  117. package/src/__tests__/materializeTestUtils.ts +17 -0
  118. package/src/__tests__/narrationAlign.test.ts +150 -0
  119. package/src/__tests__/narrationFeatures.test.ts +71 -0
  120. package/src/__tests__/narrationNuclei.test.ts +45 -0
  121. package/src/__tests__/narrationPacing.test.ts +189 -0
  122. package/src/__tests__/narrationScript.test.ts +149 -0
  123. package/src/__tests__/narrationSessionRepro.test.ts +62 -0
  124. package/src/__tests__/narrationSidecar.test.ts +88 -0
  125. package/src/__tests__/narrationSyllables.test.ts +65 -0
  126. package/src/__tests__/narrationTestSignals.ts +167 -0
  127. package/src/__tests__/narrationVad.test.ts +66 -0
  128. package/src/__tests__/resolveDocTheme.test.ts +16 -2
  129. package/src/__tests__/rootExports.test.ts +18 -0
  130. package/src/__tests__/scopedContentContainer.test.ts +20 -0
  131. package/src/__tests__/shapeGeometry.test.ts +12 -0
  132. package/src/__tests__/templateAnnotationParse.test.ts +10 -0
  133. package/src/__tests__/templateInputs.test.ts +69 -0
  134. package/src/__tests__/templateMetadata.test.ts +2 -1
  135. package/src/__tests__/templates.test.ts +143 -17
  136. package/src/__tests__/themeCompile.test.ts +49 -22
  137. package/src/__tests__/transformProvenance.test.ts +160 -0
  138. package/src/__tests__/transformV2.test.ts +40 -14
  139. package/src/__tests__/treeviewBattle.test.ts +76 -0
  140. package/src/__tests__/treeviewDetect.test.ts +168 -0
  141. package/src/__tests__/treeviewParse.test.ts +118 -0
  142. package/src/__tests__/treeviewPipeline.test.ts +183 -0
  143. package/src/__tests__/treeviewRender.test.ts +91 -0
  144. package/src/__tests__/versions.test.ts +44 -0
  145. package/src/doc/applyNarrationTiming.ts +244 -0
  146. package/src/doc/asciiDiagram/chars.ts +210 -0
  147. package/src/doc/asciiDiagram/detect.ts +175 -0
  148. package/src/doc/asciiDiagram/index.ts +38 -0
  149. package/src/doc/asciiDiagram/mapping.ts +225 -0
  150. package/src/doc/asciiDiagram/parse.ts +1095 -0
  151. package/src/doc/asciiDiagram/render.ts +963 -0
  152. package/src/doc/asciiDiagram/repair.ts +400 -0
  153. package/src/doc/asciiDiagram/types.ts +65 -0
  154. package/src/doc/asciiTimeline/detect.ts +138 -0
  155. package/src/doc/asciiTimeline/index.ts +26 -0
  156. package/src/doc/asciiTimeline/mapping.ts +43 -0
  157. package/src/doc/asciiTimeline/parse.ts +870 -0
  158. package/src/doc/asciiTimeline/render.ts +427 -0
  159. package/src/doc/asciiTimeline/types.ts +73 -0
  160. package/src/doc/asciiTimeline/wrappedFlow.ts +427 -0
  161. package/src/doc/audioMapping.ts +60 -46
  162. package/src/doc/customTemplatesFrontmatter.ts +0 -41
  163. package/src/doc/docToMarkdown.ts +70 -2
  164. package/src/doc/index.ts +76 -4
  165. package/src/doc/markdownToDoc.ts +138 -2
  166. package/src/doc/materializeBlockLayers.ts +304 -0
  167. package/src/doc/mediaAnnotations.ts +20 -0
  168. package/src/doc/resolveDocTheme.ts +8 -7
  169. package/src/doc/templateInputs.ts +185 -2
  170. package/src/doc/templates/__tests__/customTemplateRegistry.test.ts +22 -8
  171. package/src/doc/templates/__tests__/listBlock.test.ts +64 -0
  172. package/src/doc/templates/diagramBlock.ts +237 -38
  173. package/src/doc/templates/fallbackBlock.ts +1 -1
  174. package/src/doc/templates/index.ts +134 -279
  175. package/src/doc/templates/inputDescriptors.ts +20 -1
  176. package/src/doc/templates/listBlock.ts +88 -30
  177. package/src/doc/templates/metadata.ts +10 -0
  178. package/src/doc/templates/persistentLayers.ts +12 -126
  179. package/src/doc/templates/registry.ts +88 -0
  180. package/src/doc/templates/templateNames.ts +22 -0
  181. package/src/doc/templates/timelineBlock.ts +613 -0
  182. package/src/doc/templates/treeBlock.ts +131 -0
  183. package/src/doc/treeview/chars.ts +72 -0
  184. package/src/doc/treeview/detect.ts +145 -0
  185. package/src/doc/treeview/index.ts +30 -0
  186. package/src/doc/treeview/mapping.ts +95 -0
  187. package/src/doc/treeview/parse.ts +201 -0
  188. package/src/doc/treeview/render.ts +41 -0
  189. package/src/doc/treeview/types.ts +43 -0
  190. package/src/doc/utils/applyRenderStyle.ts +2 -9
  191. package/src/doc/utils/diagramText.ts +78 -0
  192. package/src/doc/utils/shapeGeometry.ts +67 -5
  193. package/src/doc/validate.ts +1 -1
  194. package/src/generate/templateMapper.ts +7 -0
  195. package/src/imageEdit/export.ts +6 -5
  196. package/src/imageEdit/versionPaths.ts +13 -26
  197. package/src/imageEdit/versions.ts +21 -100
  198. package/src/index.ts +1 -0
  199. package/src/internal/immutable.ts +58 -0
  200. package/src/jsonForm/index.ts +9 -1
  201. package/src/jsonForm/pointer.ts +68 -23
  202. package/src/markdown/attrTokens.ts +7 -5
  203. package/src/markdown/index.ts +1 -0
  204. package/src/markdown/stringify.ts +4 -9
  205. package/src/markdown/utils.ts +36 -14
  206. package/src/narration/align.ts +514 -0
  207. package/src/narration/features.ts +231 -0
  208. package/src/narration/index.ts +86 -0
  209. package/src/narration/nuclei.ts +118 -0
  210. package/src/narration/pacing.ts +211 -0
  211. package/src/narration/script.ts +239 -0
  212. package/src/narration/session.ts +100 -0
  213. package/src/narration/sidecar.ts +187 -0
  214. package/src/narration/syllables.ts +85 -0
  215. package/src/narration/trace.ts +50 -0
  216. package/src/narration/types.ts +299 -0
  217. package/src/narration/vad.ts +131 -0
  218. package/src/recommend/numberHighlight.ts +25 -0
  219. package/src/recommend/templates.ts +108 -12
  220. package/src/schemas/BlockTemplates.ts +156 -7
  221. package/src/schemas/Doc.ts +72 -9
  222. package/src/schemas/Media.ts +12 -2
  223. package/src/schemas/Theme.ts +45 -25
  224. package/src/schemas/Viewport.ts +2 -2
  225. package/src/schemas/fontStacks.ts +79 -0
  226. package/src/schemas/themeCompile.ts +15 -0
  227. package/src/schemas/themeConstants.ts +2 -0
  228. package/src/schemas/themeLibrary.ts +25 -19
  229. package/src/schemas/themeValidator.ts +1 -1
  230. package/src/schemas/themes/gezellig.json +1 -1
  231. package/src/spatial/Geohash.ts +0 -3
  232. package/src/storage/ContentContainer.ts +17 -9
  233. package/src/storage/ScopedContentContainer.ts +45 -15
  234. package/src/transform/applyTransform.ts +5 -5
  235. package/src/transform/index.ts +3 -1
  236. package/src/transform/registry.ts +169 -49
  237. package/src/transform/templateSelector.ts +21 -2
  238. package/src/transform/timingAllocator.ts +152 -21
  239. package/src/transform/types.ts +14 -1
  240. package/src/versions/gitignore.ts +34 -0
  241. package/src/versions/index.ts +2 -0
  242. package/src/versions/operations.ts +18 -104
  243. package/src/versions/paths.ts +13 -28
  244. package/src/versions/snapshotStore.ts +202 -0
  245. package/src/versions/types.ts +3 -1
  246. package/dist/chunk-5TYS5V3G.js.map +0 -1
  247. package/dist/chunk-A4O7GIWE.js.map +0 -1
  248. package/dist/chunk-BEQODJWV.js.map +0 -1
  249. package/dist/chunk-DLXZMS5K.js.map +0 -1
  250. package/dist/chunk-GPLTCSXO.js.map +0 -1
  251. package/dist/chunk-HWVFJAAH.js.map +0 -1
  252. package/dist/chunk-IMSCRLLV.js.map +0 -1
  253. package/dist/chunk-JELIRVDP.js +0 -24
  254. package/dist/chunk-JELIRVDP.js.map +0 -1
  255. package/dist/chunk-KKNUBQ6Y.js.map +0 -1
  256. package/dist/chunk-LDQ2HJIX.js.map +0 -1
  257. package/dist/chunk-LTN6I7QW.js.map +0 -1
  258. package/dist/chunk-P3K7NLHF.js.map +0 -1
  259. package/dist/chunk-T5UK6YOB.js.map +0 -1
  260. package/dist/chunk-UY7KGQ5R.js +0 -192
  261. package/dist/chunk-UY7KGQ5R.js.map +0 -1
  262. package/dist/chunk-VLJJHDAC.js +0 -218
  263. package/dist/chunk-VLJJHDAC.js.map +0 -1
  264. package/dist/chunk-VWUFZ6ZG.js.map +0 -1
  265. package/dist/story/index.d.ts +0 -1801
  266. package/dist/story/index.js +0 -279
  267. package/src/doc/getLayers.ts +0 -195
  268. /package/dist/{story → narration}/index.js.map +0 -0
@@ -0,0 +1,83 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { parseMarkdown, stringifyMarkdown } from '../markdown/index';
3
+ import { markdownToDoc } from '../doc/markdownToDoc';
4
+ import { docToMarkdown } from '../doc/docToMarkdown';
5
+
6
+ /** One full normalization cycle: md → doc → md. */
7
+ function normalize(markdown: string): string {
8
+ return stringifyMarkdown(docToMarkdown(markdownToDoc(parseMarkdown(markdown))));
9
+ }
10
+
11
+ /** Byte-stability convention: stable after one normalization cycle. */
12
+ function expectStable(markdown: string): string {
13
+ const once = normalize(markdown);
14
+ const twice = normalize(once);
15
+ expect(twice).toBe(once);
16
+ return once;
17
+ }
18
+
19
+ describe('docToMarkdown media round-trip', () => {
20
+ it('preserves a preamble document-anchored narration annotation', () => {
21
+ const md = `{[audio src=audio/narration-001.webm anchor=document]}\n\n# Hello\n\nBody text.\n`;
22
+ const once = expectStable(md);
23
+ expect(once).toContain('{[audio src=audio/narration-001.webm anchor=document]}');
24
+ // It must come back at the TOP (before the first heading) so reparsing
25
+ // yields documentMedia again.
26
+ expect(once.indexOf('{[audio')).toBeLessThan(once.indexOf('# Hello'));
27
+ const reparsed = markdownToDoc(parseMarkdown(once));
28
+ expect(reparsed.documentMedia?.length).toBe(1);
29
+ expect(reparsed.documentMedia![0].anchor).toBe('document');
30
+ });
31
+
32
+ it('preserves a block-level clip mid-contents with params', () => {
33
+ const md = `# Scene\n\nLead-in paragraph.\n\n{[video src=video/b.mp4 startAt=2 clipStart=1 clipEnd=9 spillover=true]}\n\nTrailing paragraph.\n`;
34
+ const once = expectStable(md);
35
+ expect(once).toContain(
36
+ '{[video src=video/b.mp4 startAt=2 clipStart=1 clipEnd=9 spillover=true]}',
37
+ );
38
+ // Original position: after the lead-in, before the trailing paragraph.
39
+ expect(once.indexOf('Lead-in')).toBeLessThan(once.indexOf('{[video'));
40
+ expect(once.indexOf('{[video')).toBeLessThan(once.indexOf('Trailing'));
41
+ const reparsed = markdownToDoc(parseMarkdown(once));
42
+ const withMedia = reparsed.blocks.find((b) => b.media && b.media.length > 0);
43
+ expect(withMedia?.media?.[0].clipStart).toBe(1);
44
+ expect(withMedia?.media?.[0].spillover).toBe(true);
45
+ });
46
+
47
+ it('preserves a document-anchored annotation authored inside a section body', () => {
48
+ const md = `# Section\n\nSome text.\n\n{[audio src=audio/full.mp3 anchor=document startAt=0:30]}\n\nMore text.\n`;
49
+ const once = expectStable(md);
50
+ // Raw text passthrough keeps the mm:ss time form byte-identical.
51
+ expect(once).toContain('{[audio src=audio/full.mp3 anchor=document startAt=0:30]}');
52
+ const reparsed = markdownToDoc(parseMarkdown(once));
53
+ expect(reparsed.documentMedia?.[0].startAt).toBe(30);
54
+ });
55
+
56
+ it('preserves quoted srcs with spaces', () => {
57
+ const md = `{[audio src="audio/my take.webm" anchor=document]}\n\n# Doc\n\nText.\n`;
58
+ const once = expectStable(md);
59
+ expect(once).toContain('src="audio/my take.webm"');
60
+ const reparsed = markdownToDoc(parseMarkdown(once));
61
+ expect(reparsed.documentMedia?.[0].src).toBe('audio/my take.webm');
62
+ });
63
+
64
+ it('emits programmatic doc clips (no origin) at the document top canonically', () => {
65
+ const doc = markdownToDoc(parseMarkdown('# Title\n\nBody.\n'));
66
+ doc.documentMedia = [
67
+ { id: 'clip-1', src: 'audio/take.webm', kind: 'audio', startAt: 0, anchor: 'document' },
68
+ ];
69
+ const md = stringifyMarkdown(docToMarkdown(doc));
70
+ expect(md).toContain('{[audio src=audio/take.webm anchor=document]}');
71
+ expect(md.indexOf('{[audio')).toBeLessThan(md.indexOf('# Title'));
72
+ // And the emission itself round-trips.
73
+ const reparsed = markdownToDoc(parseMarkdown(md));
74
+ expect(reparsed.documentMedia?.length).toBe(1);
75
+ expect(stringifyMarkdown(docToMarkdown(reparsed))).toBe(md);
76
+ });
77
+
78
+ it('leaves docs without media untouched', () => {
79
+ const md = `# Plain\n\nNothing to see.\n`;
80
+ const once = expectStable(md);
81
+ expect(once).not.toContain('{[audio');
82
+ });
83
+ });
@@ -90,9 +90,9 @@ describe('drawingBlock template', () => {
90
90
  });
91
91
  });
92
92
 
93
- describe('drawingBlock integration with getLayers', () => {
93
+ describe('drawingBlock integration with materializeBlockLayers', () => {
94
94
  it('renders a drawing block through the template registry', async () => {
95
- const { getLayers } = await import('../doc/getLayers.js');
95
+ const { materializeBlockLayers } = await import('../doc/materializeBlockLayers.js');
96
96
  const parent: Block = {
97
97
  id: 'sketch',
98
98
  startTime: 0,
@@ -116,7 +116,7 @@ describe('drawingBlock integration with getLayers', () => {
116
116
  shape('l', 'arrow', { from: 'ceo', to: 'dev' }, { title: 'manages' }),
117
117
  ],
118
118
  };
119
- const layers = getLayers(parent, {});
119
+ const layers = materializeBlockLayers(parent, { persistentLayers: false }).layers;
120
120
  const types = new Set(layers.map((l: Layer) => l.type));
121
121
  expect(types.has('shape')).toBe(true);
122
122
  expect(types.has('path')).toBe(true);
@@ -0,0 +1,512 @@
1
+ /**
2
+ * Battle-test corpus: a broad, category-organized set of AI-style ASCII
3
+ * diagrams, each with its intended semantics. Every fixture here is
4
+ * verified to (a) detect as a diagram, (b) parse to exactly these
5
+ * nodes/edges/containers, and (c) survive the render↔parse fixpoint
6
+ * byte-stably. See asciiDiagramBattle.test.ts.
7
+ *
8
+ * GENERATED by scratchpad/gen-battle-fixtures.mjs — edit there and re-run.
9
+ */
10
+
11
+ export interface BattleDiagram {
12
+ name: string;
13
+ category: string;
14
+ art: string;
15
+ /** Expected node ids (order-independent). */
16
+ nodes: string[];
17
+ /** Expected edges: [source, target, label?, directed?]. directed defaults true. */
18
+ edges: Array<[string, string, (string | undefined)?, boolean?]>;
19
+ /** Expected containment: childId → containerId. */
20
+ containers?: Record<string, string>;
21
+ }
22
+
23
+ export const BATTLE_DIAGRAMS: BattleDiagram[] = [
24
+ {
25
+ name: 'linearEtl',
26
+ category: 'linear',
27
+ art: `┌─────────┐ ┌───────────┐ ┌──────────┐
28
+ │ Extract │ ──> │ Transform │ ──> │ Load │
29
+ └─────────┘ └───────────┘ └──────────┘`,
30
+ nodes: ['extract', 'transform', 'load'],
31
+ edges: [
32
+ ['extract', 'transform'],
33
+ ['transform', 'load'],
34
+ ],
35
+ },
36
+ {
37
+ name: 'requestLifecycle',
38
+ category: 'linear',
39
+ art: `┌────────┐ ┌───────────────┐ ┌─────────┐ ┌──────────┐
40
+ │ Client │───>│ Load Balancer │───>│ API │───>│ Database │
41
+ └────────┘ └───────────────┘ └─────────┘ └──────────┘`,
42
+ nodes: ['client', 'load-balancer', 'api', 'database'],
43
+ edges: [
44
+ ['client', 'load-balancer'],
45
+ ['load-balancer', 'api'],
46
+ ['api', 'database'],
47
+ ],
48
+ },
49
+ {
50
+ name: 'deployPipelineAscii',
51
+ category: 'linear',
52
+ art: `+-------+ +------+ +-------+ +--------+
53
+ | Build | --> | Test | --> | Stage | --> | Deploy |
54
+ +-------+ +------+ +-------+ +--------+`,
55
+ nodes: ['build', 'test', 'stage', 'deploy'],
56
+ edges: [
57
+ ['build', 'test'],
58
+ ['test', 'stage'],
59
+ ['stage', 'deploy'],
60
+ ],
61
+ },
62
+ {
63
+ name: 'mlPipeline',
64
+ category: 'linear',
65
+ art: `┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
66
+ │ Data │──►│ Features │──►│ Train │──►│ Model │
67
+ └──────────┘ └──────────┘ └────┬─────┘ └──────────┘
68
+ │ metrics
69
+
70
+ ┌──────────┐
71
+ │ Evaluate │
72
+ └──────────┘`,
73
+ nodes: ['data', 'features', 'train', 'model', 'evaluate'],
74
+ edges: [
75
+ ['data', 'features'],
76
+ ['features', 'train'],
77
+ ['train', 'model'],
78
+ ['train', 'evaluate', 'metrics'],
79
+ ],
80
+ },
81
+ {
82
+ name: 'approvalFlow',
83
+ category: 'linear',
84
+ art: `┌─────────┐ ┌──────────┐ ┌──────────┐ ┌───────────┐
85
+ │ Draft │──►│ Review │──►│ Approved │──►│ Published │
86
+ └─────────┘ └──────────┘ └──────────┘ └───────────┘`,
87
+ nodes: ['draft', 'review', 'approved', 'published'],
88
+ edges: [
89
+ ['draft', 'review'],
90
+ ['review', 'approved'],
91
+ ['approved', 'published'],
92
+ ],
93
+ },
94
+ {
95
+ name: 'loadBalancerFanout',
96
+ category: 'fan-out',
97
+ art: ` ┌───────────────┐
98
+ │ Load Balancer │
99
+ └──┬──┬──┬──┬───┘
100
+ ┌──────┘ │ │ └──────┐
101
+ ▼ ▼ ▼ ▼
102
+ ┌──────┐┌──────┐┌──────┐┌──────┐
103
+ │ Web1 ││ Web2 ││ Web3 ││ Web4 │
104
+ └──────┘└──────┘└──────┘└──────┘`,
105
+ nodes: ['load-balancer', 'web1', 'web2', 'web3', 'web4'],
106
+ edges: [
107
+ ['load-balancer', 'web1'],
108
+ ['load-balancer', 'web2'],
109
+ ['load-balancer', 'web3'],
110
+ ['load-balancer', 'web4'],
111
+ ],
112
+ },
113
+ {
114
+ name: 'pubsubBroker',
115
+ category: 'fan-out',
116
+ art: ` ┌──────────┐ ┌──────────┐
117
+ │ Producer │─────►│ Broker │
118
+ └──────────┘ └─┬──┬──┬──┘
119
+ ┌─────────┘ │ └─────────┐
120
+ ▼ ▼ ▼
121
+ ┌──────────┐┌──────────┐┌──────────┐
122
+ │ Sub1 ││ Sub2 ││ Sub3 │
123
+ └──────────┘└──────────┘└──────────┘`,
124
+ nodes: ['producer', 'broker', 'sub1', 'sub2', 'sub3'],
125
+ edges: [
126
+ ['producer', 'broker'],
127
+ ['broker', 'sub1'],
128
+ ['broker', 'sub2'],
129
+ ['broker', 'sub3'],
130
+ ],
131
+ },
132
+ {
133
+ name: 'collectorFanin',
134
+ category: 'fan-in',
135
+ art: `┌────┐ ┌────┐ ┌────┐
136
+ │ S1 │ │ S2 │ │ S3 │
137
+ └─┬──┘ └─┬──┘ └─┬──┘
138
+ │ │ │
139
+ ▼ ▼ ▼
140
+ ┌─────────────────────┐
141
+ │ Collector │
142
+ └─────────────────────┘`,
143
+ nodes: ['s1', 's2', 's3', 'collector'],
144
+ edges: [
145
+ ['s1', 'collector'],
146
+ ['s2', 'collector'],
147
+ ['s3', 'collector'],
148
+ ],
149
+ },
150
+ {
151
+ name: 'sharedCache',
152
+ category: 'fan-in',
153
+ art: `┌──────────┐ ┌──────────┐ ┌──────────┐
154
+ │ Service1 │ │ Service2 │ │ Service3 │
155
+ └────┬─────┘ └────┬─────┘ └────┬─────┘
156
+ │ │ │
157
+ ▼ ▼ ▼
158
+ ┌────────────────────────────────────────┐
159
+ │ Shared Cache │
160
+ └────────────────────────────────────────┘`,
161
+ nodes: ['service1', 'service2', 'service3', 'shared-cache'],
162
+ edges: [
163
+ ['service1', 'shared-cache'],
164
+ ['service2', 'shared-cache'],
165
+ ['service3', 'shared-cache'],
166
+ ],
167
+ },
168
+ {
169
+ name: 'diamond',
170
+ category: 'dag',
171
+ art: ` ┌───┐
172
+ │ A │
173
+ └┬─┬┘
174
+ ┌───┘ └───┐
175
+ ▼ ▼
176
+ ┌───┐ ┌───┐
177
+ │ B │ │ C │
178
+ └─┬─┘ └─┬─┘
179
+ └───┐ ┌───┘
180
+ ▼ ▼
181
+ ┌───┐
182
+ │ D │
183
+ └───┘`,
184
+ nodes: ['a', 'b', 'c', 'd'],
185
+ edges: [
186
+ ['a', 'b'],
187
+ ['a', 'c'],
188
+ ['b', 'd'],
189
+ ['c', 'd'],
190
+ ],
191
+ },
192
+ {
193
+ name: 'orgTree',
194
+ category: 'dag',
195
+ art: ` ┌───────┐
196
+ │ CEO │
197
+ └───┬───┘
198
+ ┌─────────┴─────────┐
199
+ ▼ ▼
200
+ ┌───────┐ ┌───────┐
201
+ │ VP-Eng│ │ VP-Ops│
202
+ └───┬───┘ └───────┘
203
+ ┌───┴───┐
204
+ ▼ ▼
205
+ ┌───────┐┌───────┐
206
+ │ Dev ││ QA │
207
+ └───────┘└───────┘`,
208
+ nodes: ['ceo', 'vp-eng', 'vp-ops', 'dev', 'qa'],
209
+ edges: [
210
+ ['ceo', 'vp-eng'],
211
+ ['ceo', 'vp-ops'],
212
+ ['vp-eng', 'dev'],
213
+ ['vp-eng', 'qa'],
214
+ ],
215
+ },
216
+ {
217
+ name: 'decisionTree',
218
+ category: 'dag',
219
+ art: ` ┌───────────┐
220
+ │ Check? │
221
+ └──┬─────┬──┘
222
+ │ │
223
+ ▼ ▼
224
+ ┌────────┐ ┌────────┐
225
+ │ Accept │ │ Reject │
226
+ └────────┘ └────────┘`,
227
+ nodes: ['check', 'accept', 'reject'],
228
+ edges: [
229
+ ['check', 'accept'],
230
+ ['check', 'reject'],
231
+ ],
232
+ },
233
+ {
234
+ name: 'microservices',
235
+ category: 'dag',
236
+ art: ` ┌──────────────┐
237
+ │ Gateway │
238
+ └──┬────┬────┬─┘
239
+ ┌──────────┘ │ └──────────┐
240
+ ▼ ▼ ▼
241
+ ┌──────────┐ ┌──────────┐ ┌──────────┐
242
+ │ Users │ │ Orders │ │ Payments │
243
+ └────┬─────┘ └────┬─────┘ └────┬─────┘
244
+ ▼ ▼ ▼
245
+ ┌──────────┐ ┌──────────┐ ┌──────────┐
246
+ │ UsersDB │ │ OrdersDB │ │ PayDB │
247
+ └──────────┘ └──────────┘ └──────────┘`,
248
+ nodes: ['gateway', 'users', 'orders', 'payments', 'usersdb', 'ordersdb', 'paydb'],
249
+ edges: [
250
+ ['gateway', 'users'],
251
+ ['gateway', 'orders'],
252
+ ['gateway', 'payments'],
253
+ ['users', 'usersdb'],
254
+ ['orders', 'ordersdb'],
255
+ ['payments', 'paydb'],
256
+ ],
257
+ },
258
+ {
259
+ name: 'layered',
260
+ category: 'dag',
261
+ art: `┌─────────────────────────────┐
262
+ │ Presentation Layer │
263
+ └──────────────┬──────────────┘
264
+
265
+ ┌─────────────────────────────┐
266
+ │ Business Layer │
267
+ └──────────────┬──────────────┘
268
+
269
+ ┌─────────────────────────────┐
270
+ │ Data Layer │
271
+ └─────────────────────────────┘`,
272
+ nodes: ['presentation-layer', 'business-layer', 'data-layer'],
273
+ edges: [
274
+ ['presentation-layer', 'business-layer'],
275
+ ['business-layer', 'data-layer'],
276
+ ],
277
+ },
278
+ {
279
+ name: 'threeTier',
280
+ category: 'dag',
281
+ art: `┌──────────┐ ┌──────────┐ ┌──────────┐
282
+ │ Browser │────►│ App │────►│ DB │
283
+ └──────────┘ └────┬─────┘ └──────────┘
284
+
285
+
286
+ ┌──────────┐
287
+ │ Cache │
288
+ └──────────┘`,
289
+ nodes: ['browser', 'app', 'db', 'cache'],
290
+ edges: [
291
+ ['browser', 'app'],
292
+ ['app', 'db'],
293
+ ['app', 'cache'],
294
+ ],
295
+ },
296
+ {
297
+ name: 'retryLoop',
298
+ category: 'cycle',
299
+ art: `┌─────────┐ ┌──────────┐
300
+ │ Request │ ──────> │ Worker │
301
+ └─────────┘ └────┬─────┘
302
+ ▲ │
303
+ └───────────────────┘`,
304
+ nodes: ['request', 'worker'],
305
+ edges: [
306
+ ['request', 'worker'],
307
+ ['worker', 'request'],
308
+ ],
309
+ },
310
+ {
311
+ name: 'stateMachine',
312
+ category: 'cycle',
313
+ art: `┌─────────┐ ┌─────────┐ ┌──────────┐
314
+ │ Idle │ ───────> │ Running │ ──────> │ Complete │
315
+ └─────────┘ └────┬────┘ └──────────┘
316
+ ▲ │
317
+ └────────────────────┘`,
318
+ nodes: ['idle', 'running', 'complete'],
319
+ edges: [
320
+ ['idle', 'running'],
321
+ ['running', 'complete'],
322
+ ['running', 'idle'],
323
+ ],
324
+ },
325
+ {
326
+ name: 'circuitBreaker',
327
+ category: 'cycle',
328
+ art: ` ┌──────────┐
329
+ │ Closed │
330
+ └──┬─────┬─┘
331
+ │ ▲
332
+ ┌───────────┘ │
333
+ ▼ │
334
+ ┌──────────┐ ┌─────┴─────┐
335
+ │ Open │────►│ Half-Open │
336
+ └──────────┘ └───────────┘`,
337
+ nodes: ['closed', 'open', 'half-open'],
338
+ edges: [
339
+ ['closed', 'open'],
340
+ ['open', 'half-open'],
341
+ ['half-open', 'closed'],
342
+ ],
343
+ },
344
+ {
345
+ name: 'fullMesh3',
346
+ category: 'many-to-many',
347
+ art: ` ┌───────┐
348
+ │ Alpha │
349
+ └──┬─┬──┘
350
+ ▲ ▲
351
+ ┌───────────┘ │
352
+ │ └────────────┐
353
+ ▼ ▼
354
+ ┌──────┐ ┌───────┐
355
+ │ Beta ├◄────────────────►┤ Gamma │
356
+ └──────┘ └───────┘`,
357
+ nodes: ['alpha', 'beta', 'gamma'],
358
+ edges: [
359
+ ['alpha', 'beta'],
360
+ ['beta', 'alpha'],
361
+ ['alpha', 'gamma'],
362
+ ['gamma', 'alpha'],
363
+ ['beta', 'gamma'],
364
+ ['gamma', 'beta'],
365
+ ],
366
+ },
367
+ {
368
+ name: 'bidirReplication',
369
+ category: 'edges',
370
+ art: `┌──────────┐ ┌──────────┐
371
+ │ Primary │ <───────────> │ Replica │
372
+ └──────────┘ └──────────┘`,
373
+ nodes: ['primary', 'replica'],
374
+ edges: [
375
+ ['primary', 'replica'],
376
+ ['replica', 'primary'],
377
+ ],
378
+ },
379
+ {
380
+ name: 'undirectedPair',
381
+ category: 'edges',
382
+ art: `┌────────┐ ┌────────┐
383
+ │ NodeA │──────────│ NodeB │
384
+ └────────┘ └────────┘`,
385
+ nodes: ['nodea', 'nodeb'],
386
+ edges: [['nodea', 'nodeb', undefined, false]],
387
+ },
388
+ {
389
+ name: 'labeledTyped',
390
+ category: 'labels',
391
+ art: `┌──────────┐ ┌──────────┐
392
+ │ API │ ─── reads ──►│ Database │
393
+ └────┬─────┘ └──────────┘
394
+ │ writes
395
+
396
+ ┌──────────┐
397
+ │ Cache │
398
+ └──────────┘`,
399
+ nodes: ['api', 'database', 'cache'],
400
+ edges: [
401
+ ['api', 'database', 'reads'],
402
+ ['api', 'cache', 'writes'],
403
+ ],
404
+ },
405
+ {
406
+ name: 'queueDlq',
407
+ category: 'labels',
408
+ art: `┌──────────┐ ┌──────────┐ ┌──────────┐
409
+ │ Producer │────►│ Queue │────►│ Consumer │
410
+ └──────────┘ └────┬─────┘ └──────────┘
411
+ │ on-fail
412
+
413
+ ┌──────────┐
414
+ │ DLQ │
415
+ └──────────┘`,
416
+ nodes: ['producer', 'queue', 'consumer', 'dlq'],
417
+ edges: [
418
+ ['producer', 'queue'],
419
+ ['queue', 'consumer'],
420
+ ['queue', 'dlq', 'on-fail'],
421
+ ],
422
+ },
423
+ {
424
+ name: 'cqrs',
425
+ category: 'labels',
426
+ art: `┌──────────┐ ┌──────────────┐
427
+ │ Client │─── command ───────►│ Write Model │
428
+ └────┬─────┘ └──────┬───────┘
429
+ │ query │ events
430
+ ▼ ▼
431
+ ┌──────────────┐ ┌──────────────┐
432
+ │ Read Model │◄────────────────│ Event Store │
433
+ └──────────────┘ └──────────────┘`,
434
+ nodes: ['client', 'write-model', 'read-model', 'event-store'],
435
+ edges: [
436
+ ['client', 'write-model', 'command'],
437
+ ['client', 'read-model', 'query'],
438
+ ['write-model', 'event-store', 'events'],
439
+ ['event-store', 'read-model'],
440
+ ],
441
+ },
442
+ {
443
+ name: 'multilineLabels',
444
+ category: 'labels',
445
+ art: `┌──────────────┐ ┌──────────────┐
446
+ │ molen-kernel │ │ molen-client │
447
+ │ headless sim │───────►│ three.js │
448
+ │ no DOM │ │ WebGL │
449
+ └──────────────┘ └──────────────┘`,
450
+ nodes: ['molen-kernel', 'molen-client'],
451
+ edges: [['molen-kernel', 'molen-client']],
452
+ },
453
+ {
454
+ name: 'k8sCluster',
455
+ category: 'container',
456
+ art: `┌───────────── Kubernetes Cluster ─────────────┐
457
+ │ │
458
+ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
459
+ │ │ Pod A │ │ Pod B │ │ Pod C │ │
460
+ │ └─────────┘ └─────────┘ └─────────┘ │
461
+ │ │
462
+ └──────────────────────────────────────────────┘`,
463
+ nodes: ['kubernetes-cluster', 'pod-a', 'pod-b', 'pod-c'],
464
+ edges: [],
465
+ containers: {
466
+ 'pod-a': 'kubernetes-cluster',
467
+ 'pod-b': 'kubernetes-cluster',
468
+ 'pod-c': 'kubernetes-cluster',
469
+ },
470
+ },
471
+ {
472
+ name: 'twoRegions',
473
+ category: 'container',
474
+ art: `┌──── Region US ─────┐ ┌──── Region EU ─────┐
475
+ │ │ │ │
476
+ │ ┌──────────────┐ │ │ ┌──────────────┐ │
477
+ │ │ us-worker │ │ │ │ eu-worker │ │
478
+ │ └──────────────┘ │ │ └──────────────┘ │
479
+ │ │ │ │
480
+ └────────────────────┘ └────────────────────┘`,
481
+ nodes: ['region-us', 'us-worker', 'region-eu', 'eu-worker'],
482
+ edges: [],
483
+ containers: { 'us-worker': 'region-us', 'eu-worker': 'region-eu' },
484
+ },
485
+ {
486
+ name: 'nestedTwoLevels',
487
+ category: 'container',
488
+ art: `┌─────────── Region ────────────┐
489
+ │ │
490
+ │ ┌─────── Zone A ────────┐ │
491
+ │ │ │ │
492
+ │ │ ┌──────────────┐ │ │
493
+ │ │ │ Instance 1 │ │ │
494
+ │ │ └──────────────┘ │ │
495
+ │ │ │ │
496
+ │ └───────────────────────┘ │
497
+ │ │
498
+ └───────────────────────────────┘`,
499
+ nodes: ['region', 'zone-a', 'instance-1'],
500
+ edges: [],
501
+ containers: { 'zone-a': 'region', 'instance-1': 'zone-a' },
502
+ },
503
+ {
504
+ name: 'roundedCorners',
505
+ category: 'style',
506
+ art: `╭─────────╮ ╭─────────╮
507
+ │ Start │ ───► │ End │
508
+ ╰─────────╯ ╰─────────╯`,
509
+ nodes: ['start', 'end'],
510
+ edges: [['start', 'end']],
511
+ },
512
+ ];