@diagrammo/dgmo 0.46.0 → 0.48.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 (534) hide show
  1. package/dist/advanced.d.ts +3 -855
  2. package/dist/advanced.js +541 -70107
  3. package/dist/analyzer-UYKBXAKR.js +15 -0
  4. package/dist/auto.js +1 -1
  5. package/dist/auto.mjs +1 -1
  6. package/dist/block.js +83 -69417
  7. package/dist/calculator-LDYQCDDY.js +11 -0
  8. package/dist/charts-d3-QOZHKTCT.js +25 -0
  9. package/dist/chunk-2DXMZRWJ.js +800 -0
  10. package/dist/chunk-2LACINWN.js +786 -0
  11. package/dist/chunk-33LCUH4H.js +893 -0
  12. package/dist/chunk-3CIOJ5YJ.js +35 -0
  13. package/dist/chunk-3HDBAXF5.js +86 -0
  14. package/dist/chunk-3JLKYGBQ.js +290 -0
  15. package/dist/chunk-3MQQNXPD.js +415 -0
  16. package/dist/chunk-3R5POCTZ.js +967 -0
  17. package/dist/chunk-3SMGIJDD.js +112 -0
  18. package/dist/chunk-44CM72SQ.js +409 -0
  19. package/dist/chunk-4GRI7MEB.js +1604 -0
  20. package/dist/chunk-4OHACBGX.js +606 -0
  21. package/dist/chunk-5DH3KYFH.js +21 -0
  22. package/dist/chunk-5MLYVTYM.js +76 -0
  23. package/dist/chunk-5QXL4NYS.js +553 -0
  24. package/dist/chunk-5WRI5ZAA.js +31 -0
  25. package/dist/chunk-6EK55ART.js +117 -0
  26. package/dist/chunk-6FZYCKXB.js +2065 -0
  27. package/dist/chunk-6HO5ZTXK.js +277 -0
  28. package/dist/chunk-6KFWC4CO.js +7 -0
  29. package/dist/chunk-6MTWIQVO.js +121 -0
  30. package/dist/chunk-6NYWYNBI.js +329 -0
  31. package/dist/chunk-6SM5NJQH.js +620 -0
  32. package/dist/chunk-725LDASD.js +422 -0
  33. package/dist/chunk-76OWZ3BA.js +130 -0
  34. package/{src/block/parser.ts → dist/chunk-7JEGTRLD.js} +100 -189
  35. package/dist/chunk-A44USUV7.js +923 -0
  36. package/dist/chunk-A4GOY3PE.js +25 -0
  37. package/dist/chunk-A4VFQH5G.js +251 -0
  38. package/dist/chunk-AELOBFW5.js +375 -0
  39. package/dist/chunk-AN2MOO5T.js +522 -0
  40. package/dist/chunk-BMR4CIEO.js +41 -0
  41. package/dist/chunk-BS7SV7C3.js +51 -0
  42. package/dist/chunk-BUPDIHML.js +613 -0
  43. package/dist/chunk-BUVJFRPY.js +887 -0
  44. package/dist/chunk-C3Z4DX3V.js +112 -0
  45. package/dist/chunk-CI2G3OEB.js +1818 -0
  46. package/dist/chunk-CKNVO24D.js +54 -0
  47. package/dist/chunk-CUVHQHZ7.js +391 -0
  48. package/dist/chunk-CXK4DZH3.js +114 -0
  49. package/dist/{editor.cjs → chunk-DKEPS5LH.js} +14 -225
  50. package/dist/chunk-DU7W62N5.js +487 -0
  51. package/dist/chunk-DZAQBW35.js +751 -0
  52. package/dist/chunk-EB6FPQUP.js +316 -0
  53. package/dist/chunk-EMS2SK26.js +376 -0
  54. package/dist/chunk-ERLBPDGU.js +510 -0
  55. package/dist/chunk-EVCVDTXE.js +732 -0
  56. package/dist/chunk-FCIIHT24.js +384 -0
  57. package/dist/chunk-FFUN5D66.js +97 -0
  58. package/dist/chunk-FITOLXSX.js +714 -0
  59. package/dist/chunk-FMDSF3OV.js +68 -0
  60. package/dist/chunk-FNB3PNGF.js +410 -0
  61. package/dist/chunk-G2H74XJZ.js +2104 -0
  62. package/dist/chunk-GMR5MFHJ.js +339 -0
  63. package/dist/chunk-H6PI3KJT.js +305 -0
  64. package/dist/chunk-I46X5VUA.js +59 -0
  65. package/dist/chunk-I5IGB56C.js +255 -0
  66. package/dist/chunk-IH6ZMVQ5.js +3950 -0
  67. package/dist/chunk-IJZFN3HM.js +463 -0
  68. package/dist/chunk-IPWPWM6R.js +3079 -0
  69. package/dist/chunk-IYULMRBE.js +23 -0
  70. package/dist/chunk-IZMRON6C.js +167 -0
  71. package/dist/chunk-J5FIXQRT.js +813 -0
  72. package/dist/chunk-JED6ZDEE.js +73 -0
  73. package/dist/chunk-JEUJOX5X.js +136 -0
  74. package/dist/chunk-JM4LEPY3.js +436 -0
  75. package/dist/chunk-L3UHP6TD.js +101 -0
  76. package/dist/chunk-L66GEFB6.js +524 -0
  77. package/dist/chunk-LIOHW36Z.js +469 -0
  78. package/dist/chunk-LP7EC4MI.js +16 -0
  79. package/dist/chunk-MITI6NOL.js +548 -0
  80. package/dist/chunk-MO5YHHMQ.js +168 -0
  81. package/dist/chunk-MZSBFAPJ.js +885 -0
  82. package/dist/chunk-NNAAJVBU.js +9 -0
  83. package/dist/chunk-NOTA6TEI.js +47 -0
  84. package/dist/chunk-NURGB5Z4.js +335 -0
  85. package/dist/chunk-NX4EQS3U.js +1062 -0
  86. package/dist/chunk-NXMESY4J.js +1 -0
  87. package/dist/chunk-OJR3MQYV.js +1458 -0
  88. package/dist/chunk-OURHMODR.js +454 -0
  89. package/dist/chunk-P235SVAH.js +1057 -0
  90. package/dist/chunk-P4RH4OSP.js +1038 -0
  91. package/dist/chunk-P6T66CCZ.js +226 -0
  92. package/dist/chunk-PBAHEPLQ.js +223 -0
  93. package/dist/chunk-PBHE5UG6.js +1530 -0
  94. package/dist/chunk-PDKUANDZ.js +35 -0
  95. package/dist/chunk-PWIHZ6NJ.js +296 -0
  96. package/{src/c4/layout.ts → dist/chunk-QEBUY5NY.js} +384 -1140
  97. package/dist/chunk-QLCCJEJI.js +253 -0
  98. package/dist/chunk-QOOUC2J5.js +52 -0
  99. package/dist/chunk-QYTPPDAO.js +402 -0
  100. package/dist/chunk-R3XI2VC7.js +801 -0
  101. package/dist/chunk-RCERWOJR.js +154 -0
  102. package/dist/chunk-RJE5JTVK.js +362 -0
  103. package/dist/chunk-RTLBSQ3G.js +1155 -0
  104. package/dist/chunk-RUNNI6VE.js +448 -0
  105. package/dist/chunk-RVMRZAPB.js +354 -0
  106. package/dist/chunk-RYEGFMUO.js +955 -0
  107. package/dist/chunk-RZCF2VVM.js +528 -0
  108. package/dist/chunk-SML67VPV.js +1025 -0
  109. package/dist/chunk-SQ7IY53Q.js +44 -0
  110. package/dist/chunk-SQAG3UXH.js +161 -0
  111. package/dist/chunk-SRTU6HMQ.js +1851 -0
  112. package/dist/chunk-T2OE3YQB.js +550 -0
  113. package/dist/chunk-T3UBLACL.js +299 -0
  114. package/dist/chunk-TLVIHFRO.js +296 -0
  115. package/dist/chunk-TYS6CSS5.js +52 -0
  116. package/dist/chunk-U6EQI3U6.js +130 -0
  117. package/dist/chunk-UJ4FJLBF.js +86 -0
  118. package/dist/chunk-ULOEATBZ.js +347 -0
  119. package/dist/chunk-UNCCZ3NU.js +127 -0
  120. package/dist/chunk-USZW2HHU.js +197 -0
  121. package/dist/chunk-UWN33VNX.js +1610 -0
  122. package/dist/chunk-UZ7EZW3B.js +40 -0
  123. package/dist/chunk-VOKGH627.js +37 -0
  124. package/dist/chunk-WBRZJOEC.js +204 -0
  125. package/dist/chunk-WCSOJFU3.js +380 -0
  126. package/dist/chunk-WCT3NYCV.js +160 -0
  127. package/dist/chunk-WVAJFAIA.js +529 -0
  128. package/dist/chunk-X5LCYCL5.js +394 -0
  129. package/dist/chunk-XAKEUMEJ.js +1256 -0
  130. package/dist/chunk-Y2ZE3MSG.js +590 -0
  131. package/dist/chunk-Y3J7VR52.js +646 -0
  132. package/dist/chunk-Y5QGVTVC.js +33 -0
  133. package/dist/chunk-YAA6IID3.js +548 -0
  134. package/dist/chunk-YGJTDXVR.js +692 -0
  135. package/{src/utils/time-ticks.ts → dist/chunk-YRYKTYMQ.js} +45 -112
  136. package/dist/chunk-YYVTBZZC.js +61 -0
  137. package/dist/chunk-Z5CGSFJB.js +647 -0
  138. package/{src/gantt/calculator.ts → dist/chunk-Z72XRXD4.js} +236 -428
  139. package/dist/chunk-ZGR7ULFJ.js +978 -0
  140. package/dist/chunk-ZJEL4H2R.js +851 -0
  141. package/dist/chunk-ZO27FGJS.js +554 -0
  142. package/dist/collapse-CLPJD2RL.js +11 -0
  143. package/dist/collapse-SLINJQW6.js +9 -0
  144. package/dist/collapse-ZWUFFCK2.js +9 -0
  145. package/dist/compute-G55RG53N.js +8 -0
  146. package/dist/dgmo-router-DZCIDUO5.js +73 -0
  147. package/dist/dimensions-J3FHIMJ6.js +22 -0
  148. package/dist/element.js +1 -1
  149. package/dist/element.mjs +1 -1
  150. package/dist/flowchart-parser-ZNSKIBVR.js +21 -0
  151. package/dist/flowchart-renderer-ZAEOL4DX.js +35 -0
  152. package/dist/index.d.ts +3 -469
  153. package/dist/index.js +137 -69685
  154. package/dist/layout-46N4JJYK.js +17 -0
  155. package/dist/layout-5RA6QWR6.js +26 -0
  156. package/dist/layout-5T6RH5M6.js +14 -0
  157. package/dist/layout-7QXCNOVS.js +13 -0
  158. package/dist/layout-EE6GE46Y.js +17 -0
  159. package/dist/layout-G23F7LDH.js +9 -0
  160. package/dist/layout-HRBZPFCN.js +23 -0
  161. package/dist/layout-HWDSRX5E.js +20 -0
  162. package/dist/layout-HYZPWAK5.js +18 -0
  163. package/dist/layout-N76NR4ZO.js +19 -0
  164. package/dist/layout-OB3RI63S.js +14 -0
  165. package/dist/layout-S2QBYSK2.js +15 -0
  166. package/dist/layout-UM5F4DJB.js +15 -0
  167. package/dist/layout-search-OYV77HHS.js +1655 -0
  168. package/dist/load-data-EHDDZ5ZS.js +8 -0
  169. package/dist/palettes-PRJ2RI6D.js +56 -0
  170. package/dist/parser-2MFP2H2E.js +11 -0
  171. package/dist/parser-3STE62GB.js +21 -0
  172. package/dist/parser-4FL2CMDG.js +18 -0
  173. package/dist/parser-4OGOBJJ4.js +13 -0
  174. package/dist/parser-6BTRLKRZ.js +15 -0
  175. package/dist/parser-AQETEVRW.js +21 -0
  176. package/dist/parser-CFZD5IBL.js +12 -0
  177. package/dist/parser-FUSKFGZN.js +16 -0
  178. package/dist/parser-I6KXEYFK.js +13 -0
  179. package/dist/parser-IXSZZQHY.js +18 -0
  180. package/dist/parser-JAL2HD4I.js +13 -0
  181. package/dist/parser-KNSQSKQO.js +20 -0
  182. package/dist/parser-N5O4IHUN.js +13 -0
  183. package/dist/parser-NGU3ETO6.js +16 -0
  184. package/dist/parser-NRAI6O25.js +19 -0
  185. package/dist/parser-O5J4VBII.js +11 -0
  186. package/dist/parser-OV7NOBW6.js +21 -0
  187. package/dist/parser-PQ3POX45.js +23 -0
  188. package/dist/parser-TFZFBVWN.js +11 -0
  189. package/dist/parser-TRTHDFPP.js +18 -0
  190. package/dist/parser-VQP2UJ6W.js +11 -0
  191. package/dist/parser-XDVELHRK.js +13 -0
  192. package/dist/parser-XKRP7QKP.js +11 -0
  193. package/dist/parser-Y6WTBU76.js +14 -0
  194. package/dist/parser-ZSDPZYQP.js +12 -0
  195. package/dist/renderer-2XQQQ6QR.js +28 -0
  196. package/dist/renderer-5NDVNDAZ.js +26 -0
  197. package/dist/renderer-6EEZHMH4.js +26 -0
  198. package/dist/renderer-6W5462RB.js +43 -0
  199. package/dist/renderer-7MVLWWQD.js +22 -0
  200. package/dist/renderer-AWWAF6XD.js +33 -0
  201. package/dist/renderer-BYCNMBF7.js +34 -0
  202. package/dist/renderer-CLJHQPBJ.js +29 -0
  203. package/dist/renderer-GHPZMFU2.js +15 -0
  204. package/dist/renderer-JGBKUCZD.js +16 -0
  205. package/dist/renderer-KQB7UGDS.js +19 -0
  206. package/dist/renderer-PGX2TKVS.js +24 -0
  207. package/dist/renderer-QIWVHWZO.js +15 -0
  208. package/dist/renderer-RPOA5YPZ.js +35 -0
  209. package/dist/renderer-SMIBCRVB.js +24 -0
  210. package/dist/renderer-T7YKC5VP.js +28 -0
  211. package/dist/renderer-TOCOVLHS.js +37 -0
  212. package/dist/renderer-UWA5XS74.js +39 -0
  213. package/dist/renderer-VACVQYML.js +25 -0
  214. package/dist/renderer-W4M6QLIJ.js +37 -0
  215. package/dist/renderer-WZRREQ2P.js +23 -0
  216. package/dist/renderer-YY2OSF6E.js +27 -0
  217. package/dist/renderer-Z7I3W2PP.js +24 -0
  218. package/dist/renderer-ZGNTUWC4.js +22 -0
  219. package/dist/renderer-ZTR3DER3.js +19 -0
  220. package/dist/renderer-radial-C32GPJQ7.js +21 -0
  221. package/dist/resolver-NOAA7ID7.js +13 -0
  222. package/dist/state-collapse-HCGD6IVJ.js +8 -0
  223. package/dist/state-parser-Z2V23ATR.js +18 -0
  224. package/dist/state-renderer-RFYHSP4K.js +34 -0
  225. package/dist/themes-1-CuKpeH.d.ts +908 -0
  226. package/package.json +19 -70
  227. package/dist/advanced.cjs +0 -71648
  228. package/dist/advanced.d.cts +0 -6571
  229. package/dist/auto.cjs +0 -70648
  230. package/dist/auto.d.cts +0 -54
  231. package/dist/block.cjs +0 -70208
  232. package/dist/block.d.cts +0 -115
  233. package/dist/editor.d.cts +0 -26
  234. package/dist/element.cjs +0 -70530
  235. package/dist/element.d.cts +0 -62
  236. package/dist/highlight.cjs +0 -852
  237. package/dist/highlight.d.cts +0 -49
  238. package/dist/index.cjs +0 -70491
  239. package/dist/index.d.cts +0 -626
  240. package/dist/pert.cjs +0 -325
  241. package/dist/pert.d.cts +0 -559
  242. package/src/advanced.ts +0 -873
  243. package/src/arc/parser.ts +0 -15
  244. package/src/arc/renderer.ts +0 -633
  245. package/src/auto/index.ts +0 -621
  246. package/src/auto/shared.ts +0 -370
  247. package/src/auto/styles.ts +0 -73
  248. package/src/block/layout.ts +0 -198
  249. package/src/block/renderer.ts +0 -484
  250. package/src/block/types.ts +0 -67
  251. package/src/boxes-and-lines/collapse.ts +0 -96
  252. package/src/boxes-and-lines/focus.ts +0 -257
  253. package/src/boxes-and-lines/label-placement.ts +0 -339
  254. package/src/boxes-and-lines/layout-grouped.ts +0 -912
  255. package/src/boxes-and-lines/layout-layered.ts +0 -722
  256. package/src/boxes-and-lines/layout-search.ts +0 -1678
  257. package/src/boxes-and-lines/layout.ts +0 -398
  258. package/src/boxes-and-lines/parser.ts +0 -1181
  259. package/src/boxes-and-lines/renderer.ts +0 -1465
  260. package/src/boxes-and-lines/types.ts +0 -66
  261. package/src/c4/parser.ts +0 -1158
  262. package/src/c4/renderer.ts +0 -2008
  263. package/src/c4/types.ts +0 -89
  264. package/src/chart-type-registry.ts +0 -529
  265. package/src/chart-types.ts +0 -225
  266. package/src/chart.ts +0 -685
  267. package/src/charts-d3/bar.ts +0 -338
  268. package/src/charts-d3/chord.ts +0 -140
  269. package/src/charts-d3/function.ts +0 -147
  270. package/src/charts-d3/funnel.ts +0 -104
  271. package/src/charts-d3/heatmap.ts +0 -145
  272. package/src/charts-d3/index.ts +0 -337
  273. package/src/charts-d3/interactions.ts +0 -477
  274. package/src/charts-d3/line.ts +0 -344
  275. package/src/charts-d3/mount.ts +0 -151
  276. package/src/charts-d3/pie.ts +0 -117
  277. package/src/charts-d3/polar.ts +0 -89
  278. package/src/charts-d3/radar.ts +0 -0
  279. package/src/charts-d3/sankey.ts +0 -0
  280. package/src/charts-d3/scatter.ts +0 -185
  281. package/src/charts-d3/shared.ts +0 -208
  282. package/src/class/layout.ts +0 -326
  283. package/src/class/parser.ts +0 -635
  284. package/src/class/renderer.ts +0 -793
  285. package/src/class/types.ts +0 -58
  286. package/src/cli-banner.ts +0 -106
  287. package/src/cli.ts +0 -1080
  288. package/src/colors-diagnostics.ts +0 -35
  289. package/src/colors.ts +0 -379
  290. package/src/completion-types.ts +0 -27
  291. package/src/completion.ts +0 -2668
  292. package/src/cycle/layout.ts +0 -1026
  293. package/src/cycle/parser.ts +0 -387
  294. package/src/cycle/renderer.ts +0 -653
  295. package/src/cycle/types.ts +0 -101
  296. package/src/d3.ts +0 -1434
  297. package/src/data-chart-parser.ts +0 -1007
  298. package/src/dgmo-router.ts +0 -296
  299. package/src/diagnostics-registry.ts +0 -108
  300. package/src/diagnostics.ts +0 -0
  301. package/src/dimensions.ts +0 -20
  302. package/src/directives-registry.ts +0 -396
  303. package/src/editor/dgmo.grammar +0 -76
  304. package/src/editor/dgmo.grammar.d.ts +0 -2
  305. package/src/editor/dgmo.grammar.js +0 -18
  306. package/src/editor/dgmo.grammar.terms.d.ts +0 -5
  307. package/src/editor/dgmo.grammar.terms.js +0 -35
  308. package/src/editor/highlight-api.ts +0 -596
  309. package/src/editor/highlight.ts +0 -36
  310. package/src/editor/index.ts +0 -27
  311. package/src/editor/keywords.ts +0 -128
  312. package/src/editor/tokens.ts +0 -30
  313. package/src/element/index.ts +0 -432
  314. package/src/embed/css.ts +0 -312
  315. package/src/embed/escape.ts +0 -15
  316. package/src/embed/index.ts +0 -280
  317. package/src/er/classify.ts +0 -228
  318. package/src/er/layout.ts +0 -530
  319. package/src/er/parser.ts +0 -750
  320. package/src/er/renderer.ts +0 -725
  321. package/src/er/types.ts +0 -52
  322. package/src/error-card.ts +0 -269
  323. package/src/event-line/diagnostics.ts +0 -117
  324. package/src/event-line/parser.ts +0 -643
  325. package/src/event-line/renderer.ts +0 -1742
  326. package/src/event-line/types.ts +0 -74
  327. package/src/fonts.ts +0 -3
  328. package/src/gantt/parser.ts +0 -1641
  329. package/src/gantt/renderer.ts +0 -3787
  330. package/src/gantt/resolver.ts +0 -172
  331. package/src/gantt/types.ts +0 -206
  332. package/src/graph/diagnostics.ts +0 -64
  333. package/src/graph/edge-spline.ts +0 -29
  334. package/src/graph/flowchart-parser.ts +0 -759
  335. package/src/graph/flowchart-renderer.ts +0 -817
  336. package/src/graph/layout.ts +0 -472
  337. package/src/graph/notes.ts +0 -21
  338. package/src/graph/state-collapse.ts +0 -78
  339. package/src/graph/state-parser.ts +0 -574
  340. package/src/graph/state-renderer.ts +0 -653
  341. package/src/graph/types.ts +0 -65
  342. package/src/index.ts +0 -261
  343. package/src/infra/compute.ts +0 -1385
  344. package/src/infra/layout.ts +0 -888
  345. package/src/infra/parser.ts +0 -1234
  346. package/src/infra/renderer.ts +0 -2534
  347. package/src/infra/roles.ts +0 -75
  348. package/src/infra/types.ts +0 -216
  349. package/src/infra/validation.ts +0 -201
  350. package/src/journey-map/layout.ts +0 -490
  351. package/src/journey-map/parser.ts +0 -557
  352. package/src/journey-map/renderer.ts +0 -1580
  353. package/src/journey-map/types.ts +0 -47
  354. package/src/kanban/mutations.ts +0 -175
  355. package/src/kanban/parser.ts +0 -509
  356. package/src/kanban/renderer.ts +0 -1449
  357. package/src/kanban/types.ts +0 -39
  358. package/src/label-layout.ts +0 -368
  359. package/src/map/colorize.ts +0 -54
  360. package/src/map/completion.ts +0 -316
  361. package/src/map/context-labels.ts +0 -609
  362. package/src/map/data/PROVENANCE.json +0 -1
  363. package/src/map/data/README.md +0 -40
  364. package/src/map/data/airport-collisions.json +0 -1
  365. package/src/map/data/airports.json +0 -1
  366. package/src/map/data/gazetteer.json +0 -1
  367. package/src/map/data/lakes.json +0 -1
  368. package/src/map/data/mountain-ranges.json +0 -1
  369. package/src/map/data/na-lakes.json +0 -1
  370. package/src/map/data/na-land.json +0 -1
  371. package/src/map/data/region-names.json +0 -1
  372. package/src/map/data/rivers.json +0 -1
  373. package/src/map/data/types.ts +0 -121
  374. package/src/map/data/us-states.json +0 -1
  375. package/src/map/data/water-bodies.json +0 -1
  376. package/src/map/data/world-coarse.json +0 -1
  377. package/src/map/data/world-detail.json +0 -1
  378. package/src/map/diagnostics.ts +0 -134
  379. package/src/map/dimensions.ts +0 -150
  380. package/src/map/geo-query.ts +0 -295
  381. package/src/map/geo.ts +0 -438
  382. package/src/map/invert.ts +0 -111
  383. package/src/map/layout.ts +0 -5069
  384. package/src/map/legend-band.ts +0 -127
  385. package/src/map/load-data.ts +0 -186
  386. package/src/map/parser.ts +0 -783
  387. package/src/map/renderer.ts +0 -1445
  388. package/src/map/resolved-types.ts +0 -160
  389. package/src/map/resolver.ts +0 -1146
  390. package/src/map/types.ts +0 -194
  391. package/src/mindmap/collapse.ts +0 -67
  392. package/src/mindmap/layout.ts +0 -649
  393. package/src/mindmap/parser.ts +0 -430
  394. package/src/mindmap/renderer.ts +0 -637
  395. package/src/mindmap/text-wrap.ts +0 -218
  396. package/src/mindmap/types.ts +0 -56
  397. package/src/org/collapse.ts +0 -151
  398. package/src/org/layout.ts +0 -1262
  399. package/src/org/parser.ts +0 -519
  400. package/src/org/renderer.ts +0 -866
  401. package/src/org/resolver.ts +0 -636
  402. package/src/palettes/atlas.ts +0 -77
  403. package/src/palettes/blueprint.ts +0 -73
  404. package/src/palettes/catppuccin.ts +0 -84
  405. package/src/palettes/color-utils.ts +0 -443
  406. package/src/palettes/index.ts +0 -67
  407. package/src/palettes/nord.ts +0 -67
  408. package/src/palettes/registry.ts +0 -114
  409. package/src/palettes/slate.ts +0 -73
  410. package/src/palettes/tidewater.ts +0 -73
  411. package/src/palettes/tokyo-night.ts +0 -86
  412. package/src/palettes/types.ts +0 -88
  413. package/src/pert/analyzer.ts +0 -1183
  414. package/src/pert/diagnostics.ts +0 -110
  415. package/src/pert/index.ts +0 -43
  416. package/src/pert/internal.ts +0 -252
  417. package/src/pert/layout.ts +0 -971
  418. package/src/pert/monte-carlo.ts +0 -446
  419. package/src/pert/parser.ts +0 -1606
  420. package/src/pert/renderer.ts +0 -4009
  421. package/src/pert/share-normalize.ts +0 -62
  422. package/src/pert/types.ts +0 -604
  423. package/src/pyramid/parser.ts +0 -185
  424. package/src/pyramid/renderer.ts +0 -768
  425. package/src/pyramid/types.ts +0 -28
  426. package/src/quadrant/parser.ts +0 -18
  427. package/src/quadrant/renderer.ts +0 -638
  428. package/src/raci/diagnostics.ts +0 -213
  429. package/src/raci/index.ts +0 -23
  430. package/src/raci/mutations.ts +0 -321
  431. package/src/raci/parser.ts +0 -699
  432. package/src/raci/renderer.ts +0 -1627
  433. package/src/raci/types.ts +0 -82
  434. package/src/raci/variants.ts +0 -320
  435. package/src/render.ts +0 -268
  436. package/src/ring/parser.ts +0 -212
  437. package/src/ring/renderer.ts +0 -490
  438. package/src/ring/types.ts +0 -27
  439. package/src/sequence/collapse.ts +0 -174
  440. package/src/sequence/parser.ts +0 -1596
  441. package/src/sequence/participant-inference.ts +0 -152
  442. package/src/sequence/renderer.ts +0 -3168
  443. package/src/sequence/tag-resolution.ts +0 -164
  444. package/src/sharing.ts +0 -211
  445. package/src/sitemap/collapse.ts +0 -100
  446. package/src/sitemap/layout.ts +0 -925
  447. package/src/sitemap/parser.ts +0 -755
  448. package/src/sitemap/renderer.ts +0 -828
  449. package/src/sitemap/types.ts +0 -42
  450. package/src/slope/parser.ts +0 -18
  451. package/src/slope/renderer.ts +0 -378
  452. package/src/swimlane/diagnostics.ts +0 -94
  453. package/src/swimlane/layout.ts +0 -597
  454. package/src/swimlane/parser.ts +0 -613
  455. package/src/swimlane/renderer.ts +0 -681
  456. package/src/swimlane/types.ts +0 -151
  457. package/src/tech-radar/interactive.ts +0 -1129
  458. package/src/tech-radar/layout.ts +0 -197
  459. package/src/tech-radar/parser.ts +0 -436
  460. package/src/tech-radar/renderer.ts +0 -1239
  461. package/src/tech-radar/shared.ts +0 -151
  462. package/src/tech-radar/types.ts +0 -86
  463. package/src/themes.ts +0 -22
  464. package/src/timeline/parser.ts +0 -472
  465. package/src/timeline/renderer.ts +0 -3024
  466. package/src/timeline/types.ts +0 -33
  467. package/src/timeline/viz-parser.ts +0 -21
  468. package/src/treemap/diagnostics.ts +0 -62
  469. package/src/treemap/layout-radial.ts +0 -233
  470. package/src/treemap/layout.ts +0 -189
  471. package/src/treemap/parser.ts +0 -463
  472. package/src/treemap/renderer-radial.ts +0 -438
  473. package/src/treemap/renderer.ts +0 -572
  474. package/src/treemap/treemap-shared.ts +0 -309
  475. package/src/treemap/types.ts +0 -60
  476. package/src/utils/arrow-markers.ts +0 -51
  477. package/src/utils/arrows-diagnostics.ts +0 -62
  478. package/src/utils/arrows.ts +0 -227
  479. package/src/utils/brand.ts +0 -61
  480. package/src/utils/card.ts +0 -183
  481. package/src/utils/collapse-engine/tree-edges.ts +0 -115
  482. package/src/utils/collapse-engine/tree.ts +0 -117
  483. package/src/utils/d3-helpers.ts +0 -219
  484. package/src/utils/d3-types.ts +0 -9
  485. package/src/utils/description-helpers.ts +0 -38
  486. package/src/utils/duration.ts +0 -312
  487. package/src/utils/export-container.ts +0 -41
  488. package/src/utils/expr-eval.ts +0 -27
  489. package/src/utils/fit-canvas.ts +0 -64
  490. package/src/utils/hover-styles.ts +0 -673
  491. package/src/utils/inline-markdown.ts +0 -96
  492. package/src/utils/legend-constants.ts +0 -48
  493. package/src/utils/legend-d3.ts +0 -618
  494. package/src/utils/legend-integration.ts +0 -107
  495. package/src/utils/legend-layout.ts +0 -776
  496. package/src/utils/legend-svg.ts +0 -214
  497. package/src/utils/legend-types.ts +0 -272
  498. package/src/utils/name-normalize.ts +0 -163
  499. package/src/utils/note-box/constants.ts +0 -25
  500. package/src/utils/note-box/index.ts +0 -11
  501. package/src/utils/note-box/metrics.ts +0 -90
  502. package/src/utils/note-box/svg.ts +0 -331
  503. package/src/utils/notes/bounds.ts +0 -30
  504. package/src/utils/notes/build.ts +0 -131
  505. package/src/utils/notes/index.ts +0 -18
  506. package/src/utils/notes/model.ts +0 -19
  507. package/src/utils/notes/parse.ts +0 -131
  508. package/src/utils/notes/place.ts +0 -177
  509. package/src/utils/notes/resolve.ts +0 -88
  510. package/src/utils/number-format.ts +0 -36
  511. package/src/utils/parsing.ts +0 -953
  512. package/src/utils/reserved-key-registry.ts +0 -239
  513. package/src/utils/safe-href.ts +0 -38
  514. package/src/utils/scaling.ts +0 -97
  515. package/src/utils/svg-embed.ts +0 -251
  516. package/src/utils/tag-groups.ts +0 -607
  517. package/src/utils/text-measure.ts +0 -122
  518. package/src/utils/title-constants.ts +0 -24
  519. package/src/utils/visual-conventions.ts +0 -61
  520. package/src/utils/wrapped-desc.ts +0 -87
  521. package/src/venn/parser.ts +0 -18
  522. package/src/venn/renderer.ts +0 -1047
  523. package/src/version-control/diagnostics.ts +0 -67
  524. package/src/version-control/parser.ts +0 -544
  525. package/src/version-control/renderer.ts +0 -724
  526. package/src/version-control/types.ts +0 -100
  527. package/src/visualizations/parse.ts +0 -1560
  528. package/src/visualizations/types.ts +0 -262
  529. package/src/wireframe/layout.ts +0 -479
  530. package/src/wireframe/parser.ts +0 -1114
  531. package/src/wireframe/renderer.ts +0 -1318
  532. package/src/wireframe/types.ts +0 -110
  533. package/src/wordcloud/parser.ts +0 -18
  534. package/src/wordcloud/renderer.ts +0 -357
