@bendyline/squisq 1.5.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/README.md +63 -10
  2. package/dist/{Doc-BBVGq1_9.d.ts → Doc-ylqWKDc_.d.ts} +248 -37
  3. package/dist/{ImageEditDoc-FuyGtt6o.d.ts → ImageEditDoc-D828IXJ1.d.ts} +1 -1
  4. package/dist/annotationCoercion-Dr_DY9Hx.d.ts +86 -0
  5. package/dist/{chunk-VWUFZ6ZG.js → chunk-3E5F2XMR.js} +2 -29
  6. package/dist/chunk-3E5F2XMR.js.map +1 -0
  7. package/dist/chunk-4X3JQXNM.js +35 -0
  8. package/dist/chunk-4X3JQXNM.js.map +1 -0
  9. package/dist/{chunk-P3K7NLHF.js → chunk-5DKBFXPY.js} +31 -121
  10. package/dist/chunk-5DKBFXPY.js.map +1 -0
  11. package/dist/{chunk-DLXZMS5K.js → chunk-5J4HKRJF.js} +51 -3
  12. package/dist/chunk-5J4HKRJF.js.map +1 -0
  13. package/dist/chunk-5UYI3RJB.js +2503 -0
  14. package/dist/chunk-5UYI3RJB.js.map +1 -0
  15. package/dist/{chunk-BEQODJWV.js → chunk-66QO7XFB.js} +11 -291
  16. package/dist/chunk-66QO7XFB.js.map +1 -0
  17. package/dist/{chunk-KKNUBQ6Y.js → chunk-6PJRB2CF.js} +156 -91
  18. package/dist/chunk-6PJRB2CF.js.map +1 -0
  19. package/dist/{chunk-HWVFJAAH.js → chunk-AMIR72JP.js} +28 -10
  20. package/dist/chunk-AMIR72JP.js.map +1 -0
  21. package/dist/chunk-FVJUUTEM.js +74 -0
  22. package/dist/chunk-FVJUUTEM.js.map +1 -0
  23. package/dist/{chunk-5TYS5V3G.js → chunk-GF3BVNLU.js} +4127 -3538
  24. package/dist/chunk-GF3BVNLU.js.map +1 -0
  25. package/dist/{chunk-Q3ROPT5H.js → chunk-GZY66SDN.js} +1 -1
  26. package/dist/{chunk-Q3ROPT5H.js.map → chunk-GZY66SDN.js.map} +1 -1
  27. package/dist/chunk-JOQYPRDZ.js +127 -0
  28. package/dist/chunk-JOQYPRDZ.js.map +1 -0
  29. package/dist/{chunk-A4O7GIWE.js → chunk-LYNJEOAC.js} +10 -73
  30. package/dist/chunk-LYNJEOAC.js.map +1 -0
  31. package/dist/{chunk-LDQ2HJIX.js → chunk-PXLVXTLW.js} +252 -54
  32. package/dist/chunk-PXLVXTLW.js.map +1 -0
  33. package/dist/{chunk-TQWLI6S2.js → chunk-Q7LROV2H.js} +4 -2
  34. package/dist/{chunk-TQWLI6S2.js.map → chunk-Q7LROV2H.js.map} +1 -1
  35. package/dist/{chunk-T5UK6YOB.js → chunk-QTSJOY54.js} +60 -17
  36. package/dist/chunk-QTSJOY54.js.map +1 -0
  37. package/dist/chunk-QVANWDPH.js +2358 -0
  38. package/dist/chunk-QVANWDPH.js.map +1 -0
  39. package/dist/{chunk-GPLTCSXO.js → chunk-R2GC6RLD.js} +7 -7
  40. package/dist/chunk-R2GC6RLD.js.map +1 -0
  41. package/dist/chunk-R2YZTCLO.js +292 -0
  42. package/dist/chunk-R2YZTCLO.js.map +1 -0
  43. package/dist/chunk-S7A5D7ZJ.js +174 -0
  44. package/dist/chunk-S7A5D7ZJ.js.map +1 -0
  45. package/dist/chunk-T3TI7GSZ.js +884 -0
  46. package/dist/chunk-T3TI7GSZ.js.map +1 -0
  47. package/dist/{chunk-LTN6I7QW.js → chunk-TUWMK62B.js} +55 -4
  48. package/dist/chunk-TUWMK62B.js.map +1 -0
  49. package/dist/{chunk-IMSCRLLV.js → chunk-VR7UPJ7C.js} +35 -25
  50. package/dist/chunk-VR7UPJ7C.js.map +1 -0
  51. package/dist/doc/index.d.ts +2334 -5
  52. package/dist/doc/index.js +127 -33
  53. package/dist/generate/index.d.ts +7 -2
  54. package/dist/generate/index.js +1 -1
  55. package/dist/imageEdit/index.d.ts +9 -6
  56. package/dist/imageEdit/index.js +3 -3
  57. package/dist/index.d.ts +12 -10
  58. package/dist/index.js +246 -80
  59. package/dist/jsonForm/index.d.ts +7 -3
  60. package/dist/jsonForm/index.js +9 -4
  61. package/dist/markdown/index.d.ts +6 -70
  62. package/dist/markdown/index.js +13 -9
  63. package/dist/narration/index.d.ts +618 -0
  64. package/dist/narration/index.js +82 -0
  65. package/dist/recommend/index.d.ts +15 -1
  66. package/dist/recommend/index.js +2 -2
  67. package/dist/schemas/index.d.ts +28 -6
  68. package/dist/schemas/index.js +15 -13
  69. package/dist/spatial/index.d.ts +0 -1
  70. package/dist/spatial/index.js +1 -1
  71. package/dist/storage/index.js +1 -1
  72. package/dist/{themeLibrary-CehcJhzz.d.ts → themeLibrary-RjVgLpUi.d.ts} +10 -11
  73. package/dist/timing/index.js +5 -3
  74. package/dist/transform/index.d.ts +35 -24
  75. package/dist/transform/index.js +9 -10
  76. package/dist/{types-DlAZ7MW4.d.ts → types-BwMPl2CB.d.ts} +1 -1
  77. package/dist/{types-D98li1gV.d.ts → types-C4kiRn_k.d.ts} +3 -1
  78. package/dist/versions/index.d.ts +2 -1
  79. package/dist/versions/index.js +2 -2
  80. package/package.json +6 -6
  81. package/src/__tests__/applyNarrationTiming.test.ts +190 -0
  82. package/src/__tests__/applyRenderStyle.test.ts +3 -3
  83. package/src/__tests__/asciiDiagramBattle.test.ts +246 -0
  84. package/src/__tests__/asciiDiagramDetect.test.ts +162 -0
  85. package/src/__tests__/asciiDiagramMapping.test.ts +38 -0
  86. package/src/__tests__/asciiDiagramParse.test.ts +233 -0
  87. package/src/__tests__/asciiDiagramPipeline.test.ts +273 -0
  88. package/src/__tests__/asciiDiagramRender.test.ts +162 -0
  89. package/src/__tests__/asciiDiagramRepair.test.ts +84 -0
  90. package/src/__tests__/asciiTimelineDetect.test.ts +157 -0
  91. package/src/__tests__/asciiTimelineParse.test.ts +205 -0
  92. package/src/__tests__/asciiTimelinePipeline.test.ts +349 -0
  93. package/src/__tests__/asciiTimelineRender.test.ts +376 -0
  94. package/src/__tests__/attrTokens.test.ts +5 -0
  95. package/src/__tests__/contentContainer.test.ts +43 -0
  96. package/src/__tests__/customTemplatesFrontmatter.test.ts +0 -15
  97. package/src/__tests__/dataTable.test.ts +25 -15
  98. package/src/__tests__/diagramBlock.test.ts +3 -3
  99. package/src/__tests__/diagramText.test.ts +26 -0
  100. package/src/__tests__/docToMarkdownMedia.test.ts +83 -0
  101. package/src/__tests__/drawingBlock.test.ts +3 -3
  102. package/src/__tests__/fixtures/asciiDiagramBattle.ts +512 -0
  103. package/src/__tests__/fixtures/asciiDiagrams.ts +287 -0
  104. package/src/__tests__/fixtures/asciiTimelines.ts +35 -0
  105. package/src/__tests__/fixtures/treeviewFixtures.ts +243 -0
  106. package/src/__tests__/getLayers.test.ts +32 -18
  107. package/src/__tests__/getLayersFallback.test.ts +8 -8
  108. package/src/__tests__/imageEditVersions.test.ts +2 -0
  109. package/src/__tests__/jsonForm.pointer.test.ts +47 -1
  110. package/src/__tests__/layoutBlock.test.ts +3 -3
  111. package/src/__tests__/majorApiTypes.test.ts +18 -0
  112. package/src/__tests__/markdown.test.ts +41 -0
  113. package/src/__tests__/matchFontFamily.test.ts +114 -0
  114. package/src/__tests__/materializeBlockLayers.test.ts +135 -0
  115. package/src/__tests__/materializeTestUtils.ts +17 -0
  116. package/src/__tests__/narrationAlign.test.ts +150 -0
  117. package/src/__tests__/narrationFeatures.test.ts +71 -0
  118. package/src/__tests__/narrationNuclei.test.ts +45 -0
  119. package/src/__tests__/narrationPacing.test.ts +189 -0
  120. package/src/__tests__/narrationScript.test.ts +149 -0
  121. package/src/__tests__/narrationSessionRepro.test.ts +62 -0
  122. package/src/__tests__/narrationSidecar.test.ts +88 -0
  123. package/src/__tests__/narrationSyllables.test.ts +65 -0
  124. package/src/__tests__/narrationTestSignals.ts +167 -0
  125. package/src/__tests__/narrationVad.test.ts +66 -0
  126. package/src/__tests__/resolveDocTheme.test.ts +16 -2
  127. package/src/__tests__/rootExports.test.ts +18 -0
  128. package/src/__tests__/scopedContentContainer.test.ts +20 -0
  129. package/src/__tests__/shapeGeometry.test.ts +12 -0
  130. package/src/__tests__/templateAnnotationParse.test.ts +10 -0
  131. package/src/__tests__/templateInputs.test.ts +69 -0
  132. package/src/__tests__/templateMetadata.test.ts +2 -1
  133. package/src/__tests__/templates.test.ts +143 -17
  134. package/src/__tests__/themeCompile.test.ts +49 -22
  135. package/src/__tests__/transformProvenance.test.ts +160 -0
  136. package/src/__tests__/transformV2.test.ts +40 -14
  137. package/src/__tests__/treeviewBattle.test.ts +76 -0
  138. package/src/__tests__/treeviewDetect.test.ts +168 -0
  139. package/src/__tests__/treeviewParse.test.ts +118 -0
  140. package/src/__tests__/treeviewPipeline.test.ts +183 -0
  141. package/src/__tests__/treeviewRender.test.ts +91 -0
  142. package/src/__tests__/versions.test.ts +44 -0
  143. package/src/doc/applyNarrationTiming.ts +244 -0
  144. package/src/doc/asciiDiagram/chars.ts +210 -0
  145. package/src/doc/asciiDiagram/detect.ts +175 -0
  146. package/src/doc/asciiDiagram/index.ts +38 -0
  147. package/src/doc/asciiDiagram/mapping.ts +225 -0
  148. package/src/doc/asciiDiagram/parse.ts +1095 -0
  149. package/src/doc/asciiDiagram/render.ts +963 -0
  150. package/src/doc/asciiDiagram/repair.ts +400 -0
  151. package/src/doc/asciiDiagram/types.ts +65 -0
  152. package/src/doc/asciiTimeline/detect.ts +138 -0
  153. package/src/doc/asciiTimeline/index.ts +26 -0
  154. package/src/doc/asciiTimeline/mapping.ts +43 -0
  155. package/src/doc/asciiTimeline/parse.ts +870 -0
  156. package/src/doc/asciiTimeline/render.ts +427 -0
  157. package/src/doc/asciiTimeline/types.ts +73 -0
  158. package/src/doc/asciiTimeline/wrappedFlow.ts +427 -0
  159. package/src/doc/audioMapping.ts +60 -46
  160. package/src/doc/customTemplatesFrontmatter.ts +0 -41
  161. package/src/doc/docToMarkdown.ts +70 -2
  162. package/src/doc/index.ts +76 -4
  163. package/src/doc/markdownToDoc.ts +135 -1
  164. package/src/doc/materializeBlockLayers.ts +304 -0
  165. package/src/doc/mediaAnnotations.ts +20 -0
  166. package/src/doc/resolveDocTheme.ts +8 -7
  167. package/src/doc/templateInputs.ts +185 -2
  168. package/src/doc/templates/__tests__/customTemplateRegistry.test.ts +22 -8
  169. package/src/doc/templates/diagramBlock.ts +237 -38
  170. package/src/doc/templates/fallbackBlock.ts +1 -1
  171. package/src/doc/templates/index.ts +134 -279
  172. package/src/doc/templates/inputDescriptors.ts +20 -1
  173. package/src/doc/templates/metadata.ts +10 -0
  174. package/src/doc/templates/persistentLayers.ts +12 -126
  175. package/src/doc/templates/registry.ts +88 -0
  176. package/src/doc/templates/templateNames.ts +22 -0
  177. package/src/doc/templates/timelineBlock.ts +613 -0
  178. package/src/doc/templates/treeBlock.ts +131 -0
  179. package/src/doc/treeview/chars.ts +72 -0
  180. package/src/doc/treeview/detect.ts +145 -0
  181. package/src/doc/treeview/index.ts +30 -0
  182. package/src/doc/treeview/mapping.ts +95 -0
  183. package/src/doc/treeview/parse.ts +201 -0
  184. package/src/doc/treeview/render.ts +41 -0
  185. package/src/doc/treeview/types.ts +43 -0
  186. package/src/doc/utils/applyRenderStyle.ts +2 -9
  187. package/src/doc/utils/diagramText.ts +78 -0
  188. package/src/doc/utils/shapeGeometry.ts +67 -5
  189. package/src/doc/validate.ts +1 -1
  190. package/src/generate/templateMapper.ts +7 -0
  191. package/src/imageEdit/export.ts +6 -5
  192. package/src/imageEdit/versionPaths.ts +13 -26
  193. package/src/imageEdit/versions.ts +21 -100
  194. package/src/index.ts +1 -0
  195. package/src/internal/immutable.ts +58 -0
  196. package/src/jsonForm/index.ts +9 -1
  197. package/src/jsonForm/pointer.ts +68 -23
  198. package/src/markdown/attrTokens.ts +7 -5
  199. package/src/markdown/index.ts +1 -0
  200. package/src/markdown/stringify.ts +4 -9
  201. package/src/markdown/utils.ts +36 -14
  202. package/src/narration/align.ts +514 -0
  203. package/src/narration/features.ts +231 -0
  204. package/src/narration/index.ts +86 -0
  205. package/src/narration/nuclei.ts +118 -0
  206. package/src/narration/pacing.ts +211 -0
  207. package/src/narration/script.ts +239 -0
  208. package/src/narration/session.ts +100 -0
  209. package/src/narration/sidecar.ts +187 -0
  210. package/src/narration/syllables.ts +85 -0
  211. package/src/narration/trace.ts +50 -0
  212. package/src/narration/types.ts +299 -0
  213. package/src/narration/vad.ts +131 -0
  214. package/src/recommend/numberHighlight.ts +25 -0
  215. package/src/recommend/templates.ts +108 -12
  216. package/src/schemas/BlockTemplates.ts +156 -7
  217. package/src/schemas/Doc.ts +72 -9
  218. package/src/schemas/Media.ts +12 -2
  219. package/src/schemas/Theme.ts +45 -25
  220. package/src/schemas/Viewport.ts +2 -2
  221. package/src/schemas/fontStacks.ts +79 -0
  222. package/src/schemas/themeCompile.ts +15 -0
  223. package/src/schemas/themeConstants.ts +2 -0
  224. package/src/schemas/themeLibrary.ts +25 -19
  225. package/src/schemas/themeValidator.ts +1 -1
  226. package/src/spatial/Geohash.ts +0 -3
  227. package/src/storage/ContentContainer.ts +17 -9
  228. package/src/storage/ScopedContentContainer.ts +45 -15
  229. package/src/transform/applyTransform.ts +5 -5
  230. package/src/transform/index.ts +3 -1
  231. package/src/transform/registry.ts +169 -49
  232. package/src/transform/templateSelector.ts +21 -2
  233. package/src/transform/timingAllocator.ts +152 -21
  234. package/src/transform/types.ts +14 -1
  235. package/src/versions/gitignore.ts +34 -0
  236. package/src/versions/index.ts +2 -0
  237. package/src/versions/operations.ts +18 -104
  238. package/src/versions/paths.ts +13 -28
  239. package/src/versions/snapshotStore.ts +202 -0
  240. package/src/versions/types.ts +3 -1
  241. package/dist/chunk-5TYS5V3G.js.map +0 -1
  242. package/dist/chunk-A4O7GIWE.js.map +0 -1
  243. package/dist/chunk-BEQODJWV.js.map +0 -1
  244. package/dist/chunk-DLXZMS5K.js.map +0 -1
  245. package/dist/chunk-GPLTCSXO.js.map +0 -1
  246. package/dist/chunk-HWVFJAAH.js.map +0 -1
  247. package/dist/chunk-IMSCRLLV.js.map +0 -1
  248. package/dist/chunk-JELIRVDP.js +0 -24
  249. package/dist/chunk-JELIRVDP.js.map +0 -1
  250. package/dist/chunk-KKNUBQ6Y.js.map +0 -1
  251. package/dist/chunk-LDQ2HJIX.js.map +0 -1
  252. package/dist/chunk-LTN6I7QW.js.map +0 -1
  253. package/dist/chunk-P3K7NLHF.js.map +0 -1
  254. package/dist/chunk-T5UK6YOB.js.map +0 -1
  255. package/dist/chunk-UY7KGQ5R.js +0 -192
  256. package/dist/chunk-UY7KGQ5R.js.map +0 -1
  257. package/dist/chunk-VLJJHDAC.js +0 -218
  258. package/dist/chunk-VLJJHDAC.js.map +0 -1
  259. package/dist/chunk-VWUFZ6ZG.js.map +0 -1
  260. package/dist/story/index.d.ts +0 -1801
  261. package/dist/story/index.js +0 -279
  262. package/src/doc/getLayers.ts +0 -195
  263. /package/dist/{story → narration}/index.js.map +0 -0
@@ -0,0 +1,2503 @@
1
+ import {
2
+ extractPlainText,
3
+ findNodesByType,
4
+ walkMarkdownTree
5
+ } from "./chunk-AMIR72JP.js";
6
+
7
+ // src/doc/asciiDiagram/chars.ts
8
+ var TL_CORNERS = /* @__PURE__ */ new Set(["\u250C", "\u256D", "\u250F", "\u2554"]);
9
+ var TR_CORNERS = /* @__PURE__ */ new Set(["\u2510", "\u256E", "\u2513", "\u2557"]);
10
+ var BL_CORNERS = /* @__PURE__ */ new Set(["\u2514", "\u2570", "\u2517", "\u255A"]);
11
+ var BR_CORNERS = /* @__PURE__ */ new Set(["\u2518", "\u256F", "\u251B", "\u255D"]);
12
+ var HORIZ = /* @__PURE__ */ new Set(["\u2500", "\u2501", "\u2550", "-"]);
13
+ var VERT = /* @__PURE__ */ new Set(["\u2502", "\u2503", "\u2551", "|"]);
14
+ var JUNCTIONS = /* @__PURE__ */ new Set([
15
+ "\u251C",
16
+ "\u2524",
17
+ "\u252C",
18
+ "\u2534",
19
+ "\u253C",
20
+ "\u2523",
21
+ "\u252B",
22
+ "\u2533",
23
+ "\u253B",
24
+ "\u254B",
25
+ "\u2560",
26
+ "\u2563",
27
+ "\u2566",
28
+ "\u2569",
29
+ "\u256C",
30
+ "\u255E",
31
+ "\u2561",
32
+ "\u2565",
33
+ "\u2568",
34
+ "\u256B",
35
+ "\u256A"
36
+ ]);
37
+ var SIDE_JUNCTIONS = /* @__PURE__ */ new Set(["\u251C", "\u2524", "\u253C", "\u2523", "\u252B", "\u254B", "\u2560", "\u2563", "\u256C", "\u255E", "\u2561", "\u256B", "\u256A"]);
38
+ var FLAT_JUNCTIONS = /* @__PURE__ */ new Set(["\u252C", "\u2534", "\u253C", "\u2533", "\u253B", "\u254B", "\u2566", "\u2569", "\u256C", "\u2565", "\u2568", "\u256B", "\u256A"]);
39
+ var ARROW_UP = /* @__PURE__ */ new Set(["\u25B2", "\u2191"]);
40
+ var ARROW_DOWN = /* @__PURE__ */ new Set(["\u25BC", "\u2193"]);
41
+ var ARROW_LEFT = /* @__PURE__ */ new Set(["\u25C4", "\u2190", "\u25C0"]);
42
+ var ARROW_RIGHT = /* @__PURE__ */ new Set(["\u25BA", "\u2192", "\u25B6"]);
43
+ function isTlCorner(ch) {
44
+ return TL_CORNERS.has(ch);
45
+ }
46
+ function isTrCorner(ch) {
47
+ return TR_CORNERS.has(ch);
48
+ }
49
+ function isBlCorner(ch) {
50
+ return BL_CORNERS.has(ch);
51
+ }
52
+ function isBrCorner(ch) {
53
+ return BR_CORNERS.has(ch);
54
+ }
55
+ function isAnyCorner(ch) {
56
+ return TL_CORNERS.has(ch) || TR_CORNERS.has(ch) || BL_CORNERS.has(ch) || BR_CORNERS.has(ch);
57
+ }
58
+ function isHoriz(ch) {
59
+ return HORIZ.has(ch);
60
+ }
61
+ function isVert(ch) {
62
+ return VERT.has(ch);
63
+ }
64
+ function isJunction(ch) {
65
+ return JUNCTIONS.has(ch);
66
+ }
67
+ function isPlus(ch) {
68
+ return ch === "+";
69
+ }
70
+ function isFlatBorderChar(ch) {
71
+ return isHoriz(ch) || FLAT_JUNCTIONS.has(ch) || isPlus(ch);
72
+ }
73
+ function isSideBorderChar(ch) {
74
+ return isVert(ch) || SIDE_JUNCTIONS.has(ch) || isPlus(ch);
75
+ }
76
+ function arrowDirection(ch) {
77
+ if (ARROW_UP.has(ch)) return "up";
78
+ if (ARROW_DOWN.has(ch)) return "down";
79
+ if (ARROW_LEFT.has(ch)) return "left";
80
+ if (ARROW_RIGHT.has(ch)) return "right";
81
+ return null;
82
+ }
83
+ function asciiArrowDirection(ch) {
84
+ if (ch === "^") return "up";
85
+ if (ch === "v" || ch === "V") return "down";
86
+ if (ch === "<") return "left";
87
+ if (ch === ">") return "right";
88
+ return null;
89
+ }
90
+ function isLineChar(ch) {
91
+ return isHoriz(ch) || isVert(ch) || isJunction(ch) || isPlus(ch) || isAnyCorner(ch);
92
+ }
93
+ function isUnicodeStructural(ch) {
94
+ const code = ch.codePointAt(0) ?? 0;
95
+ return code >= 9472 && code <= 9599 || ARROW_UP.has(ch) || ARROW_DOWN.has(ch) || ch === "\u2190" || ch === "\u2192" || ch === "\u25C4" || ch === "\u25BA" || ch === "\u25C0" || ch === "\u25B6";
96
+ }
97
+ function isAsciiStructural(ch) {
98
+ return ch === "+" || ch === "-" || ch === "|";
99
+ }
100
+ var UNICODE_VOCAB = {
101
+ tl: "\u250C",
102
+ tr: "\u2510",
103
+ bl: "\u2514",
104
+ br: "\u2518",
105
+ h: "\u2500",
106
+ v: "\u2502",
107
+ stemDown: "\u252C",
108
+ stemUp: "\u2534",
109
+ stemLeft: "\u2524",
110
+ stemRight: "\u251C",
111
+ cross: "\u253C",
112
+ arrowUp: "\u25B2",
113
+ arrowDown: "\u25BC",
114
+ arrowLeft: "\u25C4",
115
+ arrowRight: "\u25BA",
116
+ // Named by the pair of open sides: e.g. turnDownRight opens downward and
117
+ // to the right (a line coming from below turning to the right) = '┌'.
118
+ turnDownRight: "\u250C",
119
+ turnDownLeft: "\u2510",
120
+ turnUpRight: "\u2514",
121
+ turnUpLeft: "\u2518"
122
+ };
123
+ var ASCII_VOCAB = {
124
+ tl: "+",
125
+ tr: "+",
126
+ bl: "+",
127
+ br: "+",
128
+ h: "-",
129
+ v: "|",
130
+ stemDown: "+",
131
+ stemUp: "+",
132
+ stemLeft: "+",
133
+ stemRight: "+",
134
+ cross: "+",
135
+ arrowUp: "^",
136
+ arrowDown: "v",
137
+ arrowLeft: "<",
138
+ arrowRight: ">",
139
+ turnDownRight: "+",
140
+ turnDownLeft: "+",
141
+ turnUpRight: "+",
142
+ turnUpLeft: "+"
143
+ };
144
+
145
+ // src/doc/asciiDiagram/parse.ts
146
+ var MAX_TITLE_RUN = 60;
147
+ var MAX_BRIDGE_GAP = 40;
148
+ var MAX_LOOSE_WARNINGS = 10;
149
+ var MAX_WALL_GAP = 3;
150
+ var REPAIR_WALL_GAP = 10;
151
+ var CROSS_CHARS = /* @__PURE__ */ new Set(["\u253C", "\u254B", "\u256C", "\u256B", "\u256A"]);
152
+ function parseAsciiDiagram(text) {
153
+ return parseAsciiDiagramWithStats(text).diagram;
154
+ }
155
+ function parseAsciiDiagramWithStats(text) {
156
+ const warnings = [];
157
+ const grid = buildGrid(text, warnings);
158
+ const H = grid.length;
159
+ const W = H > 0 ? grid[0].length : 0;
160
+ let totalNonSpaceCells = 0;
161
+ let hasWideChars = false;
162
+ for (let r = 0; r < H; r++) {
163
+ for (let c = 0; c < W; c++) {
164
+ const ch = grid[r][c];
165
+ if (ch !== " ") totalNonSpaceCells++;
166
+ if (!hasWideChars && isWideChar(ch)) hasWideChars = true;
167
+ }
168
+ }
169
+ if (hasWideChars) warnings.push("wide-chars");
170
+ const style = detectStyle(grid);
171
+ const rects = traceBoxes(grid);
172
+ const boxes = buildBoxes(grid, rects, warnings);
173
+ const mask = new Uint8Array(W * H);
174
+ const perimeterOwners = /* @__PURE__ */ new Map();
175
+ for (let b = 0; b < boxes.length; b++) {
176
+ const box = boxes[b];
177
+ for (let c = box.c0; c <= box.c1; c++) {
178
+ for (const r of [box.r0, box.r1]) {
179
+ mask[r * W + c] = 1;
180
+ const owners = perimeterOwners.get(r * W + c) ?? [];
181
+ owners.push(b);
182
+ perimeterOwners.set(r * W + c, owners);
183
+ }
184
+ }
185
+ for (let r = box.r0 + 1; r <= box.r1 - 1; r++) {
186
+ for (const c of [box.c0, box.c1]) {
187
+ mask[r * W + c] = 1;
188
+ const owners = perimeterOwners.get(r * W + c) ?? [];
189
+ owners.push(b);
190
+ perimeterOwners.set(r * W + c, owners);
191
+ }
192
+ }
193
+ }
194
+ const isContainer = boxes.map((_, i) => boxes.some((b) => b.parentIdx === i));
195
+ for (let b = 0; b < boxes.length; b++) {
196
+ const box = boxes[b];
197
+ if (!isContainer[b]) {
198
+ for (let r = box.r0 + 1; r <= box.r1 - 1; r++) {
199
+ for (let c = box.c0 + 1; c <= box.c1 - 1; c++) mask[r * W + c] = 1;
200
+ }
201
+ } else {
202
+ const minChildR0 = Math.min(
203
+ ...boxes.filter((child) => child.parentIdx === b).map((child) => child.r0)
204
+ );
205
+ for (let r = box.r0 + 1; r < minChildR0; r++) {
206
+ for (let c = box.c0 + 1; c <= box.c1 - 1; c++) {
207
+ const ch = grid[r][c];
208
+ if (ch !== " " && !isLineChar(ch) && arrowDirection(ch) === null) mask[r * W + c] = 1;
209
+ }
210
+ }
211
+ }
212
+ }
213
+ const { edges, edgeCells } = extractEdges(grid, mask, perimeterOwners, boxes, warnings);
214
+ let looseNonSpaceCells = 0;
215
+ const looseRuns = [];
216
+ for (let r = 0; r < H; r++) {
217
+ let run = "";
218
+ const flush = () => {
219
+ if (run.trim().length > 0) looseRuns.push(`@${r}: ${truncate(run.trim(), 30)}`);
220
+ run = "";
221
+ };
222
+ const isLooseCell = (c) => {
223
+ if (c < 0 || c >= W) return false;
224
+ const idx = r * W + c;
225
+ return grid[r][c] !== " " && !mask[idx] && !edgeCells.has(idx);
226
+ };
227
+ for (let c = 0; c < W; c++) {
228
+ if (isLooseCell(c)) {
229
+ run += grid[r][c];
230
+ looseNonSpaceCells++;
231
+ } else if (run !== "" && grid[r][c] === " " && isLooseCell(c + 1)) {
232
+ run += " ";
233
+ } else {
234
+ flush();
235
+ }
236
+ }
237
+ flush();
238
+ }
239
+ for (const runText of looseRuns.slice(0, MAX_LOOSE_WARNINGS)) {
240
+ warnings.push(`loose-text ${runText}`);
241
+ }
242
+ if (looseRuns.length > MAX_LOOSE_WARNINGS) {
243
+ warnings.push(`loose-text (+${looseRuns.length - MAX_LOOSE_WARNINGS} more)`);
244
+ }
245
+ const nodes = boxes.map((box) => ({
246
+ id: box.id,
247
+ label: box.labelLines.join("\n"),
248
+ col: box.c0,
249
+ row: box.r0,
250
+ wCols: box.c1 - box.c0 + 1,
251
+ hRows: box.r1 - box.r0 + 1,
252
+ ...box.parentIdx >= 0 ? { containerId: boxes[box.parentIdx].id } : {}
253
+ }));
254
+ return {
255
+ diagram: { nodes, edges, style, warnings },
256
+ stats: { totalNonSpaceCells, looseNonSpaceCells, hasWideChars }
257
+ };
258
+ }
259
+ function buildGrid(text, warnings) {
260
+ const rawLines = text.replace(/\r\n?/g, "\n").split("\n");
261
+ let hadTab = false;
262
+ const rows = rawLines.map((line) => {
263
+ const cells = [];
264
+ for (const ch of line) {
265
+ if (ch === " ") {
266
+ hadTab = true;
267
+ do {
268
+ cells.push(" ");
269
+ } while (cells.length % 4 !== 0);
270
+ } else {
271
+ cells.push(ch);
272
+ }
273
+ }
274
+ return cells;
275
+ });
276
+ if (hadTab) warnings.push("tabs-expanded");
277
+ let width = 0;
278
+ for (const row of rows) width = Math.max(width, row.length);
279
+ for (const row of rows) {
280
+ while (row.length < width) row.push(" ");
281
+ }
282
+ return rows;
283
+ }
284
+ function isWideChar(ch) {
285
+ const c = ch.codePointAt(0) ?? 0;
286
+ return c >= 4352 && c <= 4447 || c >= 11904 && c <= 12350 || c >= 12353 && c <= 13311 || c >= 13312 && c <= 19903 || c >= 19968 && c <= 40959 || c >= 44032 && c <= 55203 || c >= 63744 && c <= 64255 || c >= 65072 && c <= 65103 || c >= 65280 && c <= 65376 || c >= 127744 && c <= 129791;
287
+ }
288
+ function detectStyle(grid) {
289
+ let unicode = 0;
290
+ let ascii = 0;
291
+ for (const row of grid) {
292
+ for (const ch of row) {
293
+ if (isUnicodeStructural(ch)) unicode++;
294
+ else if (isAsciiStructural(ch)) ascii++;
295
+ }
296
+ }
297
+ return ascii > unicode ? "ascii" : "unicode";
298
+ }
299
+ function traceBoxes(grid, repair = false) {
300
+ const H = grid.length;
301
+ const W = H > 0 ? grid[0].length : 0;
302
+ const found = [];
303
+ for (let r = 0; r < H; r++) {
304
+ for (let c = 0; c < W; c++) {
305
+ if (!isTlCandidate(grid, r, c)) continue;
306
+ const rect = traceBoxFrom(grid, r, c, repair);
307
+ if (rect) found.push(rect);
308
+ }
309
+ }
310
+ return found;
311
+ }
312
+ function toGrid(text) {
313
+ return buildGrid(text, []);
314
+ }
315
+ function isTlCandidate(grid, r, c) {
316
+ const ch = grid[r][c];
317
+ if (isTlCorner(ch)) return true;
318
+ if (!isPlus(ch) && !isJunction(ch)) return false;
319
+ const right = grid[r][c + 1] ?? " ";
320
+ const below = grid[r + 1]?.[c] ?? " ";
321
+ const rightOk = isFlatBorderChar(right) || isTrCorner(right);
322
+ const belowOk = isSideBorderChar(below) || isBlCorner(below);
323
+ return rightOk && belowOk;
324
+ }
325
+ function traceBoxFrom(grid, r0, c0, repair = false) {
326
+ const H = grid.length;
327
+ const W = grid[0]?.length ?? 0;
328
+ const trCandidates = [];
329
+ let titleRun = null;
330
+ let c = c0 + 1;
331
+ while (c < W) {
332
+ const ch = grid[r0][c];
333
+ if (isTrCorner(ch)) {
334
+ trCandidates.push(c);
335
+ break;
336
+ }
337
+ if (isPlus(ch) || isJunction(ch)) {
338
+ trCandidates.push(c);
339
+ c++;
340
+ continue;
341
+ }
342
+ if (isFlatBorderChar(ch)) {
343
+ c++;
344
+ continue;
345
+ }
346
+ if (arrowDirection(ch) !== null) {
347
+ c++;
348
+ continue;
349
+ }
350
+ if (titleRun) break;
351
+ let end = c;
352
+ let sawText = false;
353
+ while (end < W && end - c <= MAX_TITLE_RUN) {
354
+ const tch = grid[r0][end];
355
+ if (isFlatBorderChar(tch) || isTrCorner(tch) || arrowDirection(tch) !== null) break;
356
+ if (tch !== " ") sawText = true;
357
+ end++;
358
+ }
359
+ if (end >= W || end - c > MAX_TITLE_RUN || !sawText || end === c) break;
360
+ titleRun = { from: c, to: end - 1 };
361
+ c = end;
362
+ }
363
+ if (trCandidates.length === 0) return null;
364
+ const leftCandidates = walkSideWall(grid, c0, r0, H, isBlCorner, repair);
365
+ if (leftCandidates.length === 0) return null;
366
+ const leftSet = new Set(leftCandidates);
367
+ for (const c1 of trCandidates) {
368
+ if (c1 < c0 + 2) continue;
369
+ const rightCandidates = walkSideWall(grid, c1, r0, H, isBrCorner, repair);
370
+ for (const r1 of rightCandidates) {
371
+ if (r1 < r0 + 2) continue;
372
+ if (!leftSet.has(r1)) continue;
373
+ let ok = true;
374
+ for (let bc = c0 + 1; bc <= c1 - 1; bc++) {
375
+ if (!isFlatBorderChar(grid[r1][bc])) {
376
+ ok = false;
377
+ break;
378
+ }
379
+ }
380
+ if (!ok) continue;
381
+ const win = repair ? 1 : 0;
382
+ let leftBorders = 0;
383
+ let rightBorders = 0;
384
+ for (let ir = r0 + 1; ir <= r1 - 1; ir++) {
385
+ if (hasSideBorderNear(grid[ir], c0, win)) leftBorders++;
386
+ if (hasSideBorderNear(grid[ir], c1, win)) rightBorders++;
387
+ }
388
+ if (leftBorders === 0 || rightBorders === 0) continue;
389
+ const title = titleRun && titleRun.to < c1 ? grid[r0].slice(titleRun.from, titleRun.to + 1).join("").trim() : void 0;
390
+ return { r0, c0, r1, c1, ...title ? { borderTitle: title } : {} };
391
+ }
392
+ }
393
+ return null;
394
+ }
395
+ function hasSideBorderNear(row, col, win) {
396
+ for (let c = col - win; c <= col + win; c++) {
397
+ const ch = row[c];
398
+ if (ch !== void 0 && isSideBorderChar(ch)) return true;
399
+ }
400
+ return false;
401
+ }
402
+ function walkSideWall(grid, col, r0, H, isCorner, repair = false) {
403
+ const candidates = [];
404
+ const maxGap = repair ? REPAIR_WALL_GAP : MAX_WALL_GAP;
405
+ let gap = 0;
406
+ for (let r = r0 + 1; r < H; r++) {
407
+ const ch = grid[r][col];
408
+ if (isCorner(ch)) {
409
+ candidates.push(r);
410
+ break;
411
+ }
412
+ if (isPlus(ch) || isJunction(ch)) {
413
+ candidates.push(r);
414
+ gap = 0;
415
+ continue;
416
+ }
417
+ if (isSideBorderChar(ch)) {
418
+ gap = 0;
419
+ continue;
420
+ }
421
+ if (!repair && (isLineChar(ch) || arrowDirection(ch) !== null)) break;
422
+ if (++gap > maxGap) break;
423
+ }
424
+ return candidates;
425
+ }
426
+ function strictlyContains(parent, child) {
427
+ return parent.r0 < child.r0 && parent.c0 < child.c0 && parent.r1 > child.r1 && parent.c1 > child.c1;
428
+ }
429
+ function interiorsOverlap(a, b) {
430
+ return a.r0 + 1 <= b.r1 - 1 && b.r0 + 1 <= a.r1 - 1 && a.c0 + 1 <= b.c1 - 1 && b.c0 + 1 <= a.c1 - 1;
431
+ }
432
+ function area(rect) {
433
+ return (rect.r1 - rect.r0 + 1) * (rect.c1 - rect.c0 + 1);
434
+ }
435
+ function buildBoxes(grid, rects, warnings) {
436
+ const sorted = [...rects].sort((a, b) => a.r0 - b.r0 || a.c0 - b.c0);
437
+ const parentIdx = sorted.map((child, i) => {
438
+ let best = -1;
439
+ let bestArea = Infinity;
440
+ for (let j = 0; j < sorted.length; j++) {
441
+ if (j === i) continue;
442
+ if (!strictlyContains(sorted[j], child)) continue;
443
+ const a = area(sorted[j]);
444
+ if (a < bestArea) {
445
+ bestArea = a;
446
+ best = j;
447
+ }
448
+ }
449
+ return best;
450
+ });
451
+ let warnedOverlap = false;
452
+ for (let i = 0; i < sorted.length && !warnedOverlap; i++) {
453
+ for (let j = i + 1; j < sorted.length; j++) {
454
+ if (interiorsOverlap(sorted[i], sorted[j]) && !strictlyContains(sorted[i], sorted[j]) && !strictlyContains(sorted[j], sorted[i])) {
455
+ warnings.push("overlapping-boxes");
456
+ warnedOverlap = true;
457
+ break;
458
+ }
459
+ }
460
+ }
461
+ const hasChildren = sorted.map((_, i) => parentIdx.includes(i));
462
+ const usedIds = /* @__PURE__ */ new Map();
463
+ const boxes = sorted.map((rect, i) => {
464
+ const labelLines = [];
465
+ if (rect.borderTitle) labelLines.push(rect.borderTitle);
466
+ if (!hasChildren[i]) {
467
+ for (let r = rect.r0 + 1; r <= rect.r1 - 1; r++) {
468
+ const rowText = grid[r].slice(rect.c0 + 1, rect.c1).join("");
469
+ if (isDividerRow(rowText)) continue;
470
+ const trimmed = rowText.trim();
471
+ if (trimmed.length > 0) labelLines.push(trimmed);
472
+ }
473
+ } else {
474
+ const minChildR0 = Math.min(
475
+ ...sorted.filter((_, j) => parentIdx[j] === i).map((child) => child.r0)
476
+ );
477
+ for (let r = rect.r0 + 1; r < minChildR0; r++) {
478
+ const rowText = grid[r].slice(rect.c0 + 1, rect.c1).map((ch) => isLineChar(ch) || arrowDirection(ch) !== null ? " " : ch).join("").trim();
479
+ if (rowText.length > 0) labelLines.push(rowText);
480
+ }
481
+ }
482
+ const base = slugify(labelLines[0] ?? "");
483
+ const count = usedIds.get(base) ?? 0;
484
+ usedIds.set(base, count + 1);
485
+ const id = count === 0 ? base : `${base}-${count + 1}`;
486
+ return { ...rect, parentIdx: parentIdx[i], labelLines, id };
487
+ });
488
+ return boxes;
489
+ }
490
+ function isDividerRow(rowText) {
491
+ let sawLine = false;
492
+ for (const ch of rowText) {
493
+ if (ch === " ") continue;
494
+ if (isHoriz(ch) || isJunction(ch) || isPlus(ch)) {
495
+ sawLine = true;
496
+ continue;
497
+ }
498
+ return false;
499
+ }
500
+ return sawLine;
501
+ }
502
+ function slugify(s) {
503
+ const slug2 = s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
504
+ return slug2 || "node";
505
+ }
506
+ function extractEdges(grid, mask, perimeterOwners, boxes, warnings) {
507
+ const H = grid.length;
508
+ const W = H > 0 ? grid[0].length : 0;
509
+ const cells = /* @__PURE__ */ new Map();
510
+ for (let r = 0; r < H; r++) {
511
+ for (let c = 0; c < W; c++) {
512
+ const idx = r * W + c;
513
+ if (mask[idx]) continue;
514
+ const ch = grid[r][c];
515
+ const uni = arrowDirection(ch);
516
+ if (uni) {
517
+ cells.set(idx, { arrow: uni });
518
+ continue;
519
+ }
520
+ const asc = asciiArrowDirection(ch);
521
+ if (asc && asciiArrowGate(grid, r, c, asc)) {
522
+ cells.set(idx, { arrow: asc });
523
+ continue;
524
+ }
525
+ if (isLineChar(ch)) cells.set(idx, { arrow: null });
526
+ }
527
+ }
528
+ for (let r = 0; r < H; r++) {
529
+ let c = 0;
530
+ while (c < W) {
531
+ const idx = r * W + c;
532
+ if (!cells.has(idx) || !isHoriz(grid[r][c])) {
533
+ c++;
534
+ continue;
535
+ }
536
+ let runEnd = c;
537
+ while (runEnd + 1 < W && cells.has(r * W + runEnd + 1) && isHoriz(grid[r][runEnd + 1])) {
538
+ runEnd++;
539
+ }
540
+ let g = runEnd + 1;
541
+ let sawText = false;
542
+ let failed = false;
543
+ while (g < W && g - runEnd - 1 <= MAX_BRIDGE_GAP) {
544
+ const gch = grid[r][g];
545
+ const gidx = r * W + g;
546
+ if (mask[gidx] || isVert(gch)) {
547
+ failed = true;
548
+ break;
549
+ }
550
+ if (isHoriz(gch)) break;
551
+ if (isLineChar(gch) || arrowDirection(gch) !== null) {
552
+ failed = true;
553
+ break;
554
+ }
555
+ const asc = asciiArrowDirection(gch);
556
+ if (asc && asciiArrowGate(grid, r, g, asc)) {
557
+ failed = true;
558
+ break;
559
+ }
560
+ if (gch !== " ") sawText = true;
561
+ g++;
562
+ }
563
+ if (!failed && g < W && g > runEnd + 1 && sawText && isHoriz(grid[r][g]) && !mask[r * W + g]) {
564
+ const label = grid[r].slice(runEnd + 1, g).join("").trim();
565
+ for (let bc = runEnd + 1; bc < g; bc++) {
566
+ cells.set(r * W + bc, { arrow: null, bridgeLabel: label });
567
+ }
568
+ c = g;
569
+ } else {
570
+ c = runEnd + 1;
571
+ }
572
+ }
573
+ }
574
+ const isCrossCell = (idx) => CROSS_CHARS.has(grid[Math.floor(idx / W)][idx % W]);
575
+ const stateKey = (idx, channel) => idx * 2 + channel;
576
+ const visited = /* @__PURE__ */ new Set();
577
+ const components = [];
578
+ const seeds = [];
579
+ for (const idx of cells.keys()) {
580
+ seeds.push([idx, 0]);
581
+ if (isCrossCell(idx)) seeds.push([idx, 1]);
582
+ }
583
+ for (const [seedIdx, seedChannel] of seeds) {
584
+ if (visited.has(stateKey(seedIdx, seedChannel))) continue;
585
+ const componentCells = /* @__PURE__ */ new Set();
586
+ const queue = [[seedIdx, seedChannel]];
587
+ visited.add(stateKey(seedIdx, seedChannel));
588
+ while (queue.length > 0) {
589
+ const [idx, channel] = queue.pop();
590
+ componentCells.add(idx);
591
+ const r = Math.floor(idx / W);
592
+ const c = idx % W;
593
+ const neighbors = isCrossCell(idx) && channel === 1 ? [
594
+ [r - 1, c],
595
+ [r + 1, c]
596
+ ] : isCrossCell(idx) ? [
597
+ [r, c - 1],
598
+ [r, c + 1]
599
+ ] : [
600
+ [r - 1, c],
601
+ [r + 1, c],
602
+ [r, c - 1],
603
+ [r, c + 1]
604
+ ];
605
+ for (const [nr, nc] of neighbors) {
606
+ if (nr < 0 || nr >= H || nc < 0 || nc >= W) continue;
607
+ const nidx = nr * W + nc;
608
+ if (!cells.has(nidx)) continue;
609
+ const nChannel = isCrossCell(nidx) ? nr !== r ? 1 : 0 : 0;
610
+ const key = stateKey(nidx, nChannel);
611
+ if (!visited.has(key)) {
612
+ visited.add(key);
613
+ queue.push([nidx, nChannel]);
614
+ }
615
+ }
616
+ }
617
+ components.push([...componentCells].sort((a, b) => a - b));
618
+ }
619
+ components.sort((a, b) => a[0] - b[0]);
620
+ const edges = [];
621
+ const seen = /* @__PURE__ */ new Set();
622
+ let mergedParallel = false;
623
+ const looseComponents = /* @__PURE__ */ new Set();
624
+ const sideLabelCells = /* @__PURE__ */ new Set();
625
+ for (const component of components) {
626
+ const inComponent = new Set(component);
627
+ const endpoints = [];
628
+ for (const idx of component) {
629
+ const r = Math.floor(idx / W);
630
+ const c = idx % W;
631
+ let neighbors = 0;
632
+ for (const [nr, nc] of [
633
+ [r - 1, c],
634
+ [r + 1, c],
635
+ [r, c - 1],
636
+ [r, c + 1]
637
+ ]) {
638
+ if (inComponent.has(nr * W + nc)) neighbors++;
639
+ }
640
+ const info = cells.get(idx);
641
+ if (neighbors <= 1 || info.arrow !== null) endpoints.push(idx);
642
+ }
643
+ const attachmentsByBox = /* @__PURE__ */ new Map();
644
+ let dangling = false;
645
+ for (const idx of endpoints) {
646
+ const r = Math.floor(idx / W);
647
+ const c = idx % W;
648
+ const info = cells.get(idx);
649
+ const boxIdx = snapEndpoint(r, c, info.arrow, perimeterOwners, boxes, W, H);
650
+ if (boxIdx === null) {
651
+ dangling = true;
652
+ continue;
653
+ }
654
+ const existing = attachmentsByBox.get(boxIdx);
655
+ if (existing) {
656
+ existing.isArrowEnd = existing.isArrowEnd || info.arrow !== null;
657
+ } else {
658
+ attachmentsByBox.set(boxIdx, { boxIdx, isArrowEnd: info.arrow !== null });
659
+ }
660
+ }
661
+ const nearestOwner = (r, c) => {
662
+ const owners = perimeterOwners.get(r * W + c) ?? [];
663
+ if (owners.length === 0) return null;
664
+ return [...owners].sort(
665
+ (a, b) => boxes[a].r0 - boxes[b].r0 || boxes[a].c0 - boxes[b].c0 || a - b
666
+ )[0];
667
+ };
668
+ for (const idx of component) {
669
+ const r = Math.floor(idx / W);
670
+ const c = idx % W;
671
+ const ch = grid[r][c];
672
+ if (!isJunction(ch) && !isPlus(ch)) continue;
673
+ for (const [dr, dc] of [
674
+ [-1, 0],
675
+ [1, 0],
676
+ [0, -1],
677
+ [0, 1]
678
+ ]) {
679
+ const boxIdx = nearestOwner(r + dr, c + dc);
680
+ if (boxIdx !== null && !attachmentsByBox.has(boxIdx)) {
681
+ attachmentsByBox.set(boxIdx, { boxIdx, isArrowEnd: false });
682
+ }
683
+ }
684
+ }
685
+ for (const idx of component) {
686
+ const r = Math.floor(idx / W);
687
+ const c = idx % W;
688
+ const info = cells.get(idx);
689
+ if (info.arrow === null) continue;
690
+ const [dr, dc] = info.arrow === "down" ? [-1, 0] : info.arrow === "up" ? [1, 0] : info.arrow === "right" ? [0, -1] : [0, 1];
691
+ const boxIdx = nearestOwner(r + dr, c + dc);
692
+ if (boxIdx !== null && !attachmentsByBox.has(boxIdx)) {
693
+ attachmentsByBox.set(boxIdx, { boxIdx, isArrowEnd: false });
694
+ }
695
+ }
696
+ const attachments = [...attachmentsByBox.values()].sort(
697
+ (a, b) => boxes[a.boxIdx].r0 - boxes[b.boxIdx].r0 || boxes[a.boxIdx].c0 - boxes[b.boxIdx].c0
698
+ );
699
+ if (attachments.length === 0) {
700
+ for (const idx of component) looseComponents.add(idx);
701
+ warnings.push("dangling-edge");
702
+ continue;
703
+ }
704
+ if (attachments.length === 1) {
705
+ warnings.push("dangling-edge");
706
+ continue;
707
+ }
708
+ if (dangling) warnings.push("dangling-edge");
709
+ const bridgeLabels = [];
710
+ for (const idx of component) {
711
+ const info = cells.get(idx);
712
+ if (info.bridgeLabel && !bridgeLabels.includes(info.bridgeLabel)) {
713
+ bridgeLabels.push(info.bridgeLabel);
714
+ }
715
+ }
716
+ const isLabelStop = (ch) => isLineChar(ch) && ch !== "-" || arrowDirection(ch) !== null;
717
+ const hasAlnum = (s) => /[a-z0-9]/i.test(s);
718
+ let label = bridgeLabels[0];
719
+ if (!label) {
720
+ outer: for (const idx of component) {
721
+ const r = Math.floor(idx / W);
722
+ const c = idx % W;
723
+ if (!isVert(grid[r][c])) continue;
724
+ if ((grid[r][c + 1] ?? "") === " ") {
725
+ let end = c + 2;
726
+ let run = "";
727
+ while (end < W) {
728
+ const ch = grid[r][end];
729
+ if (mask[r * W + end] || isLabelStop(ch)) break;
730
+ if (ch === " " && (grid[r][end + 1] ?? " ") === " ") break;
731
+ run += ch;
732
+ end++;
733
+ }
734
+ const clean = run.trim().replace(/^-+|-+$/g, "");
735
+ if (clean.length > 0 && clean.length <= MAX_BRIDGE_GAP && hasAlnum(clean)) {
736
+ label = clean;
737
+ for (let lc = c + 2; lc < end; lc++) sideLabelCells.add(r * W + lc);
738
+ break outer;
739
+ }
740
+ }
741
+ if ((grid[r][c - 1] ?? "") === " ") {
742
+ let start = c - 2;
743
+ let run = "";
744
+ while (start >= 0) {
745
+ const ch = grid[r][start];
746
+ if (mask[r * W + start] || isLabelStop(ch)) break;
747
+ if (ch === " " && (grid[r][start - 1] ?? " ") === " ") break;
748
+ run = ch + run;
749
+ start--;
750
+ }
751
+ const clean = run.trim().replace(/^-+|-+$/g, "");
752
+ if (clean.length > 0 && clean.length <= MAX_BRIDGE_GAP && hasAlnum(clean)) {
753
+ label = clean;
754
+ for (let lc = start + 1; lc <= c - 2; lc++) sideLabelCells.add(r * W + lc);
755
+ break outer;
756
+ }
757
+ }
758
+ }
759
+ }
760
+ if (bridgeLabels.length > 1) warnings.push("multiple-edge-labels");
761
+ const pushEdge = (sourceIdx, targetIdx, directed) => {
762
+ const edge = {
763
+ source: boxes[sourceIdx].id,
764
+ target: boxes[targetIdx].id,
765
+ ...label ? { label } : {},
766
+ directed
767
+ };
768
+ const key = `${edge.source}|${edge.target}|${edge.directed}|${edge.label ?? ""}`;
769
+ if (seen.has(key)) {
770
+ mergedParallel = true;
771
+ return;
772
+ }
773
+ seen.add(key);
774
+ edges.push(edge);
775
+ };
776
+ const arrowEnds = attachments.filter((a) => a.isArrowEnd);
777
+ const plainEnds = attachments.filter((a) => !a.isArrowEnd);
778
+ if (attachments.length === 2) {
779
+ const [a, b] = attachments;
780
+ if (arrowEnds.length === 2) {
781
+ pushEdge(a.boxIdx, b.boxIdx, true);
782
+ pushEdge(b.boxIdx, a.boxIdx, true);
783
+ } else if (arrowEnds.length === 1) {
784
+ const target = arrowEnds[0];
785
+ const source = a === target ? b : a;
786
+ pushEdge(source.boxIdx, target.boxIdx, true);
787
+ } else {
788
+ pushEdge(a.boxIdx, b.boxIdx, false);
789
+ }
790
+ } else if (plainEnds.length === 1 && arrowEnds.length >= 1) {
791
+ for (const target of arrowEnds) pushEdge(plainEnds[0].boxIdx, target.boxIdx, true);
792
+ } else if (arrowEnds.length === 1 && plainEnds.length >= 1) {
793
+ for (const source of plainEnds) pushEdge(source.boxIdx, arrowEnds[0].boxIdx, true);
794
+ } else if (arrowEnds.length === 0) {
795
+ warnings.push("ambiguous-bus");
796
+ for (let i = 1; i < attachments.length; i++) {
797
+ pushEdge(attachments[0].boxIdx, attachments[i].boxIdx, false);
798
+ }
799
+ } else {
800
+ warnings.push("ambiguous-bus");
801
+ for (const target of arrowEnds) {
802
+ if (plainEnds.length > 0) pushEdge(plainEnds[0].boxIdx, target.boxIdx, true);
803
+ }
804
+ }
805
+ }
806
+ if (mergedParallel) warnings.push("parallel-edges-merged");
807
+ edges.sort(
808
+ (a, b) => a.source.localeCompare(b.source) || a.target.localeCompare(b.target) || (a.label ?? "").localeCompare(b.label ?? "")
809
+ );
810
+ const edgeCells = /* @__PURE__ */ new Set();
811
+ for (const component of components) {
812
+ for (const idx of component) {
813
+ if (!looseComponents.has(idx)) edgeCells.add(idx);
814
+ }
815
+ }
816
+ for (const idx of sideLabelCells) edgeCells.add(idx);
817
+ return { edges, edgeCells };
818
+ }
819
+ function asciiArrowGate(grid, r, c, dir) {
820
+ const behind = dir === "down" ? grid[r - 1]?.[c] : dir === "up" ? grid[r + 1]?.[c] : dir === "right" ? grid[r]?.[c - 1] : grid[r]?.[c + 1];
821
+ if (behind === void 0) return false;
822
+ return dir === "down" || dir === "up" ? isVert(behind) : isHoriz(behind);
823
+ }
824
+ function snapEndpoint(r, c, arrow, perimeterOwners, boxes, W, H) {
825
+ const ownersAt = (rr, cc) => {
826
+ if (rr < 0 || rr >= H || cc < 0 || cc >= W) return [];
827
+ return perimeterOwners.get(rr * W + cc) ?? [];
828
+ };
829
+ if (arrow) {
830
+ const [dr, dc] = arrow === "down" ? [1, 0] : arrow === "up" ? [-1, 0] : arrow === "right" ? [0, 1] : [0, -1];
831
+ for (const dist of [1, 2]) {
832
+ const pointed = ownersAt(r + dr * dist, c + dc * dist);
833
+ if (pointed.length > 0) {
834
+ const sorted = [...pointed].sort((a, b) => {
835
+ const da = directionDepth(boxes[a], r + dr * dist, c + dc * dist, arrow);
836
+ const db = directionDepth(boxes[b], r + dr * dist, c + dc * dist, arrow);
837
+ return db - da || a - b;
838
+ });
839
+ return sorted[0];
840
+ }
841
+ }
842
+ }
843
+ const candidates = /* @__PURE__ */ new Map();
844
+ for (const [dr, dc, priority] of [
845
+ [-1, 0, 0],
846
+ [1, 0, 0],
847
+ [0, -1, 0],
848
+ [0, 1, 0],
849
+ [-1, -1, 1],
850
+ [-1, 1, 1],
851
+ [1, -1, 1],
852
+ [1, 1, 1],
853
+ [-2, 0, 2],
854
+ [2, 0, 2],
855
+ [0, -2, 2],
856
+ [0, 2, 2]
857
+ ]) {
858
+ for (const owner of ownersAt(r + dr, c + dc)) {
859
+ const existing = candidates.get(owner);
860
+ if (existing === void 0 || priority < existing) candidates.set(owner, priority);
861
+ }
862
+ }
863
+ if (candidates.size === 0) return null;
864
+ const ranked = [...candidates.entries()].sort(
865
+ (a, b) => a[1] - b[1] || boxes[a[0]].r0 - boxes[b[0]].r0 || boxes[a[0]].c0 - boxes[b[0]].c0 || a[0] - b[0]
866
+ );
867
+ return ranked[0][0];
868
+ }
869
+ function directionDepth(box, r, c, arrow) {
870
+ switch (arrow) {
871
+ case "down":
872
+ return box.r0 === r ? 1 : 0;
873
+ // arrow points down at the box's TOP border
874
+ case "up":
875
+ return box.r1 === r ? 1 : 0;
876
+ case "right":
877
+ return box.c0 === c ? 1 : 0;
878
+ case "left":
879
+ return box.c1 === c ? 1 : 0;
880
+ }
881
+ }
882
+ function truncate(s, max) {
883
+ return s.length > max ? `${s.slice(0, max - 1)}\u2026` : s;
884
+ }
885
+
886
+ // src/doc/asciiDiagram/detect.ts
887
+ var ASCII_DIAGRAM_FENCE_LANGS = /* @__PURE__ */ new Set([
888
+ "text",
889
+ "txt",
890
+ "plaintext",
891
+ "plain",
892
+ "ascii",
893
+ "diagram"
894
+ ]);
895
+ var MAX_LINES = 400;
896
+ var MAX_COLS = 400;
897
+ var MIN_LINES = 3;
898
+ var MAX_LOOSE_RATIO = 0.25;
899
+ var STRUCTURAL_IN_LABEL = /[│┃║┌┐└┘├┤┬┴┼╭╮╰╯┏┓┗┛╔╗╚╝┣┫┳┻╋╠╣╦╩╬╞╡╥╨╫╪]/;
900
+ function isEligibleAsciiFenceLang(lang) {
901
+ if (lang === null || lang === void 0) return true;
902
+ const normalized = lang.trim().toLowerCase();
903
+ return normalized === "" || ASCII_DIAGRAM_FENCE_LANGS.has(normalized);
904
+ }
905
+ function isExplicitDiagramLang(lang) {
906
+ return typeof lang === "string" && lang.trim().toLowerCase() === "diagram";
907
+ }
908
+ function detectAsciiDiagram(text, opts = {}) {
909
+ const explicit = opts.explicit === true;
910
+ const reasons = [];
911
+ const lines = text.replace(/\r\n?/g, "\n").split("\n");
912
+ if (lines.length < MIN_LINES) {
913
+ return { isDiagram: false, reasons: [`too-few-lines(${lines.length})`] };
914
+ }
915
+ if (lines.length > MAX_LINES) {
916
+ return { isDiagram: false, reasons: [`too-many-lines(${lines.length})`] };
917
+ }
918
+ let maxCols = 0;
919
+ for (const line of lines) maxCols = Math.max(maxCols, line.length);
920
+ if (maxCols > MAX_COLS) {
921
+ return { isDiagram: false, reasons: [`too-wide(${maxCols})`] };
922
+ }
923
+ if (!explicit) {
924
+ let tlCandidates = 0;
925
+ for (const line of lines) {
926
+ const unicodeMatches = line.match(/[┌╭┏╔]|[├┬┼┣┳╋╠╦╬][─━═]/gu);
927
+ tlCandidates += unicodeMatches ? unicodeMatches.length : 0;
928
+ const plusMatches = line.match(/\+[-+]/g);
929
+ tlCandidates += plusMatches ? plusMatches.length : 0;
930
+ }
931
+ if (tlCandidates < 2) {
932
+ return { isDiagram: false, reasons: [`too-few-corner-candidates(${tlCandidates})`] };
933
+ }
934
+ }
935
+ const nonBlank = lines.filter((l) => l.trim().length > 0);
936
+ const pipeRows = nonBlank.filter((l) => /^\s*\|.*\|\s*$/.test(l)).length;
937
+ const hasGfmSeparator = nonBlank.some((l) => /^\s*\|?[\s:|-]+\|?\s*$/.test(l) && l.includes("-"));
938
+ if (pipeRows > nonBlank.length / 2 && hasGfmSeparator) {
939
+ return { isDiagram: false, reasons: ["markdown-table"] };
940
+ }
941
+ const { diagram, stats } = parseAsciiDiagramWithStats(text);
942
+ const minBoxes = explicit ? 1 : 2;
943
+ if (diagram.nodes.length < minBoxes) {
944
+ return { isDiagram: false, reasons: [`too-few-boxes(${diagram.nodes.length})`] };
945
+ }
946
+ const garbled = diagram.nodes.filter((n) => STRUCTURAL_IN_LABEL.test(n.label)).length;
947
+ if (garbled > 0 && garbled >= diagram.nodes.length * 0.25) {
948
+ return { isDiagram: false, reasons: [`garbled-labels(${garbled}/${diagram.nodes.length})`] };
949
+ }
950
+ if (!explicit) {
951
+ if (diagram.edges.length === 0 && diagram.nodes.length >= 4 && isLattice(diagram)) {
952
+ return { isDiagram: false, reasons: ["table-lattice"] };
953
+ }
954
+ if (stats.totalNonSpaceCells > 0) {
955
+ const looseRatio = stats.looseNonSpaceCells / stats.totalNonSpaceCells;
956
+ if (looseRatio > MAX_LOOSE_RATIO) {
957
+ return { isDiagram: false, reasons: [`loose-ratio(${looseRatio.toFixed(2)})`] };
958
+ }
959
+ }
960
+ }
961
+ reasons.push(`boxes(${diagram.nodes.length})`, `edges(${diagram.edges.length})`);
962
+ if (explicit) reasons.push("explicit");
963
+ return { isDiagram: true, diagram, reasons };
964
+ }
965
+ function isAsciiDiagramFence(node) {
966
+ if (node.type !== "code") return false;
967
+ if (!isEligibleAsciiFenceLang(node.lang)) return false;
968
+ return detectAsciiDiagram(node.value, { explicit: isExplicitDiagramLang(node.lang) }).isDiagram;
969
+ }
970
+ function isLattice(diagram) {
971
+ const leaves = diagram.nodes.filter((n) => !n.containerId);
972
+ if (leaves.length < 4) return false;
973
+ const rowBands = new Set(leaves.map((n) => n.row));
974
+ const colBands = new Set(leaves.map((n) => n.col));
975
+ if (rowBands.size < 2 || colBands.size < 2) return false;
976
+ return leaves.every(
977
+ (a) => leaves.some((b) => {
978
+ if (a === b) return false;
979
+ const aR1 = a.row + a.hRows - 1;
980
+ const aC1 = a.col + a.wCols - 1;
981
+ const bR1 = b.row + b.hRows - 1;
982
+ const bC1 = b.col + b.wCols - 1;
983
+ return a.row === bR1 || aR1 === b.row || a.col === bC1 || aC1 === b.col;
984
+ })
985
+ );
986
+ }
987
+
988
+ // src/doc/asciiTimeline/wrappedFlow.ts
989
+ var UNDERLINE_RE = /[─━═-]{6,}/gu;
990
+ var FLOW_CHAR_RE = /[─━═│|►◄→←┌┐└┘├┤]/u;
991
+ var ARROW_RE = /[►◄→←]/u;
992
+ var LEFT_ARROW_RE = /[◄←]/u;
993
+ var RIGHT_ARROW_RE = /[►→]/u;
994
+ var CONNECTOR_ONLY_RE = /^[\s─━═│|►◄→←┌┐└┘├┤]+$/u;
995
+ var HEADER_RE = /^[A-Z][A-Z0-9 _/-]*(?:\s+\([^)]*\))?$/u;
996
+ function parseWrappedFlowTimeline(text) {
997
+ const lines = text.replace(/\r\n?/g, "\n").split("\n");
998
+ const header = findHeader(lines);
999
+ if (!header || header.tracks.length < 2) return null;
1000
+ const drafts = [];
1001
+ const links = [];
1002
+ const usedIds = /* @__PURE__ */ new Set();
1003
+ const lastByTrack = /* @__PURE__ */ new Map();
1004
+ let pendingDescription = null;
1005
+ let activeBranchRoot = null;
1006
+ let wrapSource = null;
1007
+ let arrowCount = 0;
1008
+ let hasLeftArrow = false;
1009
+ let hasRightArrow = false;
1010
+ let hasWrapTop = false;
1011
+ let hasWrapBottom = false;
1012
+ const addEvent = (segment) => {
1013
+ const label = normalizeText(segment.text);
1014
+ const id = uniqueId(label, usedIds);
1015
+ const draft = {
1016
+ id,
1017
+ label,
1018
+ trackIndex: segment.trackIndex,
1019
+ order: drafts.length
1020
+ };
1021
+ drafts.push(draft);
1022
+ return draft;
1023
+ };
1024
+ const addLink = (source, target, label) => {
1025
+ if (!source || !target || source === target) return;
1026
+ if (links.some((link) => link.source === source && link.target === target && link.label === label)) {
1027
+ return;
1028
+ }
1029
+ links.push({ source, target, ...label ? { label } : {} });
1030
+ };
1031
+ for (let row = header.underlineRow + 1; row < lines.length; row++) {
1032
+ const original = lines[row];
1033
+ if (!original.trim()) continue;
1034
+ if (CONNECTOR_ONLY_RE.test(original)) {
1035
+ hasWrapTop || (hasWrapTop = original.includes("\u2510"));
1036
+ hasWrapBottom || (hasWrapBottom = original.includes("\u2518"));
1037
+ continue;
1038
+ }
1039
+ const trimmed = original.trim();
1040
+ if (trimmed.startsWith("(") && !ARROW_RE.test(original)) {
1041
+ const trackIndex = trackIndexAt(header.tracks, firstContentColumn(original));
1042
+ const eventId = lastByTrack.get(trackIndex);
1043
+ if (!eventId) return null;
1044
+ pendingDescription = {
1045
+ eventId,
1046
+ text: normalizeText(original.replace(/[│|]+\s*$/u, ""))
1047
+ };
1048
+ continue;
1049
+ }
1050
+ let line = original;
1051
+ let continuationTarget = null;
1052
+ if (pendingDescription) {
1053
+ const firstArrow = firstArrowColumn(line);
1054
+ if (firstArrow < 0) return null;
1055
+ const continuation = normalizeText(Array.from(line).slice(0, firstArrow).join(""));
1056
+ const event = drafts.find((candidate) => candidate.id === pendingDescription?.eventId);
1057
+ if (!event) return null;
1058
+ event.description = normalizeText(`${pendingDescription.text} ${continuation}`);
1059
+ continuationTarget = event.id;
1060
+ line = `${" ".repeat(firstArrow)}${Array.from(line).slice(firstArrow).join("")}`;
1061
+ pendingDescription = null;
1062
+ }
1063
+ const segments = extractSegments(line, header.tracks);
1064
+ if (segments.length === 0) return null;
1065
+ if (segments.some((segment) => !/[\p{L}\p{N}]/u.test(segment.text))) return null;
1066
+ const previousByTrack = new Map(lastByTrack);
1067
+ const events = segments.map(addEvent);
1068
+ const directions = [];
1069
+ for (let index = 0; index < segments.length - 1; index++) {
1070
+ const between = Array.from(line).slice(segments[index].end + 1, segments[index + 1].start).join("");
1071
+ const direction = LEFT_ARROW_RE.test(between) ? "left" : RIGHT_ARROW_RE.test(between) ? "right" : null;
1072
+ directions.push(direction);
1073
+ if (direction) arrowCount++;
1074
+ hasLeftArrow || (hasLeftArrow = direction === "left");
1075
+ hasRightArrow || (hasRightArrow = direction === "right");
1076
+ if (direction === "left") addLink(events[index + 1].id, events[index].id);
1077
+ if (direction === "right") addLink(events[index].id, events[index + 1].id);
1078
+ }
1079
+ const leadingBranch = /^[\s│|]*[├└]/u.exec(original)?.[0] ?? "";
1080
+ if (leadingBranch) {
1081
+ if (!activeBranchRoot) {
1082
+ activeBranchRoot = previousByTrack.get(events[0].trackIndex) ?? null;
1083
+ }
1084
+ addLink(activeBranchRoot ?? void 0, events[0].id);
1085
+ if (leadingBranch.includes("\u2514")) activeBranchRoot = null;
1086
+ } else {
1087
+ activeBranchRoot = null;
1088
+ }
1089
+ const allLeft = directions.length > 0 && directions.every((direction) => direction === "left");
1090
+ const ordered = allLeft ? [...events].reverse() : events;
1091
+ const first = ordered[0];
1092
+ if (!leadingBranch && !original.includes("\u2518")) {
1093
+ addLink(previousByTrack.get(first.trackIndex), first.id);
1094
+ }
1095
+ if (continuationTarget) addLink(ordered[ordered.length - 1]?.id, continuationTarget);
1096
+ for (const event of ordered) lastByTrack.set(event.trackIndex, event.id);
1097
+ if (original.includes("\u2510")) {
1098
+ hasWrapTop = true;
1099
+ wrapSource = events[events.length - 1]?.id ?? null;
1100
+ }
1101
+ if (original.includes("\u2518")) {
1102
+ hasWrapBottom = true;
1103
+ const target = allLeft ? events[0]?.id : events[events.length - 1]?.id;
1104
+ addLink(wrapSource ?? void 0, target);
1105
+ }
1106
+ }
1107
+ if (pendingDescription || drafts.length < 6) return null;
1108
+ if (arrowCount < 4 || !hasLeftArrow || !hasRightArrow || !hasWrapTop || !hasWrapBottom) {
1109
+ return null;
1110
+ }
1111
+ if (!hasCrossTrackLink(drafts, links)) return null;
1112
+ const columns = assignColumns(drafts, links);
1113
+ const endColumn = Math.max(10, ...columns.values());
1114
+ const tracks = header.tracks.map((band, trackIndex) => {
1115
+ const trackEvents = drafts.filter((event) => event.trackIndex === trackIndex).sort(
1116
+ (left, right) => (columns.get(left.id) ?? 0) - (columns.get(right.id) ?? 0) || left.order - right.order
1117
+ );
1118
+ let previousColumn = -1;
1119
+ const events = trackEvents.map((event, index) => {
1120
+ const ideal = columns.get(event.id) ?? index * 10;
1121
+ const column = Math.max(previousColumn + 1, ideal);
1122
+ previousColumn = column;
1123
+ return {
1124
+ id: event.id,
1125
+ label: event.label,
1126
+ ...event.description ? { description: event.description } : {},
1127
+ column,
1128
+ side: index % 2 === 0 ? "above" : "below",
1129
+ marker: "filled"
1130
+ };
1131
+ });
1132
+ return {
1133
+ id: band.id,
1134
+ label: band.label,
1135
+ row: band.row,
1136
+ startColumn: 0,
1137
+ endColumn,
1138
+ events
1139
+ };
1140
+ });
1141
+ const width = Math.max(0, ...lines.map((line) => Array.from(line).length));
1142
+ links.sort(
1143
+ (left, right) => left.source.localeCompare(right.source) || left.target.localeCompare(right.target) || (left.label ?? "").localeCompare(right.label ?? "")
1144
+ );
1145
+ return {
1146
+ tracks,
1147
+ links,
1148
+ width,
1149
+ height: lines.length,
1150
+ style: "unicode",
1151
+ warnings: []
1152
+ };
1153
+ }
1154
+ function isWrappedFlowTimelineSource(text) {
1155
+ return parseWrappedFlowTimeline(text) !== null;
1156
+ }
1157
+ function findHeader(lines) {
1158
+ for (let row = 1; row < Math.min(lines.length, 5); row++) {
1159
+ const runs = Array.from(lines[row].matchAll(UNDERLINE_RE)).map((match) => ({
1160
+ start: match.index,
1161
+ end: (match.index ?? 0) + Array.from(match[0]).length - 1
1162
+ }));
1163
+ if (runs.length < 2) continue;
1164
+ const headings = splitHeader(lines[row - 1]);
1165
+ if (headings.length !== runs.length || headings.some((heading) => !HEADER_RE.test(heading.text))) {
1166
+ continue;
1167
+ }
1168
+ const tracks = runs.map((run, index) => {
1169
+ const heading = headings[index];
1170
+ const base = heading.text.replace(/\s+\([^)]*\)\s*$/u, "").trim();
1171
+ return {
1172
+ id: safeId(base),
1173
+ label: heading.text,
1174
+ row: row - 1,
1175
+ start: run.start,
1176
+ end: run.end
1177
+ };
1178
+ });
1179
+ return { tracks, underlineRow: row };
1180
+ }
1181
+ return null;
1182
+ }
1183
+ function splitHeader(line) {
1184
+ const chars = Array.from(line);
1185
+ const segments = [];
1186
+ let start = -1;
1187
+ let spaces = 0;
1188
+ for (let index = 0; index <= chars.length; index++) {
1189
+ const char = chars[index];
1190
+ if (char === " ") {
1191
+ spaces++;
1192
+ if (start >= 0 && spaces >= 3) {
1193
+ const end = index - spaces;
1194
+ const text = chars.slice(start, end + 1).join("").trim();
1195
+ if (text) segments.push({ text, start, end });
1196
+ start = -1;
1197
+ }
1198
+ continue;
1199
+ }
1200
+ spaces = 0;
1201
+ if (char !== void 0 && start < 0) start = index;
1202
+ if (char === void 0 && start >= 0) {
1203
+ const text = chars.slice(start).join("").trim();
1204
+ if (text) segments.push({ text, start, end: chars.length - 1 });
1205
+ }
1206
+ }
1207
+ return segments;
1208
+ }
1209
+ function extractSegments(line, tracks) {
1210
+ const chars = Array.from(line);
1211
+ const segments = [];
1212
+ let start = -1;
1213
+ for (let index = 0; index <= chars.length; index++) {
1214
+ const structural = index === chars.length || FLOW_CHAR_RE.test(chars[index] ?? "");
1215
+ if (!structural && start < 0) start = index;
1216
+ if (!structural || start < 0) continue;
1217
+ const raw = chars.slice(start, index).join("");
1218
+ const leading = raw.length - raw.trimStart().length;
1219
+ const text = normalizeText(raw);
1220
+ if (text) {
1221
+ const segmentStart = start + leading;
1222
+ const segmentEnd = segmentStart + Array.from(text).length - 1;
1223
+ segments.push({
1224
+ text,
1225
+ start: segmentStart,
1226
+ end: segmentEnd,
1227
+ trackIndex: trackIndexAt(tracks, segmentStart)
1228
+ });
1229
+ }
1230
+ start = -1;
1231
+ }
1232
+ return segments;
1233
+ }
1234
+ function trackIndexAt(tracks, column) {
1235
+ for (let index = 1; index < tracks.length; index++) {
1236
+ if (column < tracks[index].start) return index - 1;
1237
+ }
1238
+ return tracks.length - 1;
1239
+ }
1240
+ function firstContentColumn(line) {
1241
+ const chars = Array.from(line);
1242
+ const index = chars.findIndex((char) => /[\p{L}\p{N}(]/u.test(char));
1243
+ return Math.max(0, index);
1244
+ }
1245
+ function firstArrowColumn(line) {
1246
+ return Array.from(line).findIndex((char) => ARROW_RE.test(char));
1247
+ }
1248
+ function normalizeText(value) {
1249
+ return value.normalize("NFC").replace(/^[\s:]+|[\s:]+$/gu, "").replace(/\s+/gu, " ").trim();
1250
+ }
1251
+ function safeId(value) {
1252
+ return value.normalize("NFC").toLowerCase().replace(/[^a-z0-9_.~-]+/gu, "-").replace(/^-+|-+$/gu, "") || "event";
1253
+ }
1254
+ function uniqueId(label, used) {
1255
+ const base = safeId(label);
1256
+ if (!used.has(base)) {
1257
+ used.add(base);
1258
+ return base;
1259
+ }
1260
+ let suffix = 2;
1261
+ while (used.has(`${base}-${suffix}`)) suffix++;
1262
+ const id = `${base}-${suffix}`;
1263
+ used.add(id);
1264
+ return id;
1265
+ }
1266
+ function hasCrossTrackLink(events, links) {
1267
+ const trackById = new Map(events.map((event) => [event.id, event.trackIndex]));
1268
+ return links.some((link) => trackById.get(link.source) !== trackById.get(link.target));
1269
+ }
1270
+ function assignColumns(events, links) {
1271
+ const outgoing = /* @__PURE__ */ new Map();
1272
+ const indegree = new Map(events.map((event) => [event.id, 0]));
1273
+ for (const link of links) {
1274
+ outgoing.set(link.source, [...outgoing.get(link.source) ?? [], link.target]);
1275
+ indegree.set(link.target, (indegree.get(link.target) ?? 0) + 1);
1276
+ }
1277
+ const ranks = new Map(events.map((event) => [event.id, 0]));
1278
+ const queue = events.filter((event) => indegree.get(event.id) === 0).map((event) => event.id);
1279
+ let visited = 0;
1280
+ while (queue.length > 0) {
1281
+ const source = queue.shift();
1282
+ visited++;
1283
+ for (const target of outgoing.get(source) ?? []) {
1284
+ ranks.set(target, Math.max(ranks.get(target) ?? 0, (ranks.get(source) ?? 0) + 1));
1285
+ const next = (indegree.get(target) ?? 1) - 1;
1286
+ indegree.set(target, next);
1287
+ if (next === 0) queue.push(target);
1288
+ }
1289
+ }
1290
+ if (visited < events.length) {
1291
+ for (const event of events) ranks.set(event.id, event.order);
1292
+ }
1293
+ return new Map(Array.from(ranks, ([id, rank]) => [id, rank * 10]));
1294
+ }
1295
+
1296
+ // src/doc/asciiTimeline/parse.ts
1297
+ var STRONG_MARKERS = /* @__PURE__ */ new Set(["\u25CF", "\u25CB", "\u25C9", "\u25C6", "\u25C7", "\u2022"]);
1298
+ var ASCII_MARKERS = /* @__PURE__ */ new Set(["*", "o", "O"]);
1299
+ var HORIZONTAL = /* @__PURE__ */ new Set(["\u2500", "\u2501", "\u2550", "\u254C", "\u254D", "\u2504", "\u2505", "\u2508", "\u2509", "-"]);
1300
+ var END_ARROWS = /* @__PURE__ */ new Set(["\u25BA", "\u25B6", "\u25B7", "\u2192", ">"]);
1301
+ var POINTERS = /* @__PURE__ */ new Set(["\u25B2", "\u25B3", "\u25BC", "\u25BD", "^", "v"]);
1302
+ var CONNECTOR_ANCHORS = /* @__PURE__ */ new Set(["\u2514", "\u250C", "\u251C", "\u252C", "\u2518", "\u2510", "\u2570", "\u256D", "\u256F", "\u256E"]);
1303
+ function parseAsciiTimeline(text) {
1304
+ return parseAsciiTimelineWithStats(text).timeline;
1305
+ }
1306
+ function parseAsciiTimelineWithStats(text) {
1307
+ const lines = text.replace(/\r\n?/g, "\n").split("\n");
1308
+ const width = Math.max(0, ...lines.map((line) => Array.from(line).length));
1309
+ const wrappedFlow = parseWrappedFlowTimeline(text);
1310
+ if (wrappedFlow) {
1311
+ const markerCount = wrappedFlow.tracks.reduce((sum, track) => sum + track.events.length, 0);
1312
+ return {
1313
+ timeline: wrappedFlow,
1314
+ stats: {
1315
+ axisLines: wrappedFlow.tracks.length,
1316
+ markerCount,
1317
+ strongMarkerCount: markerCount,
1318
+ horizontalChars: Array.from(text).filter((char) => HORIZONTAL.has(char)).length,
1319
+ pointerCount: 0,
1320
+ branchDeclarations: wrappedFlow.links.length,
1321
+ wrappedFlow: true
1322
+ }
1323
+ };
1324
+ }
1325
+ const style = detectStyle2(lines);
1326
+ const pendingBranches = [];
1327
+ const axes = [];
1328
+ for (let row = 0; row < lines.length; row++) {
1329
+ const branch = parseBranchDeclaration(lines[row]);
1330
+ if (branch) {
1331
+ pendingBranches.push(branch);
1332
+ continue;
1333
+ }
1334
+ const axis = analyzeAxisLine(lines[row], row, style);
1335
+ if (axis) {
1336
+ axes.push(axis);
1337
+ continue;
1338
+ }
1339
+ const cadence = analyzeCadenceLine(lines[row], row);
1340
+ if (cadence) axes.push(cadence);
1341
+ }
1342
+ const tracks = axes.map((axis, index) => ({
1343
+ id: axis.id ?? slug(axis.label || `track-${index + 1}`),
1344
+ ...axis.label ? { label: axis.label } : {},
1345
+ ...axis.endLabel ? { endLabel: axis.endLabel } : {},
1346
+ row: axis.row,
1347
+ startColumn: axis.startColumn,
1348
+ endColumn: axis.endColumn,
1349
+ events: axis.markerColumns.map((markerColumn, eventIndex) => {
1350
+ const inline = axis.inline[eventIndex];
1351
+ return {
1352
+ id: inline.id ?? "",
1353
+ label: inline.label ?? "",
1354
+ ...inline.description ? { description: inline.description } : {},
1355
+ column: inline.column ?? markerColumn,
1356
+ ...inline.side ? { side: inline.side } : {},
1357
+ ...inline.descriptionSide ? { descriptionSide: inline.descriptionSide } : {},
1358
+ ...inline.callout === false ? { callout: false } : {},
1359
+ marker: markerKind(axis.markerGlyphs[eventIndex])
1360
+ };
1361
+ })
1362
+ }));
1363
+ for (let i = 0; i < axes.length; i++) {
1364
+ const axis = axes[i];
1365
+ const track = tracks[i];
1366
+ const aboveRow = previousNonBlankRow(lines, axis.row - 1);
1367
+ if (aboveRow >= 0 && !axes.some((candidate) => candidate.row === aboveRow)) {
1368
+ const segments = splitPlainSegments(lines[aboveRow]);
1369
+ const pointEvents = track.events.filter((event) => !event.label);
1370
+ const aligned = alignedEventSegments(segments, pointEvents);
1371
+ if (aligned) {
1372
+ for (let eventIndex = 0; eventIndex < pointEvents.length; eventIndex++) {
1373
+ pointEvents[eventIndex].label = cleanProseText(aligned[eventIndex].text);
1374
+ pointEvents[eventIndex].side = "above";
1375
+ }
1376
+ const firstAligned = segments.indexOf(aligned[0]);
1377
+ const prefix = firstAligned > 0 ? segments[firstAligned - 1] : void 0;
1378
+ if (prefix?.text.trim().endsWith(":")) {
1379
+ track.label = stripTrailingColon(prefix.text);
1380
+ track.id = slug(track.label);
1381
+ }
1382
+ }
1383
+ for (const event of track.events) {
1384
+ if (event.label) continue;
1385
+ const gap = nearestEventGap(track.events, event.column);
1386
+ const threshold = Math.max(3, Math.min(8, gap * 0.38));
1387
+ const nearest = nearestSegment(segments, event.column, threshold);
1388
+ if (!nearest) continue;
1389
+ event.label = cleanProseText(nearest.text);
1390
+ event.side = "above";
1391
+ }
1392
+ }
1393
+ }
1394
+ let pointerCount = 0;
1395
+ for (let row = 0; row < lines.length; row++) {
1396
+ if (axes.some((axis) => axis.row === row)) continue;
1397
+ const chars = Array.from(lines[row]);
1398
+ const pointerColumns = chars.map((char, column) => ({ char, column })).filter(({ column }) => isPointerAt(chars, column)).map(({ column }) => column);
1399
+ for (let pointerIndex = 0; pointerIndex < pointerColumns.length; pointerIndex++) {
1400
+ const column = pointerColumns[pointerIndex];
1401
+ const track = nearestUnclaimedMarkerTrack(tracks, row, column) ?? nearestTrack(tracks, row, column);
1402
+ if (!track) continue;
1403
+ pointerCount++;
1404
+ const nextPointer = pointerColumns[pointerIndex + 1] ?? chars.length;
1405
+ const after = cleanProseText(chars.slice(column + 1, nextPointer).join(""));
1406
+ const prefix = pointerIndex === 0 ? cleanProseText(chars.slice(0, column).join("")) : "";
1407
+ const parsed = /\{[^{}]*#[^{}\s]+[^{}]*\}\s*$/u.test(after) ? parseInlineEvent(after) : { label: after || void 0 };
1408
+ const label = stripTrailingColon(prefix) || parsed.label || after || `Event ${track.events.length + 1}`;
1409
+ const description = prefix && after ? after : parsed.description;
1410
+ const side = parsed.side ?? (row < track.row ? "above" : "below");
1411
+ const existing = track.events.find((event) => !event.id && Math.abs(event.column - column) <= 1) ?? track.events.find(
1412
+ (event) => event.callout !== false && Math.abs(event.column - column) <= 1
1413
+ );
1414
+ if (existing) {
1415
+ if (parsed.id) existing.id = parsed.id;
1416
+ if (parsed.column !== void 0) existing.column = parsed.column;
1417
+ if (!existing.label) existing.label = label;
1418
+ if (parsed.label) existing.label = parsed.label;
1419
+ if (description && !existing.description) existing.description = description;
1420
+ if (parsed.description) existing.description = parsed.description;
1421
+ if (parsed.descriptionSide) existing.descriptionSide = parsed.descriptionSide;
1422
+ else if (description && !parsed.id) existing.descriptionSide ?? (existing.descriptionSide = side);
1423
+ if (parsed.callout !== void 0) existing.callout = parsed.callout;
1424
+ existing.side = side;
1425
+ } else {
1426
+ track.events.push({
1427
+ id: parsed.id ?? "",
1428
+ label,
1429
+ ...description ? { description } : {},
1430
+ column: parsed.column ?? column,
1431
+ side,
1432
+ ...parsed.descriptionSide ? { descriptionSide: parsed.descriptionSide } : description && !parsed.id ? { descriptionSide: side } : {},
1433
+ ...parsed.callout !== void 0 ? { callout: parsed.callout } : {},
1434
+ marker: "hollow"
1435
+ });
1436
+ }
1437
+ }
1438
+ }
1439
+ for (let row = 0; row < lines.length; row++) {
1440
+ if (axes.some((axis) => axis.row === row)) continue;
1441
+ const callouts = extractConnectorCallouts(lines[row]);
1442
+ const prefix = callouts.length > 0 ? cleanProseText(Array.from(lines[row]).slice(0, callouts[0].start).join("")) : "";
1443
+ for (let calloutIndex = 0; calloutIndex < callouts.length; calloutIndex++) {
1444
+ const callout = callouts[calloutIndex];
1445
+ const track = nearestTrack(tracks, row, callout.start);
1446
+ if (!track || track.events.length === 0) continue;
1447
+ const event = nearestEvent(track.events, callout.start);
1448
+ if (!event) continue;
1449
+ const calloutText = cleanProseText(callout.text);
1450
+ const cleaned = calloutIndex === 0 && prefix ? `${prefix} ${calloutText}` : calloutText;
1451
+ if (!cleaned || cleaned === event.label) continue;
1452
+ if (event.callout === false) {
1453
+ event.callout = true;
1454
+ event.label = cleaned;
1455
+ event.side = row < track.row ? "above" : "below";
1456
+ continue;
1457
+ }
1458
+ event.description = event.description ? `${event.description} ${cleaned}` : cleaned;
1459
+ event.descriptionSide ?? (event.descriptionSide = row < track.row ? "above" : "below");
1460
+ event.side ?? (event.side = row < track.row ? "above" : "below");
1461
+ }
1462
+ }
1463
+ const warnings = [];
1464
+ const usedTrackIds = /* @__PURE__ */ new Map();
1465
+ for (const track of tracks) track.id = dedupe(track.id, usedTrackIds);
1466
+ const usedEventIds = /* @__PURE__ */ new Map();
1467
+ for (const track of tracks) {
1468
+ track.events.sort((a, b) => a.column - b.column);
1469
+ for (let i = 0; i < track.events.length; i++) {
1470
+ const event = track.events[i];
1471
+ if (!event.label) {
1472
+ event.label = `Event ${i + 1}`;
1473
+ warnings.push(`unlabeled-event(${track.id}:${event.column})`);
1474
+ }
1475
+ event.id = dedupe(event.id || slug(event.label), usedEventIds);
1476
+ event.side ?? (event.side = i % 2 === 0 ? "above" : "below");
1477
+ }
1478
+ }
1479
+ const eventIds = new Set(tracks.flatMap((track) => track.events.map((event) => event.id)));
1480
+ const labelToId = /* @__PURE__ */ new Map();
1481
+ for (const track of tracks) {
1482
+ for (const event of track.events) {
1483
+ labelToId.set(slug(event.label), event.id);
1484
+ labelToId.set(event.id, event.id);
1485
+ }
1486
+ }
1487
+ const links = [];
1488
+ for (const branch of pendingBranches) {
1489
+ const source = labelToId.get(slug(branch.source)) ?? branch.source;
1490
+ const target = labelToId.get(slug(branch.target)) ?? branch.target;
1491
+ if (!eventIds.has(source) || !eventIds.has(target)) {
1492
+ warnings.push(`unresolved-link(${branch.source}->${branch.target})`);
1493
+ continue;
1494
+ }
1495
+ links.push({ source, target, ...branch.label ? { label: branch.label } : {} });
1496
+ }
1497
+ const stats = {
1498
+ axisLines: axes.length,
1499
+ markerCount: axes.reduce((sum, axis) => sum + axis.markerColumns.length, 0),
1500
+ strongMarkerCount: axes.reduce((sum, axis) => sum + axis.strongMarkerCount, 0),
1501
+ horizontalChars: axes.reduce((sum, axis) => sum + axis.horizontalChars, 0),
1502
+ pointerCount,
1503
+ branchDeclarations: pendingBranches.length,
1504
+ wrappedFlow: false
1505
+ };
1506
+ return {
1507
+ timeline: { tracks, links, width, height: lines.length, style, warnings },
1508
+ stats
1509
+ };
1510
+ }
1511
+ function alignedEventSegments(segments, events) {
1512
+ if (events.length < 2 || segments.length < events.length) return void 0;
1513
+ const candidates = segments.slice(-events.length);
1514
+ if (candidates.some((segment) => cleanCalloutText(segment.text).length > 40)) return void 0;
1515
+ const offsets = candidates.map(
1516
+ (segment, index) => (segment.start + segment.end) / 2 - events[index].column
1517
+ );
1518
+ const spread = Math.max(...offsets) - Math.min(...offsets);
1519
+ return spread <= 3 ? [...candidates] : void 0;
1520
+ }
1521
+ function analyzeAxisLine(line, row, style) {
1522
+ const chars = Array.from(line);
1523
+ const markerColumns = [];
1524
+ const markerGlyphs = [];
1525
+ let horizontalChars = 0;
1526
+ let strongMarkerCount = 0;
1527
+ for (let i = 0; i < chars.length; i++) {
1528
+ const ch = chars[i];
1529
+ if (HORIZONTAL.has(ch)) horizontalChars++;
1530
+ if (isMarkerAt(chars, i, style)) {
1531
+ markerColumns.push(i);
1532
+ markerGlyphs.push(ch);
1533
+ if (STRONG_MARKERS.has(ch)) strongMarkerCount++;
1534
+ }
1535
+ }
1536
+ if (markerColumns.length === 0 || horizontalChars < 2) return null;
1537
+ const firstMarker = markerColumns[0];
1538
+ const colon = chars.slice(0, firstMarker).lastIndexOf(":");
1539
+ let startColumn;
1540
+ if (colon >= 0) {
1541
+ startColumn = colon + 1;
1542
+ while (chars[startColumn] === " ") startColumn++;
1543
+ } else {
1544
+ let i = firstMarker - 1;
1545
+ while (i >= 0 && (HORIZONTAL.has(chars[i]) || chars[i] === " ")) i--;
1546
+ startColumn = i + 1;
1547
+ while (chars[startColumn] === " ") startColumn++;
1548
+ }
1549
+ let endColumn = markerColumns[markerColumns.length - 1];
1550
+ const arrowSearchStart = endColumn + 1;
1551
+ const arrowColumn = chars.findIndex(
1552
+ (ch, index) => index >= arrowSearchStart && END_ARROWS.has(ch) && hasRailBefore(chars, index, ch === ">" ? 3 : 2)
1553
+ );
1554
+ let payloadEndExclusive;
1555
+ if (arrowColumn >= 0) {
1556
+ endColumn = arrowColumn;
1557
+ payloadEndExclusive = arrowColumn;
1558
+ } else {
1559
+ const lastNonSpace = findLastNonSpace(chars);
1560
+ endColumn = Math.max(endColumn, lastNonSpace);
1561
+ payloadEndExclusive = endColumn + 1;
1562
+ }
1563
+ const prefix = parseTrackPrefix(stripTrailingColon(chars.slice(0, startColumn).join("").trim()));
1564
+ const suffix = cleanProseText(chars.slice(endColumn + 1).join(""));
1565
+ const label = prefix.label;
1566
+ const inline = markerColumns.map((column, index) => {
1567
+ const next = index + 1 < markerColumns.length ? markerColumns[index + 1] : payloadEndExclusive;
1568
+ return parseInlineEvent(chars.slice(column + 1, next).join(""));
1569
+ });
1570
+ return {
1571
+ row,
1572
+ ...prefix.id ? { id: prefix.id } : {},
1573
+ startColumn: prefix.startColumn ?? startColumn,
1574
+ endColumn: prefix.endColumn ?? Math.max(endColumn, startColumn + 1),
1575
+ markerColumns,
1576
+ markerGlyphs,
1577
+ inline,
1578
+ ...label ? { label } : {},
1579
+ ...suffix ? { endLabel: suffix } : {},
1580
+ horizontalChars,
1581
+ strongMarkerCount
1582
+ };
1583
+ }
1584
+ function analyzeCadenceLine(line, row) {
1585
+ const chars = Array.from(line);
1586
+ const colon = chars.indexOf(":");
1587
+ if (colon < 0) return null;
1588
+ const label = chars.slice(0, colon).join("").trim();
1589
+ if (!label) return null;
1590
+ const tokens = [];
1591
+ let index = colon + 1;
1592
+ while (index < chars.length) {
1593
+ while (index < chars.length && /\s/u.test(chars[index])) index++;
1594
+ if (index >= chars.length) break;
1595
+ const start = index;
1596
+ while (index < chars.length && !/\s/u.test(chars[index])) index++;
1597
+ tokens.push({ value: chars.slice(start, index).join(""), column: start });
1598
+ }
1599
+ if (tokens.length < 4 || tokens[0].value.length !== 1 || tokens.some((token) => token.value !== tokens[0].value)) {
1600
+ return null;
1601
+ }
1602
+ return {
1603
+ row,
1604
+ startColumn: tokens[0].column,
1605
+ endColumn: tokens[tokens.length - 1].column,
1606
+ markerColumns: tokens.map((token) => token.column),
1607
+ markerGlyphs: tokens.map(() => "\u25CB"),
1608
+ inline: tokens.map((token, tokenIndex) => ({
1609
+ label: token.value,
1610
+ id: `${slug(label)}-${tokenIndex + 1}`,
1611
+ column: token.column,
1612
+ callout: false
1613
+ })),
1614
+ label,
1615
+ horizontalChars: 0,
1616
+ strongMarkerCount: 0
1617
+ };
1618
+ }
1619
+ function parseInlineEvent(raw) {
1620
+ let text = stripEdgeRails(raw).replace(/\s+/g, " ").trim();
1621
+ if (!text) return {};
1622
+ let id;
1623
+ let column;
1624
+ let side;
1625
+ let descriptionSide;
1626
+ let callout;
1627
+ const attrs = /\{([^{}]+)\}\s*$/.exec(text);
1628
+ if (attrs) {
1629
+ let recognized = false;
1630
+ for (const token of attrs[1].trim().split(/\s+/)) {
1631
+ if (token.startsWith("#") && token.length > 1) {
1632
+ id = token.slice(1);
1633
+ recognized = true;
1634
+ } else if (token === "side=above") {
1635
+ side = "above";
1636
+ recognized = true;
1637
+ } else if (token === "side=below") {
1638
+ side = "below";
1639
+ recognized = true;
1640
+ } else if (token === "descriptionSide=above") {
1641
+ descriptionSide = "above";
1642
+ recognized = true;
1643
+ } else if (token === "descriptionSide=below") {
1644
+ descriptionSide = "below";
1645
+ recognized = true;
1646
+ } else if (token.startsWith("column=")) {
1647
+ const parsed = parseCoordinate(token.slice(7));
1648
+ if (parsed !== void 0) {
1649
+ column = parsed;
1650
+ recognized = true;
1651
+ }
1652
+ } else if (token === "callout=false") {
1653
+ callout = false;
1654
+ recognized = true;
1655
+ } else if (token === "callout=true") {
1656
+ callout = true;
1657
+ recognized = true;
1658
+ }
1659
+ }
1660
+ if (recognized) text = text.slice(0, attrs.index).trim();
1661
+ }
1662
+ if (text.startsWith("^ ")) {
1663
+ side = "above";
1664
+ text = text.slice(2).trim();
1665
+ } else if (/^[vV]\s+/.test(text)) {
1666
+ side = "below";
1667
+ text = text.slice(2).trim();
1668
+ }
1669
+ const separator = text.indexOf("::");
1670
+ const label = (separator >= 0 ? text.slice(0, separator) : text).trim();
1671
+ const description = separator >= 0 ? text.slice(separator + 2).trim() : void 0;
1672
+ return {
1673
+ ...label ? { label } : {},
1674
+ ...description ? { description } : {},
1675
+ ...id ? { id } : {},
1676
+ ...column !== void 0 ? { column } : {},
1677
+ ...side ? { side } : {},
1678
+ ...descriptionSide ? { descriptionSide } : {},
1679
+ ...callout !== void 0 ? { callout } : {}
1680
+ };
1681
+ }
1682
+ function parseTrackPrefix(text) {
1683
+ let label = text.trim();
1684
+ let id;
1685
+ let startColumn;
1686
+ let endColumn;
1687
+ let recognized = false;
1688
+ const attrs = /\{([^{}]+)\}\s*$/.exec(label);
1689
+ if (attrs) {
1690
+ for (const token of attrs[1].trim().split(/\s+/)) {
1691
+ if (token.startsWith("#") && token.length > 1) {
1692
+ id = token.slice(1);
1693
+ recognized = true;
1694
+ } else if (token.startsWith("start=")) {
1695
+ startColumn = parseCoordinate(token.slice(6));
1696
+ recognized || (recognized = startColumn !== void 0);
1697
+ } else if (token.startsWith("end=")) {
1698
+ endColumn = parseCoordinate(token.slice(4));
1699
+ recognized || (recognized = endColumn !== void 0);
1700
+ }
1701
+ }
1702
+ if (recognized) label = label.slice(0, attrs.index).trim();
1703
+ }
1704
+ return {
1705
+ ...label ? { label } : {},
1706
+ ...id ? { id } : {},
1707
+ ...startColumn !== void 0 ? { startColumn } : {},
1708
+ ...endColumn !== void 0 ? { endColumn } : {},
1709
+ hasMetadata: recognized
1710
+ };
1711
+ }
1712
+ function parseCoordinate(value) {
1713
+ if (!value.trim()) return void 0;
1714
+ const parsed = Number(value);
1715
+ return Number.isFinite(parsed) ? parsed : void 0;
1716
+ }
1717
+ function stripEdgeRails(raw) {
1718
+ const chars = Array.from(raw);
1719
+ let start = 0;
1720
+ let end = chars.length - 1;
1721
+ let firstNonSpace = 0;
1722
+ while (firstNonSpace <= end && /\s/u.test(chars[firstNonSpace])) firstNonSpace++;
1723
+ let leadingRailEnd = firstNonSpace;
1724
+ while (leadingRailEnd <= end && HORIZONTAL.has(chars[leadingRailEnd])) leadingRailEnd++;
1725
+ const hasLeadingRail = leadingRailEnd > firstNonSpace;
1726
+ const structuralLeadingRail = hasLeadingRail && (firstNonSpace === 0 || leadingRailEnd > end || /\s/u.test(chars[leadingRailEnd] ?? ""));
1727
+ start = structuralLeadingRail ? leadingRailEnd : firstNonSpace;
1728
+ while (start <= end && /\s/u.test(chars[start])) start++;
1729
+ while (end >= start && /\s/u.test(chars[end])) end--;
1730
+ const trailingRailEnd = end;
1731
+ while (end >= start && HORIZONTAL.has(chars[end])) end--;
1732
+ const hasTrailingRail = end < trailingRailEnd;
1733
+ const trailingRun = hasTrailingRail ? chars.slice(end + 1, trailingRailEnd + 1) : [];
1734
+ const structuralTrailingRail = hasTrailingRail && (end < start || /\s/u.test(chars[end] ?? "") || trailingRun.length >= 3 || trailingRun.some((char) => char !== "-"));
1735
+ if (!structuralTrailingRail) end = trailingRailEnd;
1736
+ while (end >= start && /\s/u.test(chars[end])) end--;
1737
+ return chars.slice(start, end + 1).join("");
1738
+ }
1739
+ function hasRailBefore(chars, column, minimum) {
1740
+ let index = column - 1;
1741
+ while (index >= 0 && /\s/u.test(chars[index])) index--;
1742
+ let count = 0;
1743
+ while (index >= 0 && HORIZONTAL.has(chars[index])) {
1744
+ count++;
1745
+ index--;
1746
+ }
1747
+ return count >= minimum;
1748
+ }
1749
+ function findLastNonSpace(chars) {
1750
+ for (let index = chars.length - 1; index >= 0; index--) {
1751
+ if (!/\s/u.test(chars[index])) return index;
1752
+ }
1753
+ return -1;
1754
+ }
1755
+ function parseBranchDeclaration(line) {
1756
+ const match = /^\s*(?:branch|link)\s*:\s*([^\s:]+)\s*(?:->|=>|→)\s*([^\s:]+)(?:\s*:\s*(.+))?\s*$/i.exec(line);
1757
+ if (!match) return null;
1758
+ return {
1759
+ source: match[1],
1760
+ target: match[2],
1761
+ ...match[3]?.trim() ? { label: match[3].trim() } : {}
1762
+ };
1763
+ }
1764
+ function splitPlainSegments(line) {
1765
+ const chars = Array.from(line);
1766
+ const out = [];
1767
+ let start = -1;
1768
+ let spaces = 0;
1769
+ for (let i = 0; i <= chars.length; i++) {
1770
+ const ch = chars[i];
1771
+ if (ch === " ") {
1772
+ spaces++;
1773
+ if (start >= 0 && spaces >= 2) {
1774
+ const end = i - spaces;
1775
+ const text = chars.slice(start, end + 1).join("").trim();
1776
+ if (text) out.push({ start, end, text });
1777
+ start = -1;
1778
+ }
1779
+ continue;
1780
+ }
1781
+ spaces = 0;
1782
+ if (ch !== void 0 && start < 0) start = i;
1783
+ if (ch === void 0 && start >= 0) {
1784
+ const text = chars.slice(start).join("").trim();
1785
+ if (text) out.push({ start, end: chars.length - 1, text });
1786
+ }
1787
+ }
1788
+ return out.filter((segment) => cleanCalloutText(segment.text).length > 0);
1789
+ }
1790
+ function extractConnectorCallouts(line) {
1791
+ const chars = Array.from(line);
1792
+ const out = [];
1793
+ for (let i = 0; i < chars.length; i++) {
1794
+ if (!CONNECTOR_ANCHORS.has(chars[i])) continue;
1795
+ let j = i + 1;
1796
+ const railStart = j;
1797
+ while (j < chars.length && (HORIZONTAL.has(chars[j]) || chars[j] === " ")) j++;
1798
+ const railEnd = j;
1799
+ const afterRail = chars[j];
1800
+ if (afterRail !== void 0 && !/\s/u.test(afterRail)) {
1801
+ let lastNonAsciiRail = -1;
1802
+ for (let railIndex = railStart; railIndex < j; railIndex++) {
1803
+ if (HORIZONTAL.has(chars[railIndex]) && chars[railIndex] !== "-") {
1804
+ lastNonAsciiRail = railIndex;
1805
+ }
1806
+ }
1807
+ if (lastNonAsciiRail >= railStart && lastNonAsciiRail + 1 < j) {
1808
+ j = lastNonAsciiRail + 1;
1809
+ } else if (lastNonAsciiRail < 0 && j - railStart >= 3) {
1810
+ j--;
1811
+ }
1812
+ }
1813
+ const textStart = j;
1814
+ if (j < railEnd) while (j < chars.length && chars[j] === "-") j++;
1815
+ while (j < chars.length && !HORIZONTAL.has(chars[j]) && !CONNECTOR_ANCHORS.has(chars[j])) j++;
1816
+ const text = chars.slice(textStart, j).join("").trim();
1817
+ if (text) out.push({ start: i, end: Math.max(i, j - 1), text });
1818
+ }
1819
+ return out;
1820
+ }
1821
+ function nearestTrack(tracks, row, column) {
1822
+ return tracks.filter((track) => column >= track.startColumn - 2 && column <= track.endColumn + 2).sort((a, b) => Math.abs(a.row - row) - Math.abs(b.row - row))[0];
1823
+ }
1824
+ function isPointerAt(chars, column) {
1825
+ const char = chars[column];
1826
+ if (!POINTERS.has(char)) return false;
1827
+ if (char !== "^" && char !== "v") return true;
1828
+ return (column === 0 || /\s/u.test(chars[column - 1] ?? "")) && (column + 1 >= chars.length || /\s/u.test(chars[column + 1] ?? ""));
1829
+ }
1830
+ function nearestUnclaimedMarkerTrack(tracks, row, column) {
1831
+ return tracks.filter(
1832
+ (track) => track.events.some((event) => !event.id && Math.abs(event.column - column) <= 1)
1833
+ ).sort((a, b) => Math.abs(a.row - row) - Math.abs(b.row - row))[0];
1834
+ }
1835
+ function nearestEvent(events, column) {
1836
+ return [...events].sort(
1837
+ (a, b) => Math.abs(a.column - column) - Math.abs(b.column - column) || Number(a.callout === false) - Number(b.callout === false)
1838
+ )[0];
1839
+ }
1840
+ function nearestSegment(segments, column, threshold) {
1841
+ return [...segments].map((segment) => ({ segment, distance: Math.abs((segment.start + segment.end) / 2 - column) })).filter((candidate) => candidate.distance <= threshold).sort((a, b) => a.distance - b.distance)[0]?.segment;
1842
+ }
1843
+ function nearestEventGap(events, column) {
1844
+ const gaps = events.filter((event) => event.column !== column).map((event) => Math.abs(event.column - column));
1845
+ return gaps.length > 0 ? Math.min(...gaps) : 12;
1846
+ }
1847
+ function previousNonBlankRow(lines, from) {
1848
+ for (let row = from; row >= 0; row--) if (lines[row].trim()) return row;
1849
+ return -1;
1850
+ }
1851
+ function markerKind(glyph) {
1852
+ if (glyph === "\u25C6" || glyph === "\u25C7") return "diamond";
1853
+ if (glyph === "\u25CB" || glyph === "o" || glyph === "O") return "hollow";
1854
+ return "filled";
1855
+ }
1856
+ function isMarkerAt(chars, index, style) {
1857
+ const glyph = chars[index];
1858
+ if (STRONG_MARKERS.has(glyph)) return true;
1859
+ if (style !== "ascii" || !ASCII_MARKERS.has(glyph)) return false;
1860
+ const leftRail = railRun(chars, index, -1);
1861
+ const rightRail = railRun(chars, index, 1);
1862
+ const beforeIsWord = /[\p{L}\p{N}_]/u.test(chars[index - 1] ?? "");
1863
+ const afterIsWord = /[\p{L}\p{N}_]/u.test(chars[index + 1] ?? "");
1864
+ return leftRail >= 2 && !afterIsWord || rightRail >= 2 && !beforeIsWord;
1865
+ }
1866
+ function railRun(chars, index, direction) {
1867
+ let count = 0;
1868
+ for (let cursor = index + direction; HORIZONTAL.has(chars[cursor] ?? ""); cursor += direction) {
1869
+ count++;
1870
+ }
1871
+ return count;
1872
+ }
1873
+ function cleanCalloutText(text) {
1874
+ return text.replace(/^[\s│|└┌├┬┘┐╰╭╯╮─━═╌╍┄┅┈┉-]+/u, "").replace(/[\s│|└┌├┬┘┐╰╭╯╮─━═╌╍┄┅┈┉►▶▷→>-]+$/u, "").replace(/\s+/g, " ").trim();
1875
+ }
1876
+ function cleanProseText(text) {
1877
+ return text.replace(/^[\s│|]+/u, "").replace(/[\s│|]+$/u, "").replace(/\s+/g, " ").trim();
1878
+ }
1879
+ function stripTrailingColon(text) {
1880
+ return text.replace(/:\s*$/, "").trim();
1881
+ }
1882
+ function slug(text) {
1883
+ return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "event";
1884
+ }
1885
+ function dedupe(base, used) {
1886
+ const count = used.get(base) ?? 0;
1887
+ used.set(base, count + 1);
1888
+ return count === 0 ? base : `${base}-${count + 1}`;
1889
+ }
1890
+ function detectStyle2(lines) {
1891
+ for (const line of lines) {
1892
+ for (const ch of line) {
1893
+ if (STRONG_MARKERS.has(ch) || HORIZONTAL.has(ch) && ch !== "-" || ["\u25BA", "\u25B6", "\u25B7", "\u2192"].includes(ch)) {
1894
+ return "unicode";
1895
+ }
1896
+ }
1897
+ }
1898
+ return "ascii";
1899
+ }
1900
+
1901
+ // src/doc/asciiTimeline/detect.ts
1902
+ var ASCII_TIMELINE_FENCE_LANGS = /* @__PURE__ */ new Set([
1903
+ "text",
1904
+ "txt",
1905
+ "plaintext",
1906
+ "plain",
1907
+ "ascii",
1908
+ "timeline"
1909
+ ]);
1910
+ var MAX_LINES2 = 400;
1911
+ var MAX_CANONICAL_LINES = 2e3;
1912
+ var MAX_COLS2 = 400;
1913
+ var MAX_EXPLICIT_COLS = 4096;
1914
+ var BOX_TOP_EDGE_RE = /[┌┏╔╭].*[┐┓╗╮]/u;
1915
+ var CANONICAL_TRACK_RE = /\{#[^{}\s]+ start=[^{}\s]+ end=[^{}\s]+\}:\s/u;
1916
+ function isEligibleAsciiTimelineFenceLang(lang) {
1917
+ if (lang === null || lang === void 0) return true;
1918
+ const normalized = lang.trim().toLowerCase();
1919
+ return normalized === "" || ASCII_TIMELINE_FENCE_LANGS.has(normalized);
1920
+ }
1921
+ function isExplicitTimelineLang(lang) {
1922
+ return typeof lang === "string" && lang.trim().toLowerCase() === "timeline";
1923
+ }
1924
+ function detectAsciiTimeline(text, options = {}) {
1925
+ const explicit = options.explicit === true;
1926
+ const lines = text.replace(/\r\n?/g, "\n").split("\n");
1927
+ const canonicalTrackRows = lines.filter((line) => CANONICAL_TRACK_RE.test(line)).length;
1928
+ const canonicalLineBudget = Math.min(
1929
+ MAX_CANONICAL_LINES,
1930
+ canonicalTrackRows * 6 + lines.filter((line) => /^\s*(?:branch|link)\s*:/iu.test(line)).length + 2
1931
+ );
1932
+ if (lines.length > MAX_LINES2 && (canonicalTrackRows === 0 || lines.length > canonicalLineBudget)) {
1933
+ return { isTimeline: false, reasons: [`too-many-lines(${lines.length})`] };
1934
+ }
1935
+ const maxCols = Math.max(0, ...lines.map((line) => Array.from(line).length));
1936
+ const canonicalLongForm = canonicalTrackRows > 0 && lines.every((line) => Array.from(line).length <= MAX_COLS2 || CANONICAL_TRACK_RE.test(line));
1937
+ const maxAllowedCols = explicit || canonicalLongForm ? MAX_EXPLICIT_COLS : MAX_COLS2;
1938
+ if (maxCols > maxAllowedCols) {
1939
+ return { isTimeline: false, reasons: [`too-wide(${maxCols})`] };
1940
+ }
1941
+ const nonBlank = lines.filter((line) => line.trim().length > 0);
1942
+ const pipeRows = nonBlank.filter((line) => /^\s*\|.*\|\s*$/.test(line)).length;
1943
+ const hasGfmSeparator = nonBlank.some(
1944
+ (line) => /^\s*\|?[\s:|-]+\|?\s*$/.test(line) && line.includes("-")
1945
+ );
1946
+ if (pipeRows > nonBlank.length / 2 && hasGfmSeparator) {
1947
+ return { isTimeline: false, reasons: ["markdown-table"] };
1948
+ }
1949
+ if (!explicit && lines.some((line) => BOX_TOP_EDGE_RE.test(line))) {
1950
+ return { isTimeline: false, reasons: ["has-box-corners"] };
1951
+ }
1952
+ const boxes = parseAsciiDiagramWithStats(text).diagram.nodes.length;
1953
+ if (boxes >= (explicit ? 2 : 1)) {
1954
+ return { isTimeline: false, reasons: [`has-boxes(${boxes})`] };
1955
+ }
1956
+ if (!explicit && /^[ \t]*(?:[│|][ \t]*)*[├└+|`][─━═-]{1,4}[ \t]+[●○◉◆◇•]/mu.test(text)) {
1957
+ return { isTimeline: false, reasons: ["tree-connector-prefix"] };
1958
+ }
1959
+ const { timeline, stats } = parseAsciiTimelineWithStats(text);
1960
+ if (stats.wrappedFlow) {
1961
+ return {
1962
+ isTimeline: true,
1963
+ timeline,
1964
+ reasons: [`tracks(${stats.axisLines})`, `events(${stats.markerCount})`, "wrapped-flow"]
1965
+ };
1966
+ }
1967
+ if (stats.axisLines === 0) return { isTimeline: false, reasons: ["no-axis-lines"] };
1968
+ const explicitCadence = explicit && stats.horizontalChars === 0 && stats.markerCount >= 4;
1969
+ if (!explicitCadence && stats.horizontalChars < (explicit ? 2 : 8)) {
1970
+ return { isTimeline: false, reasons: [`too-few-horizontal(${stats.horizontalChars})`] };
1971
+ }
1972
+ if (stats.markerCount < (explicit ? 1 : 2)) {
1973
+ return { isTimeline: false, reasons: [`too-few-markers(${stats.markerCount})`] };
1974
+ }
1975
+ if (!explicit && stats.strongMarkerCount < 2) {
1976
+ return { isTimeline: false, reasons: [`too-few-strong-markers(${stats.strongMarkerCount})`] };
1977
+ }
1978
+ return {
1979
+ isTimeline: true,
1980
+ timeline,
1981
+ reasons: [
1982
+ `tracks(${stats.axisLines})`,
1983
+ `events(${stats.markerCount + stats.pointerCount})`,
1984
+ explicit ? "explicit" : "strong-markers"
1985
+ ]
1986
+ };
1987
+ }
1988
+ function isAsciiTimelineFence(node) {
1989
+ if (node.type !== "code") return false;
1990
+ if (!isEligibleAsciiTimelineFenceLang(node.lang)) return false;
1991
+ return detectAsciiTimeline(node.value, { explicit: isExplicitTimelineLang(node.lang) }).isTimeline;
1992
+ }
1993
+
1994
+ // src/doc/treeview/chars.ts
1995
+ var RAIL_CHARS = /* @__PURE__ */ new Set(["\u2502", "\u2503", "\u254E", "\u254F", "\u2551", "|"]);
1996
+ var BRANCH_TEE = /* @__PURE__ */ new Set(["\u251C", "\u2523", "\u2520", "\u251D", "\u2560", "\u255E"]);
1997
+ var BRANCH_ELBOW = /* @__PURE__ */ new Set(["\u2514", "\u2517", "\u2570", "\u255A", "\u2558"]);
1998
+ var DASH_CHARS = /* @__PURE__ */ new Set(["\u2500", "\u2501", "\u2550", "-"]);
1999
+ function isRailChar(ch) {
2000
+ return RAIL_CHARS.has(ch);
2001
+ }
2002
+ function isBranchTee(ch) {
2003
+ return BRANCH_TEE.has(ch);
2004
+ }
2005
+ function isBranchElbow(ch) {
2006
+ return BRANCH_ELBOW.has(ch);
2007
+ }
2008
+ function isDashChar(ch) {
2009
+ return DASH_CHARS.has(ch);
2010
+ }
2011
+ function isUnicodeTreeChar(ch) {
2012
+ return BRANCH_TEE.has(ch) || BRANCH_ELBOW.has(ch) || ch === "\u2502" || ch === "\u2503" || ch === "\u2500" || ch === "\u2501";
2013
+ }
2014
+ function isAsciiTreeChar(ch) {
2015
+ return ch === "|" || ch === "`" || ch === "+" || ch === "-";
2016
+ }
2017
+ var UNICODE_TREE_VOCAB = {
2018
+ tee: "\u251C\u2500\u2500 ",
2019
+ elbow: "\u2514\u2500\u2500 ",
2020
+ rail: "\u2502 ",
2021
+ gap: " "
2022
+ };
2023
+ var ASCII_TREE_VOCAB = {
2024
+ tee: "|-- ",
2025
+ elbow: "`-- ",
2026
+ rail: "| ",
2027
+ gap: " "
2028
+ };
2029
+
2030
+ // src/doc/treeview/parse.ts
2031
+ function parseTree(text) {
2032
+ return parseTreeWithStats(text).tree;
2033
+ }
2034
+ function parseTreeWithStats(text) {
2035
+ const warnings = [];
2036
+ const rawLines = text.replace(/\r\n?/g, "\n").split("\n");
2037
+ const style = detectStyle3(rawLines);
2038
+ const stats = {
2039
+ contentLines: 0,
2040
+ connectorLines: 0,
2041
+ looseLines: 0,
2042
+ indentLevels: 0
2043
+ };
2044
+ const indentSet = /* @__PURE__ */ new Set();
2045
+ const analyzed = [];
2046
+ for (const raw of rawLines) {
2047
+ if (raw.trim().length === 0) continue;
2048
+ stats.contentLines++;
2049
+ const a = analyzeLine(raw);
2050
+ if (!a || a.label.length === 0) {
2051
+ stats.looseLines++;
2052
+ continue;
2053
+ }
2054
+ if (a.hadConnector) stats.connectorLines++;
2055
+ indentSet.add(a.indent);
2056
+ analyzed.push(a);
2057
+ }
2058
+ stats.indentLevels = indentSet.size;
2059
+ const roots = [];
2060
+ const stack = [];
2061
+ for (const a of analyzed) {
2062
+ const node = {
2063
+ id: "",
2064
+ label: a.label,
2065
+ children: [],
2066
+ ...a.comment ? { comment: a.comment } : {}
2067
+ };
2068
+ while (stack.length > 0 && stack[stack.length - 1].indent >= a.indent) stack.pop();
2069
+ if (stack.length === 0) {
2070
+ roots.push(node);
2071
+ } else {
2072
+ stack[stack.length - 1].node.children.push(node);
2073
+ }
2074
+ stack.push({ indent: a.indent, node });
2075
+ }
2076
+ const usedIds = /* @__PURE__ */ new Map();
2077
+ const finalize = (node) => {
2078
+ node.isDir = node.label.endsWith("/") || node.children.length > 0;
2079
+ const base = slugify2(stripTrailingSlash(node.label));
2080
+ const count = usedIds.get(base) ?? 0;
2081
+ usedIds.set(base, count + 1);
2082
+ node.id = count === 0 ? base : `${base}-${count + 1}`;
2083
+ for (const child of node.children) finalize(child);
2084
+ };
2085
+ for (const root of roots) finalize(root);
2086
+ if (analyzed.length > 0 && stats.looseLines > 0) {
2087
+ warnings.push(`loose-lines(${stats.looseLines})`);
2088
+ }
2089
+ return { tree: { roots, style, warnings }, stats };
2090
+ }
2091
+ function analyzeLine(line) {
2092
+ const chars = Array.from(line);
2093
+ let i = 0;
2094
+ let hadConnector = false;
2095
+ for (; ; ) {
2096
+ while (i < chars.length && (chars[i] === " " || chars[i] === " ")) i++;
2097
+ const ch2 = chars[i];
2098
+ if (ch2 === void 0) return null;
2099
+ if (isRailChar(ch2) && !isDashChar(chars[i + 1] ?? "")) {
2100
+ i++;
2101
+ continue;
2102
+ }
2103
+ break;
2104
+ }
2105
+ const ch = chars[i];
2106
+ if (ch !== void 0) {
2107
+ if (isBranchTee(ch) || isBranchElbow(ch)) {
2108
+ i++;
2109
+ while (i < chars.length && (isDashChar(chars[i]) || isRailChar(chars[i]))) i++;
2110
+ hadConnector = true;
2111
+ } else if ((ch === "|" || ch === "`" || ch === "+") && isDashChar(chars[i + 1] ?? "")) {
2112
+ i++;
2113
+ while (i < chars.length && isDashChar(chars[i])) i++;
2114
+ hadConnector = true;
2115
+ } else if ((ch === "-" || ch === "*" || ch === "+") && (chars[i + 1] ?? "") === " ") {
2116
+ i++;
2117
+ hadConnector = false;
2118
+ }
2119
+ }
2120
+ while (i < chars.length && (chars[i] === " " || chars[i] === " ")) i++;
2121
+ const indent = i;
2122
+ let label = chars.slice(i).join("").replace(/\s+$/u, "");
2123
+ if (label.length === 0) return null;
2124
+ let comment;
2125
+ const commentMatch = /\s+(?:#|<--|\/\/)\s?(.*)$/.exec(label);
2126
+ if (commentMatch) {
2127
+ comment = commentMatch[1].trim() || void 0;
2128
+ label = label.slice(0, commentMatch.index).replace(/\s+$/u, "");
2129
+ }
2130
+ if (label.length === 0) return null;
2131
+ return { indent, label, hadConnector, ...comment ? { comment } : {} };
2132
+ }
2133
+ function detectStyle3(lines) {
2134
+ let unicode = 0;
2135
+ let ascii = 0;
2136
+ for (const line of lines) {
2137
+ for (const ch of line) {
2138
+ if (isUnicodeTreeChar(ch)) unicode++;
2139
+ else if (isAsciiTreeChar(ch)) ascii++;
2140
+ }
2141
+ }
2142
+ return ascii > unicode ? "ascii" : "unicode";
2143
+ }
2144
+ function stripTrailingSlash(s) {
2145
+ return s.endsWith("/") ? s.slice(0, -1) : s;
2146
+ }
2147
+ function slugify2(s) {
2148
+ const slug2 = s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
2149
+ return slug2 || "node";
2150
+ }
2151
+
2152
+ // src/doc/treeview/detect.ts
2153
+ var TREE_FENCE_LANGS = /* @__PURE__ */ new Set([
2154
+ "text",
2155
+ "txt",
2156
+ "plaintext",
2157
+ "plain",
2158
+ "ascii",
2159
+ "tree"
2160
+ ]);
2161
+ var MAX_LINES3 = 400;
2162
+ var MIN_LINES2 = 2;
2163
+ var MIN_CONNECTOR_LINES = 2;
2164
+ var MIN_NODES = 2;
2165
+ var BOX_CORNER_RE = /[┌┐┘┏┓┛╔╗╝╭╮╯]/u;
2166
+ function isEligibleTreeFenceLang(lang) {
2167
+ if (lang === null || lang === void 0) return true;
2168
+ const normalized = lang.trim().toLowerCase();
2169
+ return normalized === "" || TREE_FENCE_LANGS.has(normalized);
2170
+ }
2171
+ function isExplicitTreeLang(lang) {
2172
+ return typeof lang === "string" && lang.trim().toLowerCase() === "tree";
2173
+ }
2174
+ function detectTree(text, opts = {}) {
2175
+ const explicit = opts.explicit === true;
2176
+ const lines = text.replace(/\r\n?/g, "\n").split("\n");
2177
+ const nonBlank = lines.filter((l) => l.trim().length > 0);
2178
+ const minLines = explicit ? 1 : MIN_LINES2;
2179
+ if (nonBlank.length < minLines) {
2180
+ return { isTree: false, reasons: [`too-few-lines(${nonBlank.length})`] };
2181
+ }
2182
+ if (lines.length > MAX_LINES3) {
2183
+ return { isTree: false, reasons: [`too-many-lines(${lines.length})`] };
2184
+ }
2185
+ const pipeRows = nonBlank.filter((l) => /^\s*\|.*\|\s*$/.test(l)).length;
2186
+ const hasGfmSeparator = nonBlank.some((l) => /^\s*\|?[\s:|-]+\|?\s*$/.test(l) && l.includes("-"));
2187
+ if (pipeRows > nonBlank.length / 2 && hasGfmSeparator) {
2188
+ return { isTree: false, reasons: ["markdown-table"] };
2189
+ }
2190
+ if (BOX_CORNER_RE.test(text)) {
2191
+ return { isTree: false, reasons: ["has-box-corners"] };
2192
+ }
2193
+ if (!explicit && detectAsciiTimeline(text).isTimeline) {
2194
+ return { isTree: false, reasons: ["is-timeline"] };
2195
+ }
2196
+ const boxes = parseAsciiDiagramWithStats(text).diagram.nodes.length;
2197
+ if (boxes >= 2) {
2198
+ return { isTree: false, reasons: [`has-boxes(${boxes})`] };
2199
+ }
2200
+ const { tree, stats } = parseTreeWithStats(text);
2201
+ const nodeCount = countNodes(tree.roots);
2202
+ const minNodes = explicit ? 1 : MIN_NODES;
2203
+ if (nodeCount < minNodes) {
2204
+ return { isTree: false, reasons: [`too-few-nodes(${nodeCount})`] };
2205
+ }
2206
+ if (!explicit) {
2207
+ if (stats.connectorLines < MIN_CONNECTOR_LINES) {
2208
+ return { isTree: false, reasons: [`too-few-connectors(${stats.connectorLines})`] };
2209
+ }
2210
+ if (stats.looseLines > stats.contentLines * 0.25) {
2211
+ return { isTree: false, reasons: [`loose-ratio(${stats.looseLines}/${stats.contentLines})`] };
2212
+ }
2213
+ }
2214
+ return {
2215
+ isTree: true,
2216
+ tree,
2217
+ reasons: [`nodes(${nodeCount})`, explicit ? "explicit" : `connectors(${stats.connectorLines})`]
2218
+ };
2219
+ }
2220
+ function isTreeFence(node) {
2221
+ if (node.type !== "code") return false;
2222
+ if (!isEligibleTreeFenceLang(node.lang)) return false;
2223
+ return detectTree(node.value, { explicit: isExplicitTreeLang(node.lang) }).isTree;
2224
+ }
2225
+ function countNodes(nodes) {
2226
+ let n = 0;
2227
+ for (const node of nodes) n += 1 + countNodes(node.children);
2228
+ return n;
2229
+ }
2230
+
2231
+ // src/recommend/numberHighlight.ts
2232
+ var NUMBER_HIGHLIGHT_RE = /(?:[$€£¥]\s?\d+(?:[.,]\d+)*(?:\s?(?:[MBK]|million|billion|thousand))?|\d+(?:[.,]\d+)*\s?(?:%|‰|x|×|[MBK]|million|billion|thousand|percent|years?|days?|hours?)|\d{3,}(?:[.,]\d+)*)/i;
2233
+ function matchNumberHighlight(text) {
2234
+ const match = NUMBER_HIGHLIGHT_RE.exec(text);
2235
+ if (!match || match.index === void 0) return null;
2236
+ return {
2237
+ value: match[0],
2238
+ index: match.index,
2239
+ end: match.index + match[0].length
2240
+ };
2241
+ }
2242
+
2243
+ // src/recommend/templates.ts
2244
+ var EMPTY_PROFILE = Object.freeze({
2245
+ hasImage: false,
2246
+ imageCount: 0,
2247
+ hasVideo: false,
2248
+ hasBlockquote: false,
2249
+ hasList: false,
2250
+ hasTable: false,
2251
+ hasDate: false,
2252
+ hasNumberHighlight: false,
2253
+ wordCount: 0
2254
+ });
2255
+ var DATE_PATTERNS = [
2256
+ /\b(January|February|March|April|May|June|July|August|September|October|November|December)\s+(\d{1,2},?\s+)?\d{4}\b/i,
2257
+ /\b\d{1,2}\s+(January|February|March|April|May|June|July|August|September|October|November|December)\s+\d{4}\b/i,
2258
+ /\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Sept|Oct|Nov|Dec)\.?\s+\d{1,2},?\s+\d{4}\b/i,
2259
+ /\b\d{4}-\d{2}-\d{2}\b/,
2260
+ /\b\d{1,2}\/\d{1,2}\/\d{2,4}\b/,
2261
+ /\bQ[1-4]\s+\d{4}\b/,
2262
+ /\b\d{4}s\b/,
2263
+ /\b\d{1,2}(st|nd|rd|th)\s+century\b/i
2264
+ ];
2265
+ var VIDEO_HOST_RE = /(youtube\.com|youtu\.be|vimeo\.com|wistia\.|loom\.com)/i;
2266
+ function htmlElementsByTag(root, tagNames) {
2267
+ const blocks = findNodesByType(root, "htmlBlock");
2268
+ const inline = findNodesByType(root, "htmlInline");
2269
+ const out = [];
2270
+ const visit = (n) => {
2271
+ if (n.type === "htmlElement") {
2272
+ if (tagNames.has(n.tagName.toLowerCase())) out.push(n);
2273
+ for (const c of n.children) visit(c);
2274
+ }
2275
+ };
2276
+ for (const b of blocks) for (const c of b.htmlChildren) visit(c);
2277
+ for (const i of inline) {
2278
+ if (Array.isArray(i.htmlChildren)) for (const c of i.htmlChildren) visit(c);
2279
+ }
2280
+ return out;
2281
+ }
2282
+ function profileBlockContents(nodes) {
2283
+ if (!nodes || nodes.length === 0) return { ...EMPTY_PROFILE };
2284
+ let imageCount = 0;
2285
+ let hasBlockquote = false;
2286
+ let hasList = false;
2287
+ let hasTable = false;
2288
+ const plainParts = [];
2289
+ for (const node of nodes) {
2290
+ imageCount += findNodesByType(node, "image").length;
2291
+ imageCount += findNodesByType(node, "imageReference").length;
2292
+ if (findNodesByType(node, "blockquote").length > 0) hasBlockquote = true;
2293
+ if (findNodesByType(node, "list").length > 0) hasList = true;
2294
+ if (findNodesByType(node, "table").length > 0) hasTable = true;
2295
+ plainParts.push(extractPlainText(node));
2296
+ }
2297
+ const root = { type: "document", children: nodes };
2298
+ const imgTags = htmlElementsByTag(root, /* @__PURE__ */ new Set(["img"]));
2299
+ imageCount += imgTags.length;
2300
+ const videoTags = htmlElementsByTag(root, /* @__PURE__ */ new Set(["video", "iframe", "source"]));
2301
+ let hasVideo = false;
2302
+ for (const el of videoTags) {
2303
+ if (el.tagName.toLowerCase() === "video") {
2304
+ hasVideo = true;
2305
+ break;
2306
+ }
2307
+ const src = el.attributes.src || el.attributes.href || "";
2308
+ if (VIDEO_HOST_RE.test(src)) {
2309
+ hasVideo = true;
2310
+ break;
2311
+ }
2312
+ }
2313
+ if (!hasVideo) {
2314
+ const links = findNodesByType(root, "link");
2315
+ for (const link of links) {
2316
+ if (link.url && VIDEO_HOST_RE.test(link.url)) {
2317
+ hasVideo = true;
2318
+ break;
2319
+ }
2320
+ }
2321
+ }
2322
+ const plainText = plainParts.join(" ").trim();
2323
+ const wordCount = plainText ? plainText.split(/\s+/).length : 0;
2324
+ let hasDate = false;
2325
+ for (const re of DATE_PATTERNS) {
2326
+ if (re.test(plainText)) {
2327
+ hasDate = true;
2328
+ break;
2329
+ }
2330
+ }
2331
+ let hasNumberHighlight = false;
2332
+ walkMarkdownTree(root, (n) => {
2333
+ if (hasNumberHighlight) return true;
2334
+ if (n.type !== "paragraph") return;
2335
+ const text = extractPlainText(n).trim();
2336
+ if (!text) return true;
2337
+ const m = matchNumberHighlight(text);
2338
+ if (!m) return true;
2339
+ const words = text.split(/\s+/).length;
2340
+ if (words <= 8) hasNumberHighlight = true;
2341
+ return true;
2342
+ });
2343
+ let hasAsciiDiagram = false;
2344
+ const codeNodes = [];
2345
+ for (const node of nodes) codeNodes.push(...findNodesByType(node, "code"));
2346
+ if (codeNodes.length === 1 && isEligibleAsciiFenceLang(codeNodes[0].lang) && !hasTable && imageCount === 0 && !hasVideo) {
2347
+ const nonCodeChars = Math.max(0, plainText.length - codeNodes[0].value.length);
2348
+ const explicit = isExplicitDiagramLang(codeNodes[0].lang);
2349
+ if (nonCodeChars <= 400 && detectAsciiDiagram(codeNodes[0].value, { explicit }).isDiagram) {
2350
+ hasAsciiDiagram = true;
2351
+ }
2352
+ }
2353
+ let hasTimeline = false;
2354
+ if (!hasAsciiDiagram && codeNodes.length === 1 && isEligibleAsciiTimelineFenceLang(codeNodes[0].lang) && !hasTable && imageCount === 0 && !hasVideo) {
2355
+ const nonCodeChars = Math.max(0, plainText.length - codeNodes[0].value.length);
2356
+ const explicit = isExplicitTimelineLang(codeNodes[0].lang);
2357
+ if (nonCodeChars <= 400 && detectAsciiTimeline(codeNodes[0].value, { explicit }).isTimeline) {
2358
+ hasTimeline = true;
2359
+ }
2360
+ }
2361
+ let hasTree = false;
2362
+ if (!hasAsciiDiagram && !hasTimeline && codeNodes.length === 1 && isEligibleTreeFenceLang(codeNodes[0].lang) && !hasTable && imageCount === 0 && !hasVideo) {
2363
+ const nonCodeChars = Math.max(0, plainText.length - codeNodes[0].value.length);
2364
+ const explicit = isExplicitTreeLang(codeNodes[0].lang);
2365
+ if (nonCodeChars <= 400 && detectTree(codeNodes[0].value, { explicit }).isTree) {
2366
+ hasTree = true;
2367
+ }
2368
+ }
2369
+ return {
2370
+ hasImage: imageCount > 0,
2371
+ imageCount,
2372
+ hasVideo,
2373
+ hasBlockquote,
2374
+ hasList,
2375
+ hasTable,
2376
+ hasDate,
2377
+ hasNumberHighlight,
2378
+ wordCount,
2379
+ hasAsciiDiagram,
2380
+ hasTimeline,
2381
+ hasTree
2382
+ };
2383
+ }
2384
+ var UNIVERSAL_DEFAULTS = ["title", "sectionHeader", "factCard", "twoColumn"];
2385
+ function recommendedNamesForProfile(profile) {
2386
+ const names = /* @__PURE__ */ new Set();
2387
+ let anyContentSignal = false;
2388
+ if (profile.hasAsciiDiagram) {
2389
+ anyContentSignal = true;
2390
+ names.add("diagram");
2391
+ }
2392
+ if (profile.hasTimeline) {
2393
+ anyContentSignal = true;
2394
+ names.add("timeline");
2395
+ }
2396
+ if (profile.hasTree) {
2397
+ anyContentSignal = true;
2398
+ names.add("tree");
2399
+ }
2400
+ if (profile.hasImage) {
2401
+ anyContentSignal = true;
2402
+ names.add("imageWithCaption");
2403
+ names.add("leftFeature");
2404
+ names.add("rightFeature");
2405
+ if (profile.imageCount >= 2) names.add("photoGrid");
2406
+ }
2407
+ if (profile.hasVideo) {
2408
+ anyContentSignal = true;
2409
+ names.add("videoWithCaption");
2410
+ if (profile.hasBlockquote) names.add("videoPullQuote");
2411
+ }
2412
+ if (profile.hasBlockquote) {
2413
+ anyContentSignal = true;
2414
+ names.add("quote");
2415
+ names.add("pullQuote");
2416
+ names.add("fullBleedQuote");
2417
+ }
2418
+ if (profile.hasList) {
2419
+ anyContentSignal = true;
2420
+ names.add("list");
2421
+ }
2422
+ if (profile.hasTable) {
2423
+ anyContentSignal = true;
2424
+ names.add("dataTable");
2425
+ names.add("comparisonBar");
2426
+ }
2427
+ if (profile.hasDate) {
2428
+ anyContentSignal = true;
2429
+ names.add("dateEvent");
2430
+ }
2431
+ if (profile.hasNumberHighlight) {
2432
+ anyContentSignal = true;
2433
+ names.add("statHighlight");
2434
+ }
2435
+ if (anyContentSignal) {
2436
+ names.add("title");
2437
+ names.add("sectionHeader");
2438
+ } else {
2439
+ for (const n of UNIVERSAL_DEFAULTS) names.add(n);
2440
+ }
2441
+ return Array.from(names);
2442
+ }
2443
+ function pickAutoTemplate(profile, blockIndex = 0) {
2444
+ if (profile.hasAsciiDiagram) return "diagram";
2445
+ if (profile.hasTimeline) return "timeline";
2446
+ if (profile.hasTree) return "tree";
2447
+ if (profile.hasTable) return "dataTable";
2448
+ if (profile.imageCount >= 2) return "photoGrid";
2449
+ if (profile.hasImage) return blockIndex % 2 === 0 ? "leftFeature" : "rightFeature";
2450
+ if (profile.hasBlockquote) return "quote";
2451
+ if (profile.hasNumberHighlight) return "statHighlight";
2452
+ if (profile.hasList) return "list";
2453
+ return void 0;
2454
+ }
2455
+ function recommendTemplatesForBlock(profile, allNames) {
2456
+ const desired = new Set(recommendedNamesForProfile(profile));
2457
+ const recommended = [];
2458
+ const rest = [];
2459
+ for (const name of allNames) {
2460
+ if (desired.has(name)) recommended.push(name);
2461
+ else rest.push(name);
2462
+ }
2463
+ return { recommended, rest };
2464
+ }
2465
+
2466
+ export {
2467
+ matchNumberHighlight,
2468
+ isHoriz,
2469
+ isVert,
2470
+ isJunction,
2471
+ arrowDirection,
2472
+ UNICODE_VOCAB,
2473
+ ASCII_VOCAB,
2474
+ parseAsciiDiagram,
2475
+ traceBoxes,
2476
+ toGrid,
2477
+ ASCII_DIAGRAM_FENCE_LANGS,
2478
+ isEligibleAsciiFenceLang,
2479
+ isExplicitDiagramLang,
2480
+ detectAsciiDiagram,
2481
+ isAsciiDiagramFence,
2482
+ parseWrappedFlowTimeline,
2483
+ isWrappedFlowTimelineSource,
2484
+ parseAsciiTimeline,
2485
+ parseAsciiTimelineWithStats,
2486
+ ASCII_TIMELINE_FENCE_LANGS,
2487
+ isEligibleAsciiTimelineFenceLang,
2488
+ isExplicitTimelineLang,
2489
+ detectAsciiTimeline,
2490
+ isAsciiTimelineFence,
2491
+ UNICODE_TREE_VOCAB,
2492
+ ASCII_TREE_VOCAB,
2493
+ parseTree,
2494
+ TREE_FENCE_LANGS,
2495
+ isEligibleTreeFenceLang,
2496
+ isExplicitTreeLang,
2497
+ detectTree,
2498
+ isTreeFence,
2499
+ profileBlockContents,
2500
+ pickAutoTemplate,
2501
+ recommendTemplatesForBlock
2502
+ };
2503
+ //# sourceMappingURL=chunk-5UYI3RJB.js.map