package/src/completion.ts DELETED
@@ -1,2668 +0,0 @@
1
- /**
2
- * Diagram symbol extraction API + completion registry.
3
- *
4
- * Provides:
5
- * - DiagramSymbols interface + extractDiagramSymbols() dispatch
6
- * - COMPLETION_REGISTRY: chart-type → directives map (for editor autocomplete)
7
- * - CHART_TYPES: array of { name, description } for chart type completion
8
- * - METADATA_KEY_SET: derived set of all known directive keys
9
- *
10
- * Each diagram type registers its own extractor via registerExtractor().
11
- * All built-in extractors are registered at module init below.
12
- */
13
-
14
- import { extractSymbols as extractErSymbols } from './er/parser';
15
- import { extractSymbols as extractFlowchartSymbols } from './graph/flowchart-parser';
16
- import { extractSymbols as extractInfraSymbols } from './infra/parser';
17
- import { extractSymbols as extractClassSymbols } from './class/parser';
18
- import { extractPertSymbols } from './pert/parser';
19
- import {
20
- parseFirstLine,
21
- ALL_CHART_TYPES,
22
- measureIndent,
23
- } from './utils/parsing';
24
- import { RECOGNIZED_COLOR_NAMES } from './colors';
25
- // Closed enum sets owned by their respective parsers — imported (never
26
- // hand-copied) so completion can't drift from the grammar (one-oracle rule).
27
- import { ALL_MARKERS } from './raci/variants';
28
- import { STATE_KEYWORDS as WIREFRAME_STATE_KEYWORDS } from './wireframe/parser';
29
-
30
- const RECOGNIZED_COLOR_SET: ReadonlySet<string> = new Set(
31
- RECOGNIZED_COLOR_NAMES
32
- );
33
- // Read chart-type descriptions directly from the source-of-truth data
34
- // module instead of via dgmo-router.ts. dgmo-router imports every
35
- // parser, and the parsers (Class/ER/Infra/Pert/Flowchart) type-only
36
- // import DiagramSymbols back from this file — creating a hub of cycles
37
- // through completion ↔ dgmo-router. Going through chart-types.ts (a
38
- // leaf module with zero imports) breaks 7 of the 10 known cycles.
39
- import { chartTypes } from './chart-types';
40
-
41
- // ============================================================
42
- // Symbol extraction
43
- // ============================================================
44
-
45
- // Types live in ./completion-types so the chart-type parsers can
46
- // import them without taking a cycle through this file.
47
- import type { ChartType, DiagramSymbols, ExtractFn } from './completion-types';
48
- export type { ChartType, DiagramSymbols, ExtractFn };
49
-
50
- const extractorRegistry = new Map<ChartType, ExtractFn>();
51
-
52
- export function registerExtractor(kind: ChartType, fn: ExtractFn): void {
53
- extractorRegistry.set(kind, fn);
54
- }
55
-
56
- /**
57
- * Extract diagram symbols from document text.
58
- * Returns null if the chart type is unknown or has no registered extractor.
59
- */
60
- export function extractDiagramSymbols(docText: string): DiagramSymbols | null {
61
- // Parse chartType from first line — bare type name.
62
- let chartType: string | null = null;
63
- for (const line of docText.split('\n')) {
64
- const trimmed = line.trim();
65
- if (!trimmed || trimmed.startsWith('//')) continue;
66
- const result = parseFirstLine(trimmed);
67
- if (result) {
68
- chartType = result.chartType;
69
- }
70
- break; // only check the first non-empty, non-comment line
71
- }
72
- if (!chartType) return null;
73
- const fn = extractorRegistry.get(chartType);
74
- if (!fn) return null;
75
- const result = fn(docText);
76
- // Populate `aliases` uniformly for every chart type so downstream
77
- // editor surfaces don't need per-extractor branches.
78
- const aliases = extractAliasDeclarations(docText);
79
- return Object.keys(aliases).length > 0 ? { ...result, aliases } : result;
80
- }
81
-
82
- // ============================================================
83
- // Completion registry
84
- // ============================================================
85
-
86
- /** Specification for a single directive: description + optional enumerated values. */
87
- export interface DirectiveValueSpec {
88
- description: string;
89
- values?: string[];
90
- }
91
-
92
- /** Specification for a chart type's directives. */
93
- export interface DirectiveSpec {
94
- directives: Record<string, DirectiveValueSpec>;
95
- }
96
-
97
- // Global directives applied to every chart type
98
- const GLOBAL_DIRECTIVES: Record<string, DirectiveValueSpec> = {
99
- palette: {
100
- description: 'Color palette name',
101
- values: [
102
- 'nord',
103
- 'catppuccin',
104
- 'tokyo-night',
105
- 'atlas',
106
- 'blueprint',
107
- 'slate',
108
- 'tidewater',
109
- ],
110
- },
111
- theme: {
112
- description: 'Color theme',
113
- values: ['light', 'dark', 'transparent'],
114
- },
115
- 'no-title': {
116
- description: 'Hide the diagram title',
117
- },
118
- };
119
-
120
- function withGlobals(
121
- directives: Record<string, DirectiveValueSpec> = {}
122
- ): DirectiveSpec {
123
- return { directives: { ...GLOBAL_DIRECTIVES, ...directives } };
124
- }
125
-
126
- /** Chart-type → directive specifications. Every chart type has at least palette + theme. */
127
- export const COMPLETION_REGISTRY = new Map<string, DirectiveSpec>([
128
- // ── Data charts ──────────────────────────────────────────
129
- [
130
- 'bar',
131
- withGlobals({
132
- stack: {
133
- description:
134
- 'Multi-series block header → stacked bars (one bar/category)',
135
- },
136
- group: {
137
- description:
138
- 'Multi-series block header → clustered (side-by-side) bars',
139
- },
140
- 'x-label': { description: 'X-axis label' },
141
- 'y-label': { description: 'Y-axis label' },
142
- 'orientation-horizontal': { description: 'Switch to horizontal bars' },
143
- 'no-value': { description: 'Hide value labels atop each bar' },
144
- color: { description: 'Bar color override' },
145
- }),
146
- ],
147
- [
148
- 'line',
149
- withGlobals({
150
- series: { description: 'Series name(s)' },
151
- fill: { description: 'Fill under the line (area chart)' },
152
- 'x-label': { description: 'X-axis label' },
153
- 'y-label': { description: 'Y-axis label (left axis)' },
154
- 'y-right-label': {
155
- description: 'Right y-axis label (dual-axis; group series beneath it)',
156
- },
157
- 'no-value': { description: 'Hide value labels at each point' },
158
- }),
159
- ],
160
- [
161
- 'pie',
162
- withGlobals({
163
- hole: {
164
- description:
165
- 'Doughnut ring (optional ratio 0–0.9); shows the total in center',
166
- },
167
- 'no-center-total': {
168
- description: 'Hide the center total on a doughnut/hole pie',
169
- },
170
- 'no-name': { description: 'Hide name from segment labels' },
171
- 'no-value': { description: 'Hide value from segment labels' },
172
- 'no-percent': { description: 'Hide percent from segment labels' },
173
- }),
174
- ],
175
- [
176
- 'polar-area',
177
- withGlobals({
178
- 'no-name': { description: 'Hide name from segment labels' },
179
- 'no-value': { description: 'Hide value from segment labels' },
180
- 'no-percent': { description: 'Hide percent from segment labels' },
181
- }),
182
- ],
183
- [
184
- 'radar',
185
- withGlobals({
186
- 'no-value': { description: 'Hide value labels at each vertex' },
187
- }),
188
- ],
189
-
190
- // ── Extended charts ──────────────────────────────────────
191
- [
192
- 'scatter',
193
- withGlobals({
194
- 'no-name': { description: 'Hide point labels' },
195
- 'x-label': { description: 'X-axis label' },
196
- 'y-label': { description: 'Y-axis label' },
197
- 'size-label': { description: 'Size axis label' },
198
- }),
199
- ],
200
- [
201
- 'heatmap',
202
- withGlobals({
203
- columns: { description: 'Column labels (required)' },
204
- 'no-value': { description: 'Hide cell value text' },
205
- }),
206
- ],
207
- ['sankey', withGlobals()],
208
- [
209
- 'funnel',
210
- withGlobals({
211
- 'no-name': { description: 'Hide left-side name labels' },
212
- 'no-value': { description: 'Hide right-side value labels' },
213
- }),
214
- ],
215
- [
216
- 'function',
217
- withGlobals({
218
- x: { description: 'X-axis range (start to end)' },
219
- 'x-label': { description: 'X-axis label' },
220
- 'y-label': { description: 'Y-axis label' },
221
- shade: { description: 'Fill area below curves with translucent color' },
222
- }),
223
- ],
224
-
225
- // ── Visualizations ───────────────────────────────────────
226
- ['slope', withGlobals()],
227
- [
228
- 'wordcloud',
229
- withGlobals({
230
- rotate: {
231
- description: 'Word rotation',
232
- values: ['none', 'mixed', 'angled'],
233
- },
234
- max: { description: 'Maximum word count' },
235
- size: { description: 'Font size range (min, max)' },
236
- }),
237
- ],
238
- [
239
- 'arc',
240
- withGlobals({
241
- order: {
242
- description: 'Node ordering',
243
- values: ['appearance', 'name', 'group', 'degree'],
244
- },
245
- }),
246
- ],
247
- [
248
- 'timeline',
249
- withGlobals({
250
- 'active-tag': { description: 'Active tag group name' },
251
- }),
252
- ],
253
- [
254
- 'event-line',
255
- withGlobals({
256
- 'no-scale': { description: 'Space events evenly instead of by date' },
257
- side: {
258
- description: 'Card placement: side above | below (default alternate)',
259
- },
260
- 'no-box': {
261
- description: 'Card-less label/rule/description style (slides)',
262
- },
263
- 'no-legend': { description: 'Hide the tag legend' },
264
- TBD: {
265
- description:
266
- 'Date prefix for a not-yet-scheduled future event (e.g. "TBD Console Port")',
267
- },
268
- 'active-tag': { description: 'Active tag group name' },
269
- }),
270
- ],
271
- ['venn', withGlobals()],
272
- [
273
- 'quadrant',
274
- withGlobals({
275
- 'x-label': { description: 'X-axis labels (low, high)' },
276
- 'y-label': { description: 'Y-axis labels (low, high)' },
277
- }),
278
- ],
279
-
280
- // ── Diagrams ─────────────────────────────────────────────
281
- [
282
- 'sequence',
283
- withGlobals({
284
- activations: {
285
- description: 'Show activation bars',
286
- values: ['on', 'off'],
287
- },
288
- 'active-tag': { description: 'Active tag group name' },
289
- }),
290
- ],
291
- [
292
- 'flowchart',
293
- // Spec §5 §4.6: direction-lr, orientation-vertical, solid-fill, no-notes
294
- withGlobals({
295
- 'direction-lr': { description: 'Switch to left-to-right layout' },
296
- 'orientation-vertical': {
297
- description: 'Use vertical orientation for ranks',
298
- },
299
- 'no-notes': { description: 'Suppress all node note boxes' },
300
- }),
301
- ],
302
- ['class', withGlobals({})],
303
- [
304
- 'er',
305
- // Spec §9 §8.5: notation (chen/crow), active-tag.
306
- withGlobals({
307
- notation: {
308
- description: 'ER notation style',
309
- values: ['chen', 'crow'],
310
- },
311
- 'active-tag': { description: 'Active tag group name' },
312
- }),
313
- ],
314
- [
315
- 'org',
316
- // Spec §7 §6.5: direction-tb, sub-node-label, show-sub-node-count,
317
- // hide, active-tag. solid-fill via SOLID_FILL_CAPABLE.
318
- withGlobals({
319
- 'direction-tb': { description: 'Switch to top-to-bottom layout' },
320
- 'sub-node-label': { description: 'Label for sub-nodes' },
321
- 'show-sub-node-count': { description: 'Show sub-node counts' },
322
- hide: { description: 'Hide tag:value pairs' },
323
- 'active-tag': { description: 'Active tag group name' },
324
- }),
325
- ],
326
- [
327
- 'kanban',
328
- // Spec §11 §10.4: hide, active-tag.
329
- withGlobals({
330
- hide: { description: 'Hide tag:value pairs' },
331
- 'active-tag': { description: 'Active tag group name' },
332
- }),
333
- ],
334
- // RACI / RASCI / DACI — one chart type (`raci`); the variant (RACI, RASCI,
335
- // or DACI) is inferred from the markers used (D → DACI, S → RASCI).
336
- [
337
- 'raci',
338
- withGlobals({
339
- roles: {
340
- description:
341
- 'Declare role column order (inline `roles A, B, C` or indented block with per-role pipe metadata)',
342
- },
343
- 'active-tag': { description: 'Active tag group name' },
344
- }),
345
- ],
346
- [
347
- 'c4',
348
- // Spec §8 §7.7: direction-tb, active-tag.
349
- withGlobals({
350
- 'direction-tb': { description: 'Switch to top-to-bottom layout' },
351
- 'active-tag': { description: 'Active tag group name' },
352
- }),
353
- ],
354
- [
355
- 'state',
356
- // Spec §6 §5.6: direction-tb, solid-fill, no-notes.
357
- withGlobals({
358
- 'direction-tb': { description: 'Switch to top-to-bottom layout' },
359
- 'no-notes': { description: 'Suppress all state note boxes' },
360
- }),
361
- ],
362
- [
363
- 'sitemap',
364
- withGlobals({
365
- 'direction-tb': { description: 'Switch to top-to-bottom layout' },
366
- 'active-tag': { description: 'Active tag group name' },
367
- }),
368
- ],
369
- [
370
- 'infra',
371
- withGlobals({
372
- 'direction-tb': { description: 'Switch to top-to-bottom layout' },
373
- animate: { description: 'Enable traffic animation' },
374
- 'default-latency-ms': { description: 'Default latency for all nodes' },
375
- 'default-uptime': { description: 'Default uptime for all nodes' },
376
- 'default-rps': { description: 'Default RPS capacity for all nodes' },
377
- 'slo-availability': { description: 'SLO availability target (0-1)' },
378
- 'slo-p90-latency-ms': { description: 'SLO p90 latency target in ms' },
379
- 'slo-warning-margin': { description: 'SLO warning margin percentage' },
380
- 'active-tag': { description: 'Active tag group name' },
381
- }),
382
- ],
383
- [
384
- 'pert',
385
- withGlobals({
386
- 'time-unit': {
387
- description: 'Time unit for activity durations',
388
- values: ['min', 'h', 'd', 'bd', 'w', 'm', 'q', 'y'],
389
- },
390
- confidence: {
391
- description: 'Confidence factor for M-only durations',
392
- values: ['high', 'medium', 'low'],
393
- },
394
- direction: { description: 'Layout direction', values: ['LR', 'TB'] },
395
- 'node-detail': {
396
- description: 'Node visual density',
397
- values: ['compact', 'full'],
398
- },
399
- trials: {
400
- description:
401
- 'Monte Carlo trial count (auto-derived from activity count)',
402
- },
403
- seed: { description: 'Monte Carlo PRNG seed (auto-derived from title)' },
404
- 'scrubber-trials': {
405
- description: 'Fast-MC trials for the duration scrubber (default 300)',
406
- },
407
- 'start-date': { description: 'Project start date (YYYY-MM-DD or now)' },
408
- 'end-date': { description: 'Project end date (YYYY-MM-DD)' },
409
- 'sprint-length': { description: 'Sprint duration (e.g. 2w)' },
410
- 'sprint-number': { description: 'Starting sprint number' },
411
- 'sprint-start': { description: 'Sprint start date (YYYY-MM-DD)' },
412
- 'active-tag': { description: 'Active tag group name' },
413
- }),
414
- ],
415
- [
416
- 'gantt',
417
- // Spec §13 §12.2 Options.
418
- withGlobals({
419
- start: { description: 'Project start date (YYYY-MM-DD)' },
420
- 'today-marker': {
421
- description: 'Today marker (bare = on, or YYYY-MM-DD date)',
422
- },
423
- sort: { description: 'Sort order', values: ['time', 'group', 'tag'] },
424
- 'critical-path': { description: 'Show critical path' },
425
- 'no-dependencies': { description: 'Hide dependency arrows' },
426
- 'sprint-length': { description: 'Sprint duration (e.g. 2w)' },
427
- 'sprint-number': { description: 'Starting sprint number' },
428
- 'sprint-start': { description: 'Sprint start date (YYYY-MM-DD)' },
429
- 'active-tag': { description: 'Active tag group name' },
430
- // Legacy positive form `dependencies` — kept for back-compat. Use
431
- // `no-dependencies` to suppress dependency arrows in new code.
432
- dependencies: { description: 'Show dependencies (legacy form)' },
433
- }),
434
- ],
435
- [
436
- 'boxes-and-lines',
437
- withGlobals({
438
- direction: { description: 'Layout direction', values: ['LR', 'TB'] },
439
- 'active-tag': { description: 'Active tag group name' },
440
- hide: { description: 'Hide tag:value pairs' },
441
- heat: {
442
- description:
443
- 'Label for the value→colour ramp, with an optional trailing [low] [high] color pair (pairs with the `heat:` key)',
444
- },
445
- 'show-values': { description: 'Print box values as text' },
446
- }),
447
- ],
448
- [
449
- 'swimlane',
450
- withGlobals({
451
- direction: { description: 'Layout direction', values: ['LR', 'TB'] },
452
- lane: { description: 'Declare a lane (row) with an optional color' },
453
- 'active-tag': { description: 'Active tag group name' },
454
- }),
455
- ],
456
- [
457
- 'version-control',
458
- withGlobals({
459
- direction: {
460
- description: 'Layout direction',
461
- values: ['LR', 'TB', 'BT'],
462
- },
463
- merge: { description: 'Merge a branch into the active branch' },
464
- 'cherry-pick': { description: 'Copy a commit onto the active branch' },
465
- rebase: { description: 'Replay a branch onto another (rebase X onto Y)' },
466
- reset: { description: 'Move a branch pointer back (reset X to commit)' },
467
- revert: { description: 'Add an inverse commit undoing a commit' },
468
- ref: {
469
- description:
470
- 'Pointer at a commit (ref origin/main at commit) — remotes / HEAD',
471
- },
472
- note: { description: 'Numbered step annotation on the current commit' },
473
- 'no-labels': { description: 'Hide commit messages' },
474
- 'no-lanes': { description: 'Hide branch lanes' },
475
- 'no-head': { description: 'Hide the HEAD marker' },
476
- }),
477
- ],
478
- [
479
- 'mindmap',
480
- withGlobals({
481
- 'active-tag': { description: 'Active tag group name' },
482
- }),
483
- ],
484
- [
485
- 'wireframe',
486
- withGlobals({
487
- mobile: { description: 'Use mobile (narrow vertical) layout' },
488
- 'active-tag': { description: 'Active tag group name' },
489
- }),
490
- ],
491
- [
492
- 'tech-radar',
493
- // Spec §20 documents one directive: `show-blip-legend`. `rings` is a
494
- // structural block keyword; quadrant/ring/trend/color are pipe metadata
495
- // that live in PIPE_METADATA.
496
- withGlobals({
497
- 'show-blip-legend': {
498
- description: 'Render the four-column blip listing alongside the radar',
499
- },
500
- }),
501
- ],
502
- [
503
- 'cycle',
504
- withGlobals({
505
- 'direction-counterclockwise': {
506
- description: 'Reverse cycle direction to counterclockwise',
507
- },
508
- 'circle-nodes': {
509
- description: 'Render nodes as circles instead of rectangles',
510
- },
511
- }),
512
- ],
513
- [
514
- 'journey-map',
515
- // Spec §22 directives: `active-tag`. `persona` is a
516
- // structural keyword (like `tag` / `roles`), not a directive.
517
- // `solid-fill` is added via SOLID_FILL_CAPABLE below.
518
- withGlobals({
519
- 'active-tag': { description: 'Active tag group name' },
520
- }),
521
- ],
522
- [
523
- 'pyramid',
524
- // Spec §23.5 documents `inverted`; `solid-fill` is added via
525
- // SOLID_FILL_CAPABLE below (working but not yet in spec §23.5).
526
- // `color`/`description` are layer pipe-metadata, not directives.
527
- withGlobals({
528
- inverted: { description: 'Flip apex to the bottom (funnel orientation)' },
529
- }),
530
- ],
531
- [
532
- 'ring',
533
- // Per spec §24.5 the only chart-specific directive is `solid-fill`,
534
- // applied via SOLID_FILL_CAPABLE below. `color`/`description` are
535
- // layer pipe-metadata, not directives — they live in PIPE_METADATA.
536
- withGlobals({}),
537
- ],
538
- [
539
- 'treemap',
540
- // Hierarchy sized by a bare trailing number; color modes + opt-outs.
541
- withGlobals({
542
- heat: {
543
- description:
544
- 'Name the color-by-value ramp (`heat <Label> [low] [high]`); pairs with the per-node `heat:` key',
545
- },
546
- depth: {
547
- description:
548
- 'Render N levels; deeper subtrees collapse to a drillable block',
549
- },
550
- 'no-values': { description: 'Hide value labels' },
551
- 'no-percent': { description: 'Hide percentage labels' },
552
- 'no-headers': { description: 'Hide parent header bars' },
553
- 'no-legend': { description: 'Hide the legend' },
554
- radial: {
555
- description:
556
- 'Render as a sunburst / hierarchical pie (concentric rings)',
557
- },
558
- }),
559
- ],
560
- [
561
- 'block',
562
- // Author-controlled grid; columns are inferred from placement, so the only
563
- // directives are an explicit `columns` override and `no-legend`.
564
- withGlobals({
565
- columns: {
566
- description:
567
- 'Grid width (columns); inferred from the widest row if omitted',
568
- },
569
- 'no-legend': { description: 'Hide the tag legend' },
570
- }),
571
- ],
572
- [
573
- 'map',
574
- // Geographic map directives (§24B.2/.7). Cosmetics are ON by default — the
575
- // only switches are bare `no-*` opt-outs, surfaced proactively so a
576
- // zero-config map still hints at what can be turned off. `poi`/`route` are
577
- // content keywords, not directives; metadata keys (heat/size/width/label/style)
578
- // live in the reserved-key registry.
579
- withGlobals({
580
- 'region-heat': {
581
- description:
582
- 'Label for the region value→colour ramp, with an optional trailing [low] [high] color pair (pairs with the `heat:` key)',
583
- },
584
- 'poi-size': {
585
- description:
586
- 'Label for the POI value→marker-size channel (pairs with the `size:` key)',
587
- },
588
- 'flow-width': {
589
- description:
590
- 'Label for the edge/leg value→line-thickness channel (pairs with the `width:` key)',
591
- },
592
- locale: {
593
- description:
594
- 'Default country/state for bare place names, e.g. locale US-GA',
595
- },
596
- 'active-tag': {
597
- description: 'Which tag group leads when several are present',
598
- },
599
- caption: { description: 'Caption line (data-source attribution)' },
600
- 'no-title': { description: 'Suppress the title banner' },
601
- 'no-legend': { description: 'Suppress the legend' },
602
- 'no-coastline': {
603
- description: 'Turn off coastal water-lines (on by default)',
604
- },
605
- 'no-relief': {
606
- description: 'Turn off mountain-range relief shading (on by default)',
607
- },
608
- 'no-context-labels': {
609
- description: 'Turn off orientation labels for water + nearby countries',
610
- },
611
- 'no-region-labels': {
612
- description: 'Turn off subdivision name labels (on by default)',
613
- },
614
- 'no-region-heat-value': {
615
- description:
616
- 'Turn off the heat value shown under each region (on by default)',
617
- },
618
- 'no-poi-labels': { description: 'Turn off POI labels (on by default)' },
619
- 'no-colorize': {
620
- description:
621
- 'Force plain green-land reference dress (regions are auto-coloured by default)',
622
- },
623
- 'no-cities': {
624
- description:
625
- 'Turn off the subtle city dots scattered across the basemap (on by default)',
626
- },
627
- 'no-cluster-pois': {
628
- description:
629
- 'Always fan out coincident POI markers instead of collapsing them into a count badge',
630
- },
631
- }),
632
- ],
633
- ]);
634
-
635
- // ── Cross-chart-type bare-keyword option: `solid-fill` ──────────
636
- // Adds the directive to every chart type whose renderer actually responds to
637
- // it (renders shapes at full intent saturation when set). Chart types where
638
- // the keyword is a no-op (gantt/infra/tech-radar/event-line deliberately opt
639
- // out; quadrant/scatter-dot/wordcloud/line/arc/slope have no tinted shape
640
- // fill) intentionally don't list it — keeps the completion popup honest.
641
- const SOLID_FILL_CAPABLE = new Set([
642
- 'flowchart',
643
- 'state',
644
- 'sequence',
645
- 'c4',
646
- 'org',
647
- 'kanban',
648
- 'journey-map',
649
- 'mindmap',
650
- 'cycle',
651
- 'pyramid',
652
- 'ring',
653
- 'funnel',
654
- 'class',
655
- 'er',
656
- 'sitemap',
657
- 'boxes-and-lines',
658
- 'wireframe',
659
- 'bar',
660
- 'pie',
661
- 'polar-area',
662
- 'radar',
663
- 'scatter',
664
- 'pert',
665
- 'block',
666
- 'swimlane',
667
- 'treemap',
668
- 'heatmap',
669
- 'venn',
670
- 'timeline',
671
- ]);
672
- for (const [type, spec] of COMPLETION_REGISTRY) {
673
- if (SOLID_FILL_CAPABLE.has(type)) {
674
- spec.directives['solid-fill'] = {
675
- description:
676
- 'Render shapes with full intent color instead of the default 25% tint',
677
- };
678
- }
679
- }
680
-
681
- // ============================================================
682
- // Chart types array (for chart type completion popup)
683
- // ============================================================
684
-
685
- /** All chart types with descriptions, for chart type autocomplete. */
686
- const CHART_TYPE_DESCRIPTIONS: Record<string, string> = Object.fromEntries(
687
- chartTypes.map((c) => [c.id, c.description])
688
- );
689
-
690
- export const CHART_TYPES: ReadonlyArray<{ name: string; description: string }> =
691
- [...ALL_CHART_TYPES].map((name) => ({
692
- name,
693
- description: CHART_TYPE_DESCRIPTIONS[name] ?? name,
694
- }));
695
-
696
- // ============================================================
697
- // Entity types for `is a` declarations
698
- // ============================================================
699
-
700
- /**
701
- * Entity types for `Name is a <type>` declarations, keyed by chart type.
702
- * Values are sourced from parser constants (VALID_PARTICIPANT_TYPES,
703
- * C4_IS_A_RE).
704
- */
705
- export const ENTITY_TYPES = new Map<string, string[]>([
706
- ['sequence', ['actor', 'database', 'queue', 'cache']],
707
- [
708
- 'c4',
709
- ['person', 'system', 'container', 'component', 'external', 'database'],
710
- ],
711
- ]);
712
-
713
- // ============================================================
714
- // Structural keywords for line-leading completion
715
- // ============================================================
716
-
717
- /**
718
- * Chart-type-specific structural keywords offered on an empty/start-of-line in
719
- * the data zone (block openers like `loop`, section headers like `containers`,
720
- * the `tag` block declaration, etc.). This is the single source of truth for
721
- * the editor's structural-keyword popup — every entry MUST be a token the
722
- * corresponding parser actually recognizes (validated by the
723
- * completion-conformance suite). Do NOT add removed/diagnostic-only tokens
724
- * (e.g. cycle's `no-descriptions`) or tokens the parser ignores.
725
- *
726
- * Chart types not listed here have no structural keywords (most data charts).
727
- */
728
- export const STRUCTURAL_KEYWORDS = new Map<string, string[]>([
729
- ['sequence', ['if', 'else', 'loop', 'parallel', 'note', 'tag']],
730
- ['gantt', ['era', 'marker', 'holiday', 'workweek', 'parallel', 'tag']],
731
- ['c4', ['containers', 'components', 'deployment', 'tag']],
732
- ['timeline', ['era', 'marker', 'tag']],
733
- ['org', ['tag']],
734
- ['kanban', ['tag']],
735
- ['sitemap', ['tag']],
736
- ['infra', ['tag']],
737
- ['pert', ['tag']],
738
- ['mindmap', ['tag']],
739
- ['treemap', ['tag']],
740
- ['block', ['tag']],
741
- ['boxes-and-lines', ['tag']],
742
- ['swimlane', ['lane', 'tag']],
743
- [
744
- 'version-control',
745
- ['merge', 'cherry-pick', 'rebase', 'reset', 'revert', 'ref', 'note'],
746
- ],
747
- ['er', ['tag']],
748
- ['cycle', ['direction-counterclockwise', 'circle-nodes']],
749
- ['journey-map', ['persona', 'tag']],
750
- ['raci', ['roles']],
751
- ['tech-radar', ['rings']],
752
- [
753
- 'wireframe',
754
- [
755
- 'nav',
756
- 'tabs',
757
- 'table',
758
- 'image',
759
- 'modal',
760
- 'skeleton',
761
- 'alert',
762
- 'progress',
763
- 'chart',
764
- 'mobile',
765
- 'tag',
766
- ],
767
- ],
768
- ['class', ['abstract', 'interface', 'enum', 'extends', 'implements']],
769
- ]);
770
-
771
- /**
772
- * Chart types that support `tag` block declarations (and thus the
773
- * `alias`/`default` sub-keywords inside a tag block). Derived from
774
- * STRUCTURAL_KEYWORDS so the two can never drift — a chart supports tag blocks
775
- * iff it offers the `tag` keyword.
776
- */
777
- export const TAG_SUPPORTING_TYPES: ReadonlySet<string> = new Set(
778
- [...STRUCTURAL_KEYWORDS]
779
- .filter(([, kws]) => kws.includes('tag'))
780
- .map(([type]) => type)
781
- );
782
-
783
- // ============================================================
784
- // Reference grammar descriptor (slot 5 — entity reference)
785
- // ============================================================
786
-
787
- /** Whether a chart type has a reference position (arrow/operator → a prior
788
- * declaration) and which operators open it. The single oracle that drives
789
- * the library extractor audit, the app's entity-trigger map, and the
790
- * conformance drift-guard — so the three can never disagree. v1 models
791
- * reference-after-operator only (NOT metadata-target references). */
792
- export interface ReferenceGrammar {
793
- hasReferenceGrammar: boolean;
794
- /** Literal operator tokens that introduce a reference (e.g. `->`, `<->`,
795
- * `~>`, `+`). The app derives its trigger regex from these (allowing the
796
- * labeled `-label->` / `~label~>` variants where applicable). */
797
- referenceOperators: string[];
798
- }
799
-
800
- /**
801
- * Per-type reference grammar. Grounded in each parser's arrow/operator
802
- * grammar — NOT hand-typed blind. Types with no reference position (org,
803
- * kanban, mindmap, all data charts, the radial/visualization types) declare
804
- * `hasReferenceGrammar: false`. `map` references (`route A ~> B`) are owned by
805
- * the app's bespoke geo-completion path, so it stays `false` here to avoid
806
- * double-handling.
807
- */
808
- export const REFERENCE_GRAMMAR = new Map<string, ReferenceGrammar>([
809
- // Diagrams with a genuine reference position
810
- ['sequence', { hasReferenceGrammar: true, referenceOperators: ['->', '~>'] }],
811
- ['flowchart', { hasReferenceGrammar: true, referenceOperators: ['->'] }],
812
- ['state', { hasReferenceGrammar: true, referenceOperators: ['->'] }],
813
- ['sitemap', { hasReferenceGrammar: true, referenceOperators: ['->'] }],
814
- [
815
- 'c4',
816
- {
817
- hasReferenceGrammar: true,
818
- referenceOperators: ['->', '<->', '~>', '<~>'],
819
- },
820
- ],
821
- // ER/class/infra reference grammars are irregular (cardinality / typed
822
- // relationship operators / indented arrows); the app keeps a special-cased
823
- // trigger for those, but they're still reference-grammar types here so the
824
- // drift-guard covers their extractors.
825
- ['er', { hasReferenceGrammar: true, referenceOperators: ['--'] }],
826
- [
827
- 'class',
828
- {
829
- hasReferenceGrammar: true,
830
- referenceOperators: ['-->', '--|>', '..|>', '*--', 'o--', '..>'],
831
- },
832
- ],
833
- ['infra', { hasReferenceGrammar: true, referenceOperators: ['->'] }],
834
- ['gantt', { hasReferenceGrammar: true, referenceOperators: ['->'] }],
835
- ['pert', { hasReferenceGrammar: true, referenceOperators: ['->'] }],
836
- ['arc', { hasReferenceGrammar: true, referenceOperators: ['->'] }],
837
- ['sankey', { hasReferenceGrammar: true, referenceOperators: ['->', '--'] }],
838
- [
839
- 'boxes-and-lines',
840
- { hasReferenceGrammar: true, referenceOperators: ['->', '<->'] },
841
- ],
842
- // Venn references prior sets via the `+` intersection operator (not an arrow).
843
- ['venn', { hasReferenceGrammar: true, referenceOperators: ['+'] }],
844
- ]);
845
-
846
- // Every other registered chart type has no reference position.
847
- for (const type of ALL_CHART_TYPES) {
848
- if (!REFERENCE_GRAMMAR.has(type)) {
849
- REFERENCE_GRAMMAR.set(type, {
850
- hasReferenceGrammar: false,
851
- referenceOperators: [],
852
- });
853
- }
854
- }
855
-
856
- // ============================================================
857
- // Closed value enums (slot 7) sourced from parser constants
858
- // ============================================================
859
-
860
- /**
861
- * RACI marker alphabet (slot-7 enum in the role-assignment value position).
862
- * Consumed by editor completion. The chart type is always `raci`; the variant
863
- * (RACI / RASCI / DACI) is inferred from the markers used, so completion offers
864
- * the full union (R / A / S / C / I / D) sourced from `raci/variants.ts`'s
865
- * `ALL_MARKERS` — the marker set can never drift from the parser.
866
- */
867
- export const RACI_MARKER_ALPHABETS: ReadonlyMap<string, readonly string[]> =
868
- new Map([['raci', [...ALL_MARKERS]]]);
869
-
870
- /**
871
- * Closed set of wireframe element state flags (slot-7 trailing enum, e.g.
872
- * `(Submit) primary destructive`). Sourced from the wireframe parser's
873
- * `STATE_KEYWORDS` — exported there and consumed here, never re-typed.
874
- */
875
- export const WIREFRAME_FLAGS: readonly string[] = [...WIREFRAME_STATE_KEYWORDS];
876
-
877
- // ============================================================
878
- // Pipe metadata for inline `| key value` on data lines
879
- // ============================================================
880
-
881
- /** Specification for a single pipe metadata key. */
882
- export interface PipeKeySpec {
883
- description: string;
884
- values?: string[];
885
- }
886
-
887
- /**
888
- * Pipe metadata keys for inline `| key value` on data lines.
889
- * Keyed by chart type → { context-name: keys }.
890
- *
891
- * Contexts are open-ended. The two universal ones are:
892
- * - `node` — the default for any non-arrow line
893
- * - `edge` — lines containing an arrow (`->`, `--`)
894
- *
895
- * Charts with richer line types declare additional contexts:
896
- * - raci: `role`, `phase`, `assignment`
897
- * - ring / pyramid: `layer`
898
- * - tech-radar: `quadrant`, `blip`
899
- * - journey-map: `step`
900
- *
901
- * IMPORTANT: NEVER add 'sequence' here. The `|` character in sequence
902
- * diagrams separates display names from identifiers and tag metadata.
903
- * Adding sequence would trigger false pipe-metadata completions on every `|`.
904
- */
905
- export type PipeContextMap = Record<string, Record<string, PipeKeySpec>>;
906
-
907
- export const PIPE_METADATA = new Map<string, PipeContextMap>([
908
- [
909
- 'infra',
910
- {
911
- node: {
912
- description: { description: 'Node description text' },
913
- instances: {
914
- description: 'Instance count or auto-scaling range (N-M)',
915
- },
916
- 'latency-ms': { description: 'Per-request latency in milliseconds' },
917
- 'max-rps': { description: 'Max requests per second per instance' },
918
- 'cache-hit': { description: 'Cache hit percentage (0-100)' },
919
- 'firewall-block': { description: 'Traffic blocked percentage' },
920
- 'ratelimit-rps': { description: 'Max RPS allowed through' },
921
- 'cb-error-threshold': {
922
- description: 'Circuit breaker error threshold %',
923
- },
924
- 'cb-latency-threshold-ms': {
925
- description: 'Circuit breaker latency threshold',
926
- },
927
- uptime: { description: 'Component availability (0-1)' },
928
- concurrency: { description: 'Concurrent request limit' },
929
- 'duration-ms': { description: 'Processing duration' },
930
- 'cold-start-ms': { description: 'Function cold-start time' },
931
- buffer: { description: 'Queue/buffer capacity' },
932
- 'drain-rate': { description: 'Queue drain rate' },
933
- 'retention-hours': { description: 'Data retention period' },
934
- partitions: { description: 'Queue/stream partition count' },
935
- 'slo-availability': { description: 'Node availability target (0-1)' },
936
- 'slo-p90-latency-ms': { description: 'Node p90 latency target' },
937
- 'slo-warning-margin': { description: 'Node SLO warning margin' },
938
- },
939
- edge: {
940
- split: { description: 'Traffic split percentage (e.g., 60%)' },
941
- fanout: { description: 'Fanout multiplier (integer >= 1)' },
942
- },
943
- },
944
- ],
945
- [
946
- 'c4',
947
- {
948
- node: {
949
- description: { description: 'Element description' },
950
- tech: { description: 'Technology stack' },
951
- technology: { description: 'Technology stack (alias for tech)' },
952
- },
953
- edge: {},
954
- },
955
- ],
956
- [
957
- 'gantt',
958
- {
959
- node: {
960
- duration: { description: 'Task duration (e.g., 30bd, 5d, 1.5w)' },
961
- start: { description: 'Explicit start date (e.g., 2024-01-15)' },
962
- progress: { description: 'Task progress (0–100)' },
963
- offset: { description: 'Task start offset (e.g., 8bd, -3bd)' },
964
- },
965
- edge: {
966
- // Gantt "edge" = dependency arrow (TaskA -> TaskB | offset 2bd)
967
- offset: { description: 'Dependency offset (e.g., 2bd, -1w)' },
968
- },
969
- },
970
- ],
971
- [
972
- 'boxes-and-lines',
973
- {
974
- node: {
975
- description: { description: 'Node description text' },
976
- heat: { description: 'Numeric value for the heat (colour) ramp' },
977
- },
978
- edge: {},
979
- },
980
- ],
981
- [
982
- 'mindmap',
983
- {
984
- node: {
985
- description: { description: 'Node description text' },
986
- collapsed: { description: 'Collapse node subtree by default' },
987
- },
988
- edge: {},
989
- },
990
- ],
991
- [
992
- // Tech-radar pipe metadata (spec §20). Two contexts:
993
- // - quadrant: top-level quadrant headers (`Tools | quadrant: top-left, color: blue`)
994
- // - blip: indented blip lines (` Vite | ring: Adopt, trend: up`)
995
- 'tech-radar',
996
- {
997
- quadrant: {
998
- quadrant: {
999
- description: 'Quadrant position',
1000
- values: ['top-left', 'top-right', 'bottom-left', 'bottom-right'],
1001
- },
1002
- color: { description: 'Override quadrant color' },
1003
- },
1004
- blip: {
1005
- ring: { description: 'Ring assignment (must match a declared ring)' },
1006
- trend: {
1007
- description: 'Blip trend indicator',
1008
- values: ['new', 'up', 'down', 'stable'],
1009
- },
1010
- },
1011
- },
1012
- ],
1013
- [
1014
- 'cycle',
1015
- {
1016
- node: {
1017
- color: { description: 'Node fill color (palette name)' },
1018
- span: { description: 'Relative arc distance to next node' },
1019
- description: { description: 'Node description text' },
1020
- },
1021
- edge: {
1022
- color: { description: 'Edge stroke color (palette name)' },
1023
- width: { description: 'Edge stroke width in pixels' },
1024
- },
1025
- },
1026
- ],
1027
- [
1028
- // RACI pipe metadata (spec §24A). Two contexts:
1029
- // - role: declarations inside the `roles` block (`Cap | color: red`)
1030
- // - phase: bracketed phase headers (`[Departure] | color: teal`)
1031
- 'raci',
1032
- {
1033
- role: {
1034
- color: { description: 'Role column tint (palette name)' },
1035
- },
1036
- phase: {
1037
- color: { description: 'Phase bar tint (palette name)' },
1038
- },
1039
- },
1040
- ],
1041
- [
1042
- // Ring layer pipe metadata (spec §24.4). One context: `layer`.
1043
- 'ring',
1044
- {
1045
- layer: {
1046
- color: { description: 'Ring color (palette name)' },
1047
- description: { description: 'Layer description (one-liner shorthand)' },
1048
- },
1049
- },
1050
- ],
1051
- [
1052
- // Pyramid layer pipe metadata (spec §23.4). Identical surface to ring.
1053
- 'pyramid',
1054
- {
1055
- layer: {
1056
- color: { description: 'Layer color (palette name)' },
1057
- description: { description: 'Layer description (one-liner shorthand)' },
1058
- },
1059
- },
1060
- ],
1061
- [
1062
- // Journey-map step pipe metadata (spec §22).
1063
- // Tag aliases (e.g. `ch: Web`) are user-defined via the `tag` block
1064
- // and resolved dynamically — not part of the static map.
1065
- 'journey-map',
1066
- {
1067
- step: {
1068
- score: { description: 'Step score (1–5 integer; high = good)' },
1069
- emotion: { description: 'Emotional state at this step' },
1070
- description: { description: 'Step description text' },
1071
- pain: { description: 'Pain point at this step' },
1072
- opportunity: { description: 'Opportunity at this step' },
1073
- thought: { description: 'User thought at this step' },
1074
- },
1075
- },
1076
- ],
1077
- [
1078
- 'org',
1079
- {
1080
- node: {
1081
- description: { description: 'Person/team description' },
1082
- role: { description: 'Role or job title' },
1083
- location: { description: 'Office location' },
1084
- email: { description: 'Email address' },
1085
- phone: { description: 'Phone number' },
1086
- },
1087
- },
1088
- ],
1089
- [
1090
- 'er',
1091
- {
1092
- node: {
1093
- description: { description: 'Entity description' },
1094
- domain: { description: 'Domain grouping' },
1095
- },
1096
- },
1097
- ],
1098
- [
1099
- 'class',
1100
- {
1101
- node: {
1102
- description: { description: 'Class description' },
1103
- },
1104
- },
1105
- ],
1106
- [
1107
- 'kanban',
1108
- {
1109
- node: {
1110
- description: { description: 'Card description' },
1111
- assignee: { description: 'Assigned person' },
1112
- due: { description: 'Due date (YYYY-MM-DD)' },
1113
- },
1114
- },
1115
- ],
1116
- [
1117
- 'sitemap',
1118
- {
1119
- node: {
1120
- description: { description: 'Page description' },
1121
- status: { description: 'Page status' },
1122
- },
1123
- },
1124
- ],
1125
- [
1126
- 'pert',
1127
- {
1128
- node: {
1129
- description: { description: 'Activity description' },
1130
- confidence: {
1131
- description: 'Confidence factor',
1132
- values: ['high', 'medium', 'low'],
1133
- },
1134
- collapsed: { description: 'Collapse activity detail' },
1135
- },
1136
- },
1137
- ],
1138
- [
1139
- 'timeline',
1140
- {
1141
- node: {
1142
- description: { description: 'Event description' },
1143
- duration: { description: 'Event duration (e.g., 30d, 1.5y)' },
1144
- },
1145
- },
1146
- ],
1147
- ]);
1148
-
1149
- // ============================================================
1150
- // Derived metadata key set
1151
- // ============================================================
1152
-
1153
- /** All known directive keys, derived from COMPLETION_REGISTRY. Includes implicit keys. */
1154
- export const METADATA_KEY_SET: ReadonlySet<string> = new Set([
1155
- 'chart',
1156
- 'title', // implicit directives recognized as metadata
1157
- ...[...COMPLETION_REGISTRY.values()].flatMap((spec) =>
1158
- Object.keys(spec.directives)
1159
- ),
1160
- ]);
1161
-
1162
- // ============================================================
1163
- // Sequence extractor
1164
- // ============================================================
1165
-
1166
- // Universal Name Handling: source/target accept multi-word + "quoted" names.
1167
- // `[^|]+?` captures greedy-to-pipe-or-arrow; the arrow alternation acts as
1168
- // the boundary. Caller strips quotes via stripQuotes().
1169
- const SEQ_ARROW_RE =
1170
- /^(?:"([^"]+)"|([^|"]+?))\s+(->|-.*->|~>|~.*~>)\s+(?:"([^"]+)"|([^|"]+?))(?:\s|\|.*)?$/;
1171
- const SEQ_IS_A_RE = /^(?:"([^"]+)"|([^|":]+?))\s+is\s+an?\s+/i;
1172
- const SEQ_SECTION_RE = /^==/;
1173
- const SEQ_STRUCTURAL_RE = /^(if|else|loop|parallel|end)\b/i;
1174
-
1175
- function extractSequenceSymbols(docText: string): DiagramSymbols {
1176
- const lines = docText.split('\n');
1177
- const entities: string[] = [];
1178
- let pastFirstLine = false;
1179
-
1180
- for (const line of lines) {
1181
- const trimmed = line.trim();
1182
- if (!trimmed || trimmed.startsWith('//')) continue;
1183
-
1184
- // Skip first line (chart type)
1185
- if (!pastFirstLine) {
1186
- pastFirstLine = true;
1187
- continue;
1188
- }
1189
-
1190
- // Skip metadata lines
1191
- // split(/\s+/) on non-empty `trimmed` always yields at least one element.
1192
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
1193
- if (METADATA_KEY_SET.has(firstToken)) continue;
1194
-
1195
- // Skip sections, structural keywords
1196
- if (SEQ_SECTION_RE.test(trimmed)) continue;
1197
- if (SEQ_STRUCTURAL_RE.test(trimmed)) continue;
1198
-
1199
- // Arrow lines: A -> B, A -label-> B, A ~> B
1200
- const arrowMatch = trimmed.match(SEQ_ARROW_RE);
1201
- if (arrowMatch) {
1202
- const src = (arrowMatch[1] ?? arrowMatch[2] ?? '').trim();
1203
- const dst = (arrowMatch[4] ?? arrowMatch[5] ?? '').trim();
1204
- if (src && !entities.includes(src)) entities.push(src);
1205
- if (dst && !entities.includes(dst)) entities.push(dst);
1206
- continue;
1207
- }
1208
-
1209
- // Type declarations: A is a person, A is an actor
1210
- const isAMatch = trimmed.match(SEQ_IS_A_RE);
1211
- if (isAMatch) {
1212
- const name = (isAMatch[1] ?? isAMatch[2] ?? '').trim();
1213
- if (name && !entities.includes(name)) entities.push(name);
1214
- continue;
1215
- }
1216
- }
1217
-
1218
- return {
1219
- kind: 'sequence',
1220
- entities,
1221
- };
1222
- }
1223
-
1224
- // ============================================================
1225
- // State extractor
1226
- // ============================================================
1227
-
1228
- const STATE_ARROW_RE = /^(\S+)\s+->\s+(\S+)/;
1229
-
1230
- function extractStateSymbols(docText: string): DiagramSymbols {
1231
- const lines = docText.split('\n');
1232
- const entities: string[] = [];
1233
- let pastFirstLine = false;
1234
-
1235
- for (const line of lines) {
1236
- const trimmed = line.trim();
1237
- if (!trimmed || trimmed.startsWith('//')) continue;
1238
-
1239
- if (!pastFirstLine) {
1240
- pastFirstLine = true;
1241
- continue;
1242
- }
1243
-
1244
- // Skip metadata lines
1245
- // split(/\s+/) on non-empty `trimmed` always yields at least one element.
1246
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
1247
- if (METADATA_KEY_SET.has(firstToken)) continue;
1248
-
1249
- const arrowMatch = trimmed.match(STATE_ARROW_RE);
1250
- if (arrowMatch) {
1251
- // Regex captured groups 1 and 2 by successful match; split('|')[0] always defined.
1252
- const src = arrowMatch[1]!.split('|')[0]!.trim();
1253
- const dst = arrowMatch[2]!.split('|')[0]!.trim();
1254
- if (src && !entities.includes(src)) entities.push(src);
1255
- if (dst && !entities.includes(dst)) entities.push(dst);
1256
- }
1257
- }
1258
-
1259
- return { kind: 'state', entities };
1260
- }
1261
-
1262
- // ============================================================
1263
- // Tag declaration extraction
1264
- // ============================================================
1265
-
1266
- // Matches tag declarations in both forms:
1267
- // - `tag Name alias x` (explicit alias keyword)
1268
- // - `tag Name x` (shorthand: 1-4 lowercase chars = alias, matching parser's isAliasToken)
1269
- const TAG_DECL_EXPLICIT_RE = /^tag\s+(\S+)\s+alias\s+(\S+)/i;
1270
- const TAG_DECL_SHORT_RE = /^tag\s+(\S+)\s+([a-z]{1,4})(?:\s|$)/;
1271
-
1272
- /**
1273
- * Extract tag declarations from document text.
1274
- * Returns a map of alias (or full name) → array of tag values.
1275
- * Keys preserve original case for display; use case-insensitive lookup.
1276
- */
1277
- export function extractTagDeclarations(docText: string): Map<string, string[]> {
1278
- const result = new Map<string, string[]>();
1279
- const lines = docText.split('\n');
1280
- let currentAlias: string | null = null;
1281
- let currentValues: string[] = [];
1282
-
1283
- for (let i = 0; i < lines.length; i++) {
1284
- // In-bounds by loop guard.
1285
- const raw = lines[i]!;
1286
- const trimmed = raw.trim();
1287
-
1288
- // Check for tag declaration — try explicit `alias` keyword first, then shorthand
1289
- const tagMatch =
1290
- trimmed.match(TAG_DECL_EXPLICIT_RE) ?? trimmed.match(TAG_DECL_SHORT_RE);
1291
- if (tagMatch) {
1292
- // Save previous tag group
1293
- if (currentAlias !== null) {
1294
- result.set(currentAlias, currentValues);
1295
- }
1296
- // Both regexes capture groups 1 (and 2 for explicit) on successful match.
1297
- const name = tagMatch[1]!;
1298
- const alias = tagMatch[2] ?? name;
1299
- currentAlias = alias;
1300
- currentValues = [];
1301
- continue;
1302
- }
1303
- // Also match bare `tag Name` (no alias) — fall through with name as key
1304
- if (/^tag\s+(\S+)\s*$/i.test(trimmed)) {
1305
- if (currentAlias !== null) {
1306
- result.set(currentAlias, currentValues);
1307
- }
1308
- // Regex captured group 1 by successful re-match (test passed above).
1309
- currentAlias = trimmed.match(/^tag\s+(\S+)/i)![1]!;
1310
- currentValues = [];
1311
- continue;
1312
- }
1313
-
1314
- // Collect indented tag values
1315
- if (
1316
- currentAlias !== null &&
1317
- raw.length > 0 &&
1318
- (raw[0] === ' ' || raw[0] === '\t')
1319
- ) {
1320
- if (trimmed && !trimmed.startsWith('//')) {
1321
- // Strip trailing-token color (§1.5): `Frontend blue` → `Frontend`.
1322
- // Whitespace-split; if the last token is a recognized color word,
1323
- // drop it; otherwise the whole trimmed string is the value.
1324
- const lastSpaceIdx = trimmed.lastIndexOf(' ');
1325
- const value =
1326
- lastSpaceIdx > 0 &&
1327
- RECOGNIZED_COLOR_SET.has(trimmed.substring(lastSpaceIdx + 1))
1328
- ? trimmed.substring(0, lastSpaceIdx).trim()
1329
- : trimmed;
1330
- if (value) currentValues.push(value);
1331
- }
1332
- continue;
1333
- }
1334
-
1335
- // Non-indented non-tag line ends the current tag block
1336
- if (currentAlias !== null && trimmed) {
1337
- result.set(currentAlias, currentValues);
1338
- currentAlias = null;
1339
- currentValues = [];
1340
- }
1341
- }
1342
-
1343
- // Save last tag group
1344
- if (currentAlias !== null) {
1345
- result.set(currentAlias, currentValues);
1346
- }
1347
-
1348
- return result;
1349
- }
1350
-
1351
- // ============================================================
1352
- // Universal alias extractor (`Name as <alias>` postfix)
1353
- // ============================================================
1354
-
1355
- // Postfix-alias form on any name-slot line. Caller-agnostic — runs
1356
- // over the full document so every chart-type extractor can populate
1357
- // `DiagramSymbols.aliases` consistently.
1358
- const ALIAS_POSTFIX_DECL_RE =
1359
- /(?:^|[^|/])\s*(.+?)\s+as\s+([A-Za-z][A-Za-z0-9_]{0,11})\s*(?:\|.*)?$/;
1360
-
1361
- /**
1362
- * Scan document text for `Name as <alias>` declarations.
1363
- *
1364
- * Returns `Record<alias, canonical-fragment>`. The canonical may
1365
- * still carry color/type modifiers (the per-parser logic peels
1366
- * those off at parse time); for autocomplete display purposes the
1367
- * raw fragment is good enough.
1368
- *
1369
- * Pure helper — does NOT enforce strict-ordering, collisions, or
1370
- * other semantic rules. Those are parser-side checks.
1371
- */
1372
- export function extractAliasDeclarations(
1373
- docText: string
1374
- ): Record<string, string> {
1375
- const aliases: Record<string, string> = {};
1376
- for (const raw of docText.split('\n')) {
1377
- const trimmed = raw.trim();
1378
- if (!trimmed || trimmed.startsWith('//')) continue;
1379
- const match = trimmed.match(ALIAS_POSTFIX_DECL_RE);
1380
- if (!match) continue;
1381
- // Regex captured groups 1 and 2 by successful match.
1382
- const canonical = match[1]!.trim();
1383
- const alias = match[2]!;
1384
- // Skip if canonical itself looks structural (arrow / pipe-only / brackets-only)
1385
- if (!canonical || canonical === '[' || canonical === ']') continue;
1386
- if (!(alias in aliases)) {
1387
- aliases[alias] = canonical;
1388
- }
1389
- }
1390
- return aliases;
1391
- }
1392
-
1393
- // ============================================================
1394
- // Sitemap extractor
1395
- // ============================================================
1396
-
1397
- const SITEMAP_CONTAINER_RE = /^\[([^\]]+)\]/;
1398
- const SITEMAP_ARROW_RE = /^-.*->\s*(.+)$/;
1399
- const SITEMAP_BARE_ARROW_RE = /^->\s*(.+)$/;
1400
- const SITEMAP_METADATA_RE = /^([^:]+):\s*(.+)$/;
1401
-
1402
- function extractSitemapSymbols(docText: string): DiagramSymbols {
1403
- const lines = docText.split('\n');
1404
- const entities: string[] = [];
1405
- let pastFirstLine = false;
1406
- let inTagBlock = false;
1407
- let lastNodeIndent = -1;
1408
-
1409
- for (const line of lines) {
1410
- const trimmed = line.trim();
1411
- if (!trimmed || trimmed.startsWith('//')) continue;
1412
-
1413
- if (!pastFirstLine) {
1414
- pastFirstLine = true;
1415
- continue;
1416
- }
1417
-
1418
- // Skip metadata lines
1419
- // split(/\s+/) on non-empty `trimmed` always yields at least one element.
1420
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
1421
- if (METADATA_KEY_SET.has(firstToken)) continue;
1422
-
1423
- // Track tag blocks
1424
- if (/^tag\s+/i.test(trimmed)) {
1425
- inTagBlock = true;
1426
- continue;
1427
- }
1428
- const indent = line.search(/\S/);
1429
- if (inTagBlock) {
1430
- if (indent > 0) continue;
1431
- inTagBlock = false;
1432
- }
1433
-
1434
- // Containers: [GroupName]
1435
- const containerMatch = trimmed.match(SITEMAP_CONTAINER_RE);
1436
- if (containerMatch) {
1437
- // Regex captured group 1 by successful match; split('|')[0] always defined.
1438
- const name = containerMatch[1]!.split('|')[0]!.trim();
1439
- if (name && !entities.includes(name)) entities.push(name);
1440
- lastNodeIndent = indent;
1441
- continue;
1442
- }
1443
-
1444
- // Arrows: -> Target or -label-> Target
1445
- const bareArrow = trimmed.match(SITEMAP_BARE_ARROW_RE);
1446
- const labeledArrow = !bareArrow ? trimmed.match(SITEMAP_ARROW_RE) : null;
1447
- if (bareArrow || labeledArrow) {
1448
- // split('|')[0] always defined on any string.
1449
- const target = (bareArrow?.[1] ?? labeledArrow?.[1] ?? '')
1450
- .split('|')[0]!
1451
- .trim();
1452
- if (target && !entities.includes(target)) entities.push(target);
1453
- continue;
1454
- }
1455
-
1456
- // Indented metadata under a node (key: value) — skip
1457
- if (
1458
- indent > 0 &&
1459
- lastNodeIndent >= 0 &&
1460
- indent > lastNodeIndent &&
1461
- SITEMAP_METADATA_RE.test(trimmed)
1462
- ) {
1463
- continue;
1464
- }
1465
-
1466
- // Page label (anything else that's not special)
1467
- // split('|')[0] always defined on any string.
1468
- const label = trimmed.split('|')[0]!.trim();
1469
- if (label) {
1470
- if (!entities.includes(label)) entities.push(label);
1471
- lastNodeIndent = indent;
1472
- }
1473
- }
1474
-
1475
- return { kind: 'sitemap', entities };
1476
- }
1477
-
1478
- // ============================================================
1479
- // C4 extractor
1480
- // ============================================================
1481
-
1482
- const C4_ELEMENT_RE = /^(person|system|container|component)\s+(.+)$/i;
1483
- const C4_IS_A_RE =
1484
- /^(.+?)\s+is\s+an?\s+(person|system|container|component|external|database)\b/i;
1485
- const C4_ARROW_RE =
1486
- /^(\S+)\s+(?:->|-.*->|~>|~.*~>|<->|<-.*->|<~>|<~.*~>)\s+(\S+)/;
1487
- const C4_SECTION_RE = /^(containers|components|deployment)\s*$/i;
1488
-
1489
- function extractC4Symbols(docText: string): DiagramSymbols {
1490
- const lines = docText.split('\n');
1491
- const entities: string[] = [];
1492
- let pastFirstLine = false;
1493
- let inTagBlock = false;
1494
-
1495
- for (const line of lines) {
1496
- const trimmed = line.trim();
1497
- if (!trimmed || trimmed.startsWith('//')) continue;
1498
-
1499
- if (!pastFirstLine) {
1500
- pastFirstLine = true;
1501
- continue;
1502
- }
1503
-
1504
- // split(/\s+/) on non-empty `trimmed` always yields at least one element.
1505
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
1506
- if (METADATA_KEY_SET.has(firstToken)) continue;
1507
-
1508
- if (/^tag\s+/i.test(trimmed)) {
1509
- inTagBlock = true;
1510
- continue;
1511
- }
1512
- const indent = line.search(/\S/);
1513
- if (inTagBlock) {
1514
- if (indent > 0) continue;
1515
- inTagBlock = false;
1516
- }
1517
-
1518
- // Skip section headers
1519
- if (C4_SECTION_RE.test(trimmed)) continue;
1520
-
1521
- // Element declaration: person Name, system Name, etc.
1522
- const elemMatch = trimmed.match(C4_ELEMENT_RE);
1523
- if (elemMatch) {
1524
- // Regex captured group 2 by successful match; split('|')[0] always defined.
1525
- const name = elemMatch[2]!.split('|')[0]!.trim();
1526
- if (name && !entities.includes(name)) entities.push(name);
1527
- continue;
1528
- }
1529
-
1530
- // Is-a declaration: Name is a person
1531
- const isAMatch = trimmed.match(C4_IS_A_RE);
1532
- if (isAMatch) {
1533
- // Regex captured group 1 by successful match; split('|')[0] always defined.
1534
- const name = isAMatch[1]!.split('|')[0]!.trim();
1535
- if (name && !entities.includes(name)) entities.push(name);
1536
- continue;
1537
- }
1538
-
1539
- // Arrow lines: Source -> Target, Source ~> Target, etc.
1540
- const arrowMatch = trimmed.match(C4_ARROW_RE);
1541
- if (arrowMatch) {
1542
- // Regex captured groups 1 and 2 by successful match; split('|')[0] always defined.
1543
- const src = arrowMatch[1]!.split('|')[0]!.trim();
1544
- const dst = arrowMatch[2]!.split('|')[0]!.trim();
1545
- if (src && !entities.includes(src)) entities.push(src);
1546
- if (dst && !entities.includes(dst)) entities.push(dst);
1547
- continue;
1548
- }
1549
- }
1550
-
1551
- return {
1552
- kind: 'c4',
1553
- entities,
1554
- };
1555
- }
1556
-
1557
- // ============================================================
1558
- // Gantt extractor
1559
- // ============================================================
1560
-
1561
- const GANTT_LEGACY_DURATION_RE =
1562
- /^(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h|s)\??\s+(.+)$/;
1563
- const GANTT_LEGACY_DATE_RE = /^(\d{4}-\d{2}-\d{2}(?:\s\d{2}:\d{2})?)\s+(.+)$/;
1564
- const GANTT_GROUP_RE = /^\[(.+?)\]/;
1565
- const GANTT_STRUCTURAL_RE = /^(era|marker|holiday|workweek|parallel)\b/i;
1566
- const GANTT_META_KEY_RE = /\b(?:duration|start):\s/;
1567
-
1568
- function extractGanttSymbols(docText: string): DiagramSymbols {
1569
- const lines = docText.split('\n');
1570
- const entities: string[] = [];
1571
- let pastFirstLine = false;
1572
- let inTagBlock = false;
1573
-
1574
- for (const line of lines) {
1575
- const trimmed = line.trim();
1576
- if (!trimmed || trimmed.startsWith('//')) continue;
1577
-
1578
- if (!pastFirstLine) {
1579
- pastFirstLine = true;
1580
- continue;
1581
- }
1582
-
1583
- // split(/\s+/) on non-empty `trimmed` always yields at least one element.
1584
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
1585
- if (METADATA_KEY_SET.has(firstToken)) continue;
1586
-
1587
- if (/^tag\s+/i.test(trimmed)) {
1588
- inTagBlock = true;
1589
- continue;
1590
- }
1591
- const indent = line.search(/\S/);
1592
- if (inTagBlock) {
1593
- if (indent > 0) continue;
1594
- inTagBlock = false;
1595
- }
1596
-
1597
- // Groups: [GroupName]
1598
- const groupMatch = trimmed.match(GANTT_GROUP_RE);
1599
- if (groupMatch) {
1600
- const name = groupMatch[1]!.trim();
1601
- if (name && !entities.includes(name)) entities.push(name);
1602
- continue;
1603
- }
1604
-
1605
- // New syntax: Task Name duration: 5d or Task Name start: 2024-01-15
1606
- // (checked before structural keyword skip so "Era of Innovation duration: 5d" isn't skipped)
1607
- if (GANTT_META_KEY_RE.test(trimmed)) {
1608
- const cutIdx = trimmed.search(
1609
- /\b(?:duration|start|progress|offset|color|description):\s/
1610
- );
1611
- if (cutIdx > 0) {
1612
- let taskName = trimmed.substring(0, cutIdx).trim();
1613
- const arrowIdx = taskName.indexOf('->');
1614
- if (arrowIdx > 0)
1615
- taskName = taskName
1616
- .substring(0, arrowIdx)
1617
- .replace(/-[^>]*$/, '')
1618
- .trim();
1619
- if (taskName && !entities.includes(taskName)) entities.push(taskName);
1620
- continue;
1621
- }
1622
- }
1623
-
1624
- // Skip structural keywords (after new-syntax check so "Era of Innovation duration: 5d" isn't skipped)
1625
- if (GANTT_STRUCTURAL_RE.test(trimmed)) continue;
1626
-
1627
- // Legacy: Tasks by duration: 30d Task Name
1628
- const durMatch = trimmed.match(GANTT_LEGACY_DURATION_RE);
1629
- if (durMatch) {
1630
- let taskName = durMatch[3]!.split('|')[0]!.trim();
1631
- const arrowIdx = taskName.indexOf('->');
1632
- if (arrowIdx > 0)
1633
- taskName = taskName
1634
- .substring(0, arrowIdx)
1635
- .replace(/-[^>]*$/, '')
1636
- .trim();
1637
- if (taskName && !entities.includes(taskName)) entities.push(taskName);
1638
- continue;
1639
- }
1640
-
1641
- // Legacy: Tasks by date: 2024-01-15 Task Name
1642
- const dateMatch = trimmed.match(GANTT_LEGACY_DATE_RE);
1643
- if (dateMatch) {
1644
- let taskName = dateMatch[2]!.split('|')[0]!.trim();
1645
- const arrowIdx = taskName.indexOf('->');
1646
- if (arrowIdx > 0)
1647
- taskName = taskName
1648
- .substring(0, arrowIdx)
1649
- .replace(/-[^>]*$/, '')
1650
- .trim();
1651
- if (taskName && !entities.includes(taskName)) entities.push(taskName);
1652
- continue;
1653
- }
1654
- }
1655
-
1656
- return { kind: 'gantt', entities };
1657
- }
1658
-
1659
- // ============================================================
1660
- // Boxes-and-lines extractor
1661
- // ============================================================
1662
-
1663
- const BL_ARROW_RE = /^(\S+)\s+(?:-.*)?(?:->|<->)\s+(\S+)/;
1664
-
1665
- function extractBoxesAndLinesSymbols(docText: string): DiagramSymbols {
1666
- const lines = docText.split('\n');
1667
- const entities: string[] = [];
1668
- let pastFirstLine = false;
1669
- let inTagBlock = false;
1670
-
1671
- for (const line of lines) {
1672
- const trimmed = line.trim();
1673
- if (!trimmed || trimmed.startsWith('//')) continue;
1674
-
1675
- if (!pastFirstLine) {
1676
- pastFirstLine = true;
1677
- continue;
1678
- }
1679
-
1680
- // split(/\s+/) on non-empty `trimmed` always yields at least one element.
1681
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
1682
- if (METADATA_KEY_SET.has(firstToken)) continue;
1683
-
1684
- if (/^tag\s+/i.test(trimmed)) {
1685
- inTagBlock = true;
1686
- continue;
1687
- }
1688
- const indent = line.search(/\S/);
1689
- if (inTagBlock) {
1690
- if (indent > 0) continue;
1691
- inTagBlock = false;
1692
- }
1693
-
1694
- // Skip groups
1695
- if (/^\[.+?\]/.test(trimmed)) continue;
1696
-
1697
- // Edge lines
1698
- const arrowMatch = trimmed.match(BL_ARROW_RE);
1699
- if (arrowMatch) {
1700
- // Regex captured groups 1 and 2 by successful match; split('|')[0] always defined.
1701
- const src = arrowMatch[1]!.split('|')[0]!.trim();
1702
- const dst = arrowMatch[2]!.split('|')[0]!.trim();
1703
- if (src && !entities.includes(src)) entities.push(src);
1704
- if (dst && !entities.includes(dst)) entities.push(dst);
1705
- continue;
1706
- }
1707
-
1708
- // Node lines
1709
- // split('|')[0] and chained split('[')[0] always defined on any string.
1710
- const label = trimmed.split('|')[0]!.split('[')[0]!.trim();
1711
- if (label && !entities.includes(label)) entities.push(label);
1712
- }
1713
-
1714
- return { kind: 'boxes-and-lines', entities };
1715
- }
1716
-
1717
- // ============================================================
1718
- // Org extractor
1719
- // ============================================================
1720
-
1721
- const ORG_GROUP_RE = /^\[(.+?)\]/;
1722
-
1723
- function extractOrgSymbols(docText: string): DiagramSymbols {
1724
- const lines = docText.split('\n');
1725
- const entities: string[] = [];
1726
- let pastFirstLine = false;
1727
- let inTagBlock = false;
1728
-
1729
- for (const line of lines) {
1730
- const trimmed = line.trim();
1731
- if (!trimmed || trimmed.startsWith('//')) continue;
1732
-
1733
- if (!pastFirstLine) {
1734
- pastFirstLine = true;
1735
- continue;
1736
- }
1737
-
1738
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
1739
- if (METADATA_KEY_SET.has(firstToken)) continue;
1740
-
1741
- if (/^tag\s+/i.test(trimmed)) {
1742
- inTagBlock = true;
1743
- continue;
1744
- }
1745
- const indent = line.search(/\S/);
1746
- if (inTagBlock) {
1747
- if (indent > 0) continue;
1748
- inTagBlock = false;
1749
- }
1750
-
1751
- // Team/group headers: [Team Name]
1752
- const groupMatch = trimmed.match(ORG_GROUP_RE);
1753
- if (groupMatch) {
1754
- const name = groupMatch[1]!.split('|')[0]!.trim();
1755
- if (name && !entities.includes(name)) entities.push(name);
1756
- continue;
1757
- }
1758
-
1759
- // Skip indented metadata lines (key: value)
1760
- if (indent > 0 && /^[a-z]+\s*:/.test(trimmed)) continue;
1761
-
1762
- // Person name (indent 0 or direct child)
1763
- const label = trimmed.split('|')[0]!.trim();
1764
- if (label && !entities.includes(label)) entities.push(label);
1765
- }
1766
-
1767
- return { kind: 'org', entities };
1768
- }
1769
-
1770
- // ============================================================
1771
- // Kanban extractor
1772
- // ============================================================
1773
-
1774
- const KANBAN_COLUMN_RE = /^\[(.+?)\]/;
1775
-
1776
- function extractKanbanSymbols(docText: string): DiagramSymbols {
1777
- const lines = docText.split('\n');
1778
- const entities: string[] = [];
1779
- let pastFirstLine = false;
1780
- let inTagBlock = false;
1781
-
1782
- for (const line of lines) {
1783
- const trimmed = line.trim();
1784
- if (!trimmed || trimmed.startsWith('//')) continue;
1785
-
1786
- if (!pastFirstLine) {
1787
- pastFirstLine = true;
1788
- continue;
1789
- }
1790
-
1791
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
1792
- if (METADATA_KEY_SET.has(firstToken)) continue;
1793
-
1794
- if (/^tag\s+/i.test(trimmed)) {
1795
- inTagBlock = true;
1796
- continue;
1797
- }
1798
- const indent = line.search(/\S/);
1799
- if (inTagBlock) {
1800
- if (indent > 0) continue;
1801
- inTagBlock = false;
1802
- }
1803
-
1804
- // Column headers: [Column Name]
1805
- const colMatch = trimmed.match(KANBAN_COLUMN_RE);
1806
- if (colMatch) {
1807
- const name = colMatch[1]!.split('|')[0]!.trim();
1808
- if (name && !entities.includes(name)) entities.push(name);
1809
- continue;
1810
- }
1811
-
1812
- // Card names (indented under columns)
1813
- if (indent > 0) {
1814
- const label = trimmed.split('|')[0]!.trim();
1815
- if (label && !entities.includes(label)) entities.push(label);
1816
- }
1817
- }
1818
-
1819
- return { kind: 'kanban', entities };
1820
- }
1821
-
1822
- // ============================================================
1823
- // Mindmap extractor
1824
- // ============================================================
1825
-
1826
- function extractMindmapSymbols(docText: string): DiagramSymbols {
1827
- const lines = docText.split('\n');
1828
- const entities: string[] = [];
1829
- let pastFirstLine = false;
1830
- let inTagBlock = false;
1831
-
1832
- for (const line of lines) {
1833
- const trimmed = line.trim();
1834
- if (!trimmed || trimmed.startsWith('//')) continue;
1835
-
1836
- if (!pastFirstLine) {
1837
- pastFirstLine = true;
1838
- continue;
1839
- }
1840
-
1841
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
1842
- if (METADATA_KEY_SET.has(firstToken)) continue;
1843
-
1844
- if (/^tag\s+/i.test(trimmed)) {
1845
- inTagBlock = true;
1846
- continue;
1847
- }
1848
- const indent = line.search(/\S/);
1849
- if (inTagBlock) {
1850
- if (indent > 0) continue;
1851
- inTagBlock = false;
1852
- }
1853
-
1854
- // Skip indented metadata (description:, collapsed:)
1855
- if (/^(description|collapsed)\s*:/i.test(trimmed)) continue;
1856
-
1857
- // Node name (at any indent level)
1858
- const label = trimmed.split('|')[0]!.trim();
1859
- if (label && !entities.includes(label)) entities.push(label);
1860
- }
1861
-
1862
- return { kind: 'mindmap', entities };
1863
- }
1864
-
1865
- // ============================================================
1866
- // Treemap extractor
1867
- // ============================================================
1868
-
1869
- function extractTreemapSymbols(docText: string): DiagramSymbols {
1870
- const lines = docText.split('\n');
1871
- const entities: string[] = [];
1872
- let pastFirstLine = false;
1873
- let inTagBlock = false;
1874
-
1875
- for (const line of lines) {
1876
- const trimmed = line.trim();
1877
- if (!trimmed || trimmed.startsWith('//')) continue;
1878
-
1879
- if (!pastFirstLine) {
1880
- pastFirstLine = true;
1881
- continue;
1882
- }
1883
-
1884
- // Directives and tag blocks are not node entities.
1885
- if (
1886
- /^(depth|heat|no-[a-z]+)\s/i.test(trimmed) ||
1887
- /^(no-[a-z]+|radial)$/i.test(trimmed)
1888
- )
1889
- continue;
1890
- if (/^tag\s+/i.test(trimmed)) {
1891
- inTagBlock = true;
1892
- continue;
1893
- }
1894
- const indent = line.search(/\S/);
1895
- if (inTagBlock) {
1896
- if (indent > 0) continue;
1897
- inTagBlock = false;
1898
- }
1899
-
1900
- // Node name: strip same-line metadata + the bare trailing value number.
1901
- let label = trimmed.split(/\s+\w+:/)[0]!.trim();
1902
- label = label.replace(/\s+-?\d[\d_,.]*$/, '').trim();
1903
- if (label && !entities.includes(label)) entities.push(label);
1904
- }
1905
-
1906
- return { kind: 'treemap', entities };
1907
- }
1908
-
1909
- /** Block-diagram symbols: every `[Label]` on a grid row (skips tag blocks and
1910
- * the `columns` / `no-*` directives). */
1911
- function extractBlockSymbols(docText: string): DiagramSymbols {
1912
- const lines = docText.split('\n');
1913
- const entities: string[] = [];
1914
- let pastFirstLine = false;
1915
- let inTagBlock = false;
1916
-
1917
- for (const line of lines) {
1918
- const trimmed = line.trim();
1919
- if (!trimmed || trimmed.startsWith('//')) continue;
1920
- if (!pastFirstLine) {
1921
- pastFirstLine = true;
1922
- continue;
1923
- }
1924
- if (/^(columns|no-[a-z]+)\b/i.test(trimmed)) continue;
1925
- if (/^tag\s+/i.test(trimmed)) {
1926
- inTagBlock = true;
1927
- continue;
1928
- }
1929
- const indent = line.search(/\S/);
1930
- if (inTagBlock) {
1931
- if (indent > 0) continue;
1932
- inTagBlock = false;
1933
- }
1934
- const re = /\[([^\]]*)\]/g;
1935
- let m: RegExpExecArray | null;
1936
- while ((m = re.exec(trimmed)) !== null) {
1937
- const label = m[1]!.trim();
1938
- if (label && !entities.includes(label)) entities.push(label);
1939
- }
1940
- }
1941
-
1942
- return { kind: 'block', entities };
1943
- }
1944
-
1945
- // ============================================================
1946
- // Pyramid extractor
1947
- // ============================================================
1948
-
1949
- function extractPyramidSymbols(docText: string): DiagramSymbols {
1950
- const lines = docText.split('\n');
1951
- const entities: string[] = [];
1952
- let pastFirstLine = false;
1953
-
1954
- for (const line of lines) {
1955
- const trimmed = line.trim();
1956
- if (!trimmed || trimmed.startsWith('//')) continue;
1957
-
1958
- if (!pastFirstLine) {
1959
- pastFirstLine = true;
1960
- continue;
1961
- }
1962
-
1963
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
1964
- if (METADATA_KEY_SET.has(firstToken)) continue;
1965
- if (firstToken === 'inverted' || firstToken === 'solid-fill') continue;
1966
-
1967
- // Skip indented description lines
1968
- if (line[0] === ' ' || line[0] === '\t') continue;
1969
-
1970
- // Layer name (strip pipe metadata)
1971
- const label = trimmed.split('|')[0]!.trim();
1972
- if (label && !entities.includes(label)) entities.push(label);
1973
- }
1974
-
1975
- return { kind: 'pyramid', entities };
1976
- }
1977
-
1978
- // ============================================================
1979
- // Ring extractor
1980
- // ============================================================
1981
-
1982
- function extractRingSymbols(docText: string): DiagramSymbols {
1983
- const lines = docText.split('\n');
1984
- const entities: string[] = [];
1985
- let pastFirstLine = false;
1986
-
1987
- for (const line of lines) {
1988
- const trimmed = line.trim();
1989
- if (!trimmed || trimmed.startsWith('//')) continue;
1990
-
1991
- if (!pastFirstLine) {
1992
- pastFirstLine = true;
1993
- continue;
1994
- }
1995
-
1996
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
1997
- if (METADATA_KEY_SET.has(firstToken)) continue;
1998
- if (firstToken === 'solid-fill') continue;
1999
-
2000
- // Skip indented description lines
2001
- if (line[0] === ' ' || line[0] === '\t') continue;
2002
-
2003
- // Layer name (strip pipe metadata)
2004
- const label = trimmed.split('|')[0]!.trim();
2005
- if (label && !entities.includes(label)) entities.push(label);
2006
- }
2007
-
2008
- return { kind: 'ring', entities };
2009
- }
2010
-
2011
- // ============================================================
2012
- // Arc extractor
2013
- // ============================================================
2014
-
2015
- const ARC_ARROW_RE = /^(\S+)\s+(?:->|-[^>]*->)\s+(\S+)/;
2016
-
2017
- function extractArcSymbols(docText: string): DiagramSymbols {
2018
- const lines = docText.split('\n');
2019
- const entities: string[] = [];
2020
- let pastFirstLine = false;
2021
-
2022
- for (const line of lines) {
2023
- const trimmed = line.trim();
2024
- if (!trimmed || trimmed.startsWith('//')) continue;
2025
-
2026
- if (!pastFirstLine) {
2027
- pastFirstLine = true;
2028
- continue;
2029
- }
2030
-
2031
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
2032
- if (METADATA_KEY_SET.has(firstToken)) continue;
2033
-
2034
- const arrowMatch = trimmed.match(ARC_ARROW_RE);
2035
- if (arrowMatch) {
2036
- const src = arrowMatch[1]!.split('|')[0]!.trim();
2037
- const dst = arrowMatch[2]!.split('|')[0]!.trim();
2038
- if (src && !entities.includes(src)) entities.push(src);
2039
- if (dst && !entities.includes(dst)) entities.push(dst);
2040
- }
2041
- }
2042
-
2043
- return { kind: 'arc', entities };
2044
- }
2045
-
2046
- // ============================================================
2047
- // Sankey extractor
2048
- // ============================================================
2049
-
2050
- // Sankey links accept both directed `->` and undirected `--` (echarts.ts §1.5).
2051
- const SANKEY_ARROW_RE = /^(.+?)\s+(?:->|--)\s+(.+?)\s+(\d[\d,_.]*)/;
2052
-
2053
- function extractSankeySymbols(docText: string): DiagramSymbols {
2054
- const lines = docText.split('\n');
2055
- const entities: string[] = [];
2056
- let pastFirstLine = false;
2057
-
2058
- for (const line of lines) {
2059
- const trimmed = line.trim();
2060
- if (!trimmed || trimmed.startsWith('//')) continue;
2061
-
2062
- if (!pastFirstLine) {
2063
- pastFirstLine = true;
2064
- continue;
2065
- }
2066
-
2067
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
2068
- if (METADATA_KEY_SET.has(firstToken)) continue;
2069
-
2070
- const arrowMatch = trimmed.match(SANKEY_ARROW_RE);
2071
- if (arrowMatch) {
2072
- const src = arrowMatch[1]!.split('|')[0]!.trim();
2073
- const dst = arrowMatch[2]!.split('|')[0]!.trim();
2074
- if (src && !entities.includes(src)) entities.push(src);
2075
- if (dst && !entities.includes(dst)) entities.push(dst);
2076
- } else {
2077
- // Standalone node declaration (just a name, possibly with color)
2078
- const label = trimmed.split('|')[0]!.trim();
2079
- if (label && !entities.includes(label)) entities.push(label);
2080
- }
2081
- }
2082
-
2083
- return { kind: 'sankey', entities };
2084
- }
2085
-
2086
- // ============================================================
2087
- // Timeline extractor
2088
- // ============================================================
2089
-
2090
- const TIMELINE_ERA_RE = /^era\s+/i;
2091
- const TIMELINE_MARKER_RE = /^marker\s+/i;
2092
-
2093
- const TIMELINE_SCHEDULING_RE = /\b(?:start|end|duration)\s*:/;
2094
-
2095
- function extractTimelineSymbols(docText: string): DiagramSymbols {
2096
- const lines = docText.split('\n');
2097
- const entities: string[] = [];
2098
- let pastFirstLine = false;
2099
- let inTagBlock = false;
2100
-
2101
- for (const line of lines) {
2102
- const trimmed = line.trim();
2103
- if (!trimmed || trimmed.startsWith('//')) continue;
2104
-
2105
- if (!pastFirstLine) {
2106
- pastFirstLine = true;
2107
- continue;
2108
- }
2109
-
2110
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
2111
- if (METADATA_KEY_SET.has(firstToken)) continue;
2112
- if (TIMELINE_ERA_RE.test(trimmed) || TIMELINE_MARKER_RE.test(trimmed))
2113
- continue;
2114
-
2115
- if (/^tag\s+/i.test(trimmed)) {
2116
- inTagBlock = true;
2117
- continue;
2118
- }
2119
- const indent = line.search(/\S/);
2120
- if (inTagBlock) {
2121
- if (indent > 0) continue;
2122
- inTagBlock = false;
2123
- }
2124
-
2125
- let label: string;
2126
- if (TIMELINE_SCHEDULING_RE.test(trimmed)) {
2127
- label = trimmed
2128
- .replace(/\b(?:start|end|duration|color|description)\s*:.*$/, '')
2129
- .split('|')[0]!
2130
- .trim();
2131
- } else {
2132
- label = trimmed
2133
- .replace(
2134
- /^\d{4}(?:-\d{2}(?:-\d{2})?)?\s*(?:->\s*\d{4}(?:-\d{2}(?:-\d{2})?)?)?\s*/,
2135
- ''
2136
- )
2137
- .split('|')[0]!
2138
- .trim();
2139
- }
2140
- if (label && !entities.includes(label)) entities.push(label);
2141
- }
2142
-
2143
- return { kind: 'timeline', entities };
2144
- }
2145
-
2146
- // ============================================================
2147
- // Venn extractor
2148
- // ============================================================
2149
-
2150
- function extractVennSymbols(docText: string): DiagramSymbols {
2151
- const lines = docText.split('\n');
2152
- const entities: string[] = [];
2153
- let pastFirstLine = false;
2154
-
2155
- for (const line of lines) {
2156
- const trimmed = line.trim();
2157
- if (!trimmed || trimmed.startsWith('//')) continue;
2158
-
2159
- if (!pastFirstLine) {
2160
- pastFirstLine = true;
2161
- continue;
2162
- }
2163
-
2164
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
2165
- if (METADATA_KEY_SET.has(firstToken)) continue;
2166
-
2167
- // Skip indented intersection lines
2168
- if (line[0] === ' ' || line[0] === '\t') continue;
2169
-
2170
- // Skip intersection rows — those are references to prior sets, not
2171
- // declarations. Set NAMES + aliases come from the declaration lines below
2172
- // (the `+`-completion offers those). Match the parser's detection (d3.ts:
2173
- // any `+` on the line = intersection), not just the spaced form.
2174
- if (trimmed.includes('+')) continue;
2175
-
2176
- // Set declaration: `Name [as <alias>] [color]`. Emit both the clean name
2177
- // and the alias so `Set + ` reference completion can offer either token.
2178
- const work = trimmed.split('|')[0]!.trim();
2179
- const asMatch = work.match(/^(.+?)\s+as\s+([A-Za-z][\w-]*)\b/i);
2180
- if (asMatch) {
2181
- const name = asMatch[1]!.trim();
2182
- const alias = asMatch[2]!;
2183
- if (name && !entities.includes(name)) entities.push(name);
2184
- if (alias && !entities.includes(alias)) entities.push(alias);
2185
- continue;
2186
- }
2187
- // No alias — strip a trailing color token if present.
2188
- const colorMatch = work.match(/^(.+?)\s+(\S+)$/);
2189
- const label =
2190
- colorMatch && RECOGNIZED_COLOR_SET.has(colorMatch[2]!.toLowerCase())
2191
- ? colorMatch[1]!.trim()
2192
- : work;
2193
- if (label && !entities.includes(label)) entities.push(label);
2194
- }
2195
-
2196
- return { kind: 'venn', entities };
2197
- }
2198
-
2199
- // ============================================================
2200
- // Quadrant extractor
2201
- // ============================================================
2202
-
2203
- const QUADRANT_POSITION_RE =
2204
- /^(top-right|top-left|bottom-right|bottom-left)\s+/i;
2205
-
2206
- function extractQuadrantSymbols(docText: string): DiagramSymbols {
2207
- const lines = docText.split('\n');
2208
- const entities: string[] = [];
2209
- let pastFirstLine = false;
2210
-
2211
- for (const line of lines) {
2212
- const trimmed = line.trim();
2213
- if (!trimmed || trimmed.startsWith('//')) continue;
2214
-
2215
- if (!pastFirstLine) {
2216
- pastFirstLine = true;
2217
- continue;
2218
- }
2219
-
2220
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
2221
- if (METADATA_KEY_SET.has(firstToken)) continue;
2222
- if (QUADRANT_POSITION_RE.test(trimmed)) continue;
2223
-
2224
- // Point name (may have coordinates: Name x,y)
2225
- const parts = trimmed.split(/\s+\d/);
2226
- const label = (parts[0] ?? '').split('|')[0]!.trim();
2227
- if (label && !entities.includes(label)) entities.push(label);
2228
- }
2229
-
2230
- return { kind: 'quadrant', entities };
2231
- }
2232
-
2233
- // ============================================================
2234
- // Slope extractor
2235
- // ============================================================
2236
-
2237
- function extractSlopeSymbols(docText: string): DiagramSymbols {
2238
- const lines = docText.split('\n');
2239
- const entities: string[] = [];
2240
- let pastFirstLine = false;
2241
-
2242
- for (const line of lines) {
2243
- const trimmed = line.trim();
2244
- if (!trimmed || trimmed.startsWith('//')) continue;
2245
-
2246
- if (!pastFirstLine) {
2247
- pastFirstLine = true;
2248
- continue;
2249
- }
2250
-
2251
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
2252
- if (METADATA_KEY_SET.has(firstToken)) continue;
2253
- if (firstToken === 'period') continue;
2254
-
2255
- // Data row: Label value1 value2 [color]
2256
- // Extract just the label (everything before first number)
2257
- const numIdx = trimmed.search(/\s\d/);
2258
- const label =
2259
- numIdx > 0
2260
- ? trimmed.slice(0, numIdx).trim()
2261
- : trimmed.split('|')[0]!.trim();
2262
- if (label && !entities.includes(label)) entities.push(label);
2263
- }
2264
-
2265
- return { kind: 'slope', entities };
2266
- }
2267
-
2268
- // ============================================================
2269
- // Generic data chart extractor
2270
- // ============================================================
2271
-
2272
- const SERIES_RE = /^series\s+(.+)$/i;
2273
- // "A -> B value" / "A -- B value" flow-link rows (chord/sankey via the shared
2274
- // extractor). Both endpoints captured; trailing weight optional.
2275
- const DATA_EDGE_RE = /^(.+?)\s+(?:->|--)\s+(.+?)(?:\s+-?\d[\d,_.]*)?\s*$/;
2276
-
2277
- function extractDataChartSymbols(docText: string): DiagramSymbols {
2278
- const lines = docText.split('\n');
2279
- const entities: string[] = [];
2280
- let chartType = 'bar';
2281
- let pastFirstLine = false;
2282
-
2283
- for (const line of lines) {
2284
- const trimmed = line.trim();
2285
- if (!trimmed || trimmed.startsWith('//')) continue;
2286
-
2287
- if (!pastFirstLine) {
2288
- pastFirstLine = true;
2289
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
2290
- if (firstToken) chartType = firstToken;
2291
- continue;
2292
- }
2293
-
2294
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
2295
- if (METADATA_KEY_SET.has(firstToken)) continue;
2296
-
2297
- // Series declarations: "series Revenue, Expenses"
2298
- const seriesMatch = trimmed.match(SERIES_RE);
2299
- if (seriesMatch) {
2300
- for (const s of seriesMatch[1]!.split(',')) {
2301
- const name = s.trim().split(/\s+/)[0]!;
2302
- if (name && !entities.includes(name)) entities.push(name);
2303
- }
2304
- continue;
2305
- }
2306
-
2307
- // Edge rows (chord / flow links): "A -> B value" / "A -- B value" — recover
2308
- // BOTH endpoints. Without this the trailing-number scan below mangles the
2309
- // whole "A -> B" into a single junk entity.
2310
- const edgeMatch = trimmed.match(DATA_EDGE_RE);
2311
- if (edgeMatch) {
2312
- const src = edgeMatch[1]!.trim();
2313
- const dst = edgeMatch[2]!.trim();
2314
- if (src && !entities.includes(src)) entities.push(src);
2315
- if (dst && !entities.includes(dst)) entities.push(dst);
2316
- continue;
2317
- }
2318
-
2319
- // Label-expression rows (function curves §15.4): "Name: expression". ONLY
2320
- // for `function` — other data charts have no colon-form and would otherwise
2321
- // truncate legitimate colon-bearing labels (`12:00 5` → `12`). Column-0
2322
- // only, so indented `key: value` metadata is never misread as an entity.
2323
- if (chartType === 'function') {
2324
- const indent = line.length - line.trimStart().length;
2325
- const colonIdx = trimmed.indexOf(':');
2326
- if (indent === 0 && colonIdx > 0) {
2327
- const beforeColon = trimmed.slice(0, colonIdx).trim();
2328
- if (beforeColon && !METADATA_KEY_SET.has(beforeColon.toLowerCase())) {
2329
- if (!entities.includes(beforeColon)) entities.push(beforeColon);
2330
- continue;
2331
- }
2332
- }
2333
- }
2334
-
2335
- // Data rows: "Label value [value...] [color]"
2336
- const numIdx = trimmed.search(/\s-?\d/);
2337
- if (numIdx > 0) {
2338
- const label = trimmed.slice(0, numIdx).trim();
2339
- if (label && !entities.includes(label)) entities.push(label);
2340
- }
2341
- }
2342
-
2343
- return { kind: chartType, entities };
2344
- }
2345
-
2346
- // ============================================================
2347
- // Wireframe extractor
2348
- // ============================================================
2349
-
2350
- // Wireframe element grammar is not a numeric-row variant, so it earns its own
2351
- // extractor: `[label]` fields, `(button)`, `{a | b}` dropdowns/selects, with
2352
- // possibly several elements per line. Returns the element labels as entities.
2353
- const WF_FIELD_RE = /\[([^\]]*)\]/g;
2354
- const WF_BUTTON_RE = /\(([^)]+)\)/g;
2355
- const WF_DROPDOWN_RE = /\{([^}]+)\}/g;
2356
-
2357
- function extractWireframeSymbols(docText: string): DiagramSymbols {
2358
- const lines = docText.split('\n');
2359
- const entities: string[] = [];
2360
- let pastFirstLine = false;
2361
-
2362
- const push = (s: string): void => {
2363
- // Strip any legacy trailing `| meta` inside a bracket; the `{a|b}` pipe is
2364
- // handled separately by the dropdown split below.
2365
- const t = s.split('|')[0]!.trim();
2366
- if (t && !entities.includes(t)) entities.push(t);
2367
- };
2368
-
2369
- for (const line of lines) {
2370
- const trimmed = line.trim();
2371
- if (!trimmed || trimmed.startsWith('//')) continue;
2372
-
2373
- if (!pastFirstLine) {
2374
- pastFirstLine = true;
2375
- continue;
2376
- }
2377
-
2378
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
2379
- if (METADATA_KEY_SET.has(firstToken)) continue;
2380
-
2381
- // `[label]` input/group fields.
2382
- for (const m of trimmed.matchAll(WF_FIELD_RE)) {
2383
- const inner = m[1]!.trim();
2384
- if (inner) push(inner);
2385
- }
2386
- // `(button)` — skip radio markers `(*)` / `( )`.
2387
- for (const m of trimmed.matchAll(WF_BUTTON_RE)) {
2388
- const inner = m[1]!.trim();
2389
- if (inner === '*' || inner === '') continue;
2390
- push(inner);
2391
- }
2392
- // `{opt1 | opt2}` dropdown/select — each option is an entity (carve-out pipe).
2393
- for (const m of trimmed.matchAll(WF_DROPDOWN_RE)) {
2394
- for (const opt of m[1]!.split('|')) {
2395
- const t = opt.trim();
2396
- if (t && !entities.includes(t)) entities.push(t);
2397
- }
2398
- }
2399
- }
2400
-
2401
- return { kind: 'wireframe', entities };
2402
- }
2403
-
2404
- // ============================================================
2405
- // Register built-in extractors
2406
- // ============================================================
2407
-
2408
- registerExtractor('er', extractErSymbols);
2409
- registerExtractor('flowchart', extractFlowchartSymbols);
2410
- registerExtractor('infra', extractInfraSymbols);
2411
- registerExtractor('class', extractClassSymbols);
2412
- registerExtractor('sequence', extractSequenceSymbols);
2413
- registerExtractor('state', extractStateSymbols);
2414
- registerExtractor('sitemap', extractSitemapSymbols);
2415
- registerExtractor('c4', extractC4Symbols);
2416
- registerExtractor('gantt', extractGanttSymbols);
2417
- registerExtractor('pert', extractPertSymbols);
2418
- registerExtractor('boxes-and-lines', extractBoxesAndLinesSymbols);
2419
- registerExtractor('tech-radar', extractTechRadarSymbols);
2420
- registerExtractor('cycle', extractCycleSymbols);
2421
- registerExtractor('journey-map', extractJourneyMapSymbols);
2422
- registerExtractor('raci', extractRaciSymbols);
2423
- registerExtractor('org', extractOrgSymbols);
2424
- registerExtractor('kanban', extractKanbanSymbols);
2425
- registerExtractor('mindmap', extractMindmapSymbols);
2426
- registerExtractor('treemap', extractTreemapSymbols);
2427
- registerExtractor('block', extractBlockSymbols);
2428
- registerExtractor('pyramid', extractPyramidSymbols);
2429
- registerExtractor('ring', extractRingSymbols);
2430
- registerExtractor('arc', extractArcSymbols);
2431
- registerExtractor('sankey', extractSankeySymbols);
2432
- registerExtractor('timeline', extractTimelineSymbols);
2433
- registerExtractor('venn', extractVennSymbols);
2434
- registerExtractor('quadrant', extractQuadrantSymbols);
2435
- registerExtractor('slope', extractSlopeSymbols);
2436
- registerExtractor('bar', extractDataChartSymbols);
2437
- registerExtractor('line', extractDataChartSymbols);
2438
- registerExtractor('pie', extractDataChartSymbols);
2439
- registerExtractor('polar-area', extractDataChartSymbols);
2440
- registerExtractor('radar', extractDataChartSymbols);
2441
- registerExtractor('scatter', extractDataChartSymbols);
2442
- registerExtractor('heatmap', extractDataChartSymbols);
2443
- registerExtractor('funnel', extractDataChartSymbols);
2444
- // `function` (`Name: expr`) and `wordcloud` (`Word weight`) had NO extractor
2445
- // registered — extractDiagramSymbols returned null for them. The generalized
2446
- // shared extractor now handles the colon-label form, so register both.
2447
- registerExtractor('function', extractDataChartSymbols);
2448
- registerExtractor('wordcloud', extractDataChartSymbols);
2449
- registerExtractor('wireframe', extractWireframeSymbols);
2450
-
2451
- function extractTechRadarSymbols(docText: string): DiagramSymbols {
2452
- const entities: string[] = [];
2453
-
2454
- // Extract ring names and aliases from the rings block
2455
- const lines = docText.split('\n');
2456
- let inRings = false;
2457
- for (const line of lines) {
2458
- const trimmed = line.trim();
2459
- if (trimmed.toLowerCase() === 'rings') {
2460
- inRings = true;
2461
- continue;
2462
- }
2463
- if (inRings) {
2464
- if (!trimmed || (line[0] !== ' ' && line[0] !== '\t')) {
2465
- inRings = false;
2466
- continue;
2467
- }
2468
- // Parse ring name (and alias)
2469
- const aliasMatch = trimmed.match(/^(.+?)\s+(?:alias|aka)\s+(\S+)\s*$/i);
2470
- if (aliasMatch) {
2471
- // Regex captured groups 1 and 2 by successful match.
2472
- entities.push(aliasMatch[1]!.trim());
2473
- entities.push(aliasMatch[2]!.trim());
2474
- } else {
2475
- entities.push(trimmed);
2476
- }
2477
- }
2478
- }
2479
-
2480
- return { kind: 'tech-radar', entities };
2481
- }
2482
-
2483
- // ============================================================
2484
- // Cycle extractor
2485
- // ============================================================
2486
-
2487
- function extractCycleSymbols(docText: string): DiagramSymbols {
2488
- const lines = docText.split('\n');
2489
- const entities: string[] = [];
2490
- let pastFirstLine = false;
2491
-
2492
- for (const line of lines) {
2493
- const trimmed = line.trim();
2494
- if (!trimmed || trimmed.startsWith('//')) continue;
2495
-
2496
- if (!pastFirstLine) {
2497
- pastFirstLine = true;
2498
- continue;
2499
- }
2500
-
2501
- // Skip directives/metadata
2502
- // split(/\s+/) on non-empty `trimmed` always yields at least one element.
2503
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
2504
- if (METADATA_KEY_SET.has(firstToken)) continue;
2505
- if (
2506
- firstToken === 'direction-counterclockwise' ||
2507
- firstToken === 'circle-nodes'
2508
- )
2509
- continue;
2510
-
2511
- // Skip indented lines (descriptions, edges)
2512
- if (line[0] === ' ' || line[0] === '\t') continue;
2513
-
2514
- // Node label (strip pipe metadata)
2515
- // split('|')[0] always defined on any string.
2516
- const label = trimmed.split('|')[0]!.trim();
2517
- if (label && !entities.includes(label)) entities.push(label);
2518
- }
2519
-
2520
- return {
2521
- kind: 'cycle',
2522
- entities,
2523
- };
2524
- }
2525
-
2526
- // ============================================================
2527
- // RACI / RASCI / DACI extractor
2528
- // ============================================================
2529
- //
2530
- // Extract role names, task names, and phase labels for editor
2531
- // autocomplete. Mirrors the lightweight per-line scan pattern used
2532
- // by other extractors (cycle / journey-map) — does NOT rebuild the
2533
- // full AST.
2534
-
2535
- const RACI_PHASE_RE = /^\[(.+)\]\s*$/;
2536
- const RACI_ROLES_DIRECTIVE_RE = /^roles\s+(.+)$/i;
2537
- const RACI_VARIANT_DIRECTIVE_RE = /^variant\s+(.+)$/i;
2538
- const RACI_ROLE_ASSIGNMENT_RE = /^([^:]+):\s*(.*)$/;
2539
-
2540
- function extractRaciSymbols(docText: string): DiagramSymbols {
2541
- const lines = docText.split('\n');
2542
- const entities: string[] = [];
2543
- let chartType = 'raci';
2544
- let pastFirstLine = false;
2545
- let underTask = false;
2546
-
2547
- const push = (s: string): void => {
2548
- const trimmed = s.trim();
2549
- if (trimmed && !entities.includes(trimmed)) entities.push(trimmed);
2550
- };
2551
-
2552
- for (const line of lines) {
2553
- const trimmed = line.trim();
2554
- if (!trimmed || trimmed.startsWith('//')) continue;
2555
-
2556
- if (!pastFirstLine) {
2557
- pastFirstLine = true;
2558
- // split(/\s+/) on non-empty `trimmed` always yields at least one element.
2559
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
2560
- if (firstToken === 'raci') {
2561
- chartType = firstToken;
2562
- }
2563
- continue;
2564
- }
2565
-
2566
- const indent = measureIndent(line);
2567
-
2568
- // Header directives
2569
- if (indent === 0) {
2570
- const rolesMatch = trimmed.match(RACI_ROLES_DIRECTIVE_RE);
2571
- if (rolesMatch) {
2572
- // Regex captured group 1 by successful match.
2573
- for (const r of rolesMatch[1]!.split(',')) push(r);
2574
- continue;
2575
- }
2576
- if (RACI_VARIANT_DIRECTIVE_RE.test(trimmed)) continue;
2577
- // split(/\s+/) on non-empty `trimmed` always yields at least one element.
2578
- if (METADATA_KEY_SET.has(trimmed.split(/\s+/)[0]!.toLowerCase()))
2579
- continue;
2580
- if (
2581
- trimmed.toLowerCase() === 'draft' ||
2582
- trimmed.toLowerCase() === 'solid-fill'
2583
- )
2584
- continue;
2585
- }
2586
-
2587
- // [Phase Label]
2588
- const phaseMatch = trimmed.match(RACI_PHASE_RE);
2589
- if (phaseMatch && indent === 0) {
2590
- // Regex captured group 1 by successful match.
2591
- push(phaseMatch[1]!);
2592
- underTask = false;
2593
- continue;
2594
- }
2595
-
2596
- // Role assignment (Role: markers) — only valid under a task
2597
- const roleMatch = trimmed.match(RACI_ROLE_ASSIGNMENT_RE);
2598
- if (underTask && roleMatch) {
2599
- // Strip a possible trailing `# annotation`
2600
- // Regex captured group 1 by successful match.
2601
- const rolePart = roleMatch[1]!.trim();
2602
- push(rolePart);
2603
- continue;
2604
- }
2605
-
2606
- // Otherwise: treat the line as a task name. `#` is NOT a comment
2607
- // character in DGMO (`//` is) — task names are used verbatim.
2608
- push(trimmed);
2609
- underTask = true;
2610
- }
2611
-
2612
- return {
2613
- kind: chartType,
2614
- entities,
2615
- };
2616
- }
2617
-
2618
- function extractJourneyMapSymbols(docText: string): DiagramSymbols {
2619
- const lines = docText.split('\n');
2620
- const entities: string[] = [];
2621
- let pastFirstLine = false;
2622
-
2623
- for (const line of lines) {
2624
- const trimmed = line.trim();
2625
- if (!trimmed || trimmed.startsWith('//')) continue;
2626
-
2627
- if (!pastFirstLine) {
2628
- pastFirstLine = true;
2629
- continue;
2630
- }
2631
-
2632
- // Skip directives/metadata at indent 0
2633
- // split(/\s+/) on non-empty `trimmed` always yields at least one element.
2634
- const firstToken = trimmed.split(/\s+/)[0]!.toLowerCase();
2635
- if (METADATA_KEY_SET.has(firstToken)) continue;
2636
- if (firstToken === 'persona' || firstToken === 'tag') continue;
2637
-
2638
- const isIndented = line[0] === ' ' || line[0] === '\t';
2639
-
2640
- // Skip deep-indented lines (annotations, descriptions under steps)
2641
- // but keep singly-indented lines (steps within phases)
2642
- if (isIndented) {
2643
- // Annotation/description keywords — skip
2644
- if (/^(pain|opportunity|thought|description)\s*:/i.test(trimmed))
2645
- continue;
2646
- // Tag group entries — skip
2647
- if (/^\S+\([^)]+\)/.test(trimmed)) continue;
2648
- }
2649
-
2650
- // Phase header
2651
- const phaseMatch = trimmed.match(/^\[(.+?)\]$/);
2652
- if (phaseMatch) {
2653
- // Regex captured group 1 by successful match.
2654
- entities.push(phaseMatch[1]!.trim());
2655
- continue;
2656
- }
2657
-
2658
- // Step label (strip pipe metadata) — works for both indent 0 and indented steps
2659
- // split('|')[0] always defined on any string.
2660
- const label = trimmed.split('|')[0]!.trim();
2661
- if (label && !entities.includes(label)) entities.push(label);
2662
- }
2663
-
2664
- return {
2665
- kind: 'journey-map',
2666
- entities,
2667
- };
2668
- }