@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
@@ -1,1596 +0,0 @@
1
- // ============================================================
2
- // Sequence Diagram Parser (.dgmo format)
3
- // ============================================================
4
-
5
- import { inferParticipantType } from './participant-inference';
6
- import type { Brand, Writable } from '../utils/brand';
7
- import type { DgmoError } from '../diagnostics';
8
- import type { PaletteColors } from '../palettes/types';
9
- import {
10
- formatDgmoError,
11
- makeDgmoError,
12
- makeFail,
13
- NAME_DIAGNOSTIC_CODES,
14
- nameMergedMessage,
15
- suggest,
16
- } from '../diagnostics';
17
- import { normalizeName, displayName } from '../utils/name-normalize';
18
- import { parseArrow, parseInArrowLabel } from '../utils/arrows';
19
- import {
20
- measureIndent,
21
- extractColor,
22
- splitNameAndMeta,
23
- parseFirstLine,
24
- OPTION_NOCOLON_RE,
25
- warnUnknownMetaKeys,
26
- } from '../utils/parsing';
27
- import {
28
- SEQUENCE_REGISTRY,
29
- withTagAliases,
30
- } from '../utils/reserved-key-registry';
31
- import type { TagGroup } from '../utils/tag-groups';
32
- import {
33
- matchTagBlockHeading,
34
- validateTagValues,
35
- validateTagGroupNames,
36
- stripDefaultModifier,
37
- finalizeAutoTagColors,
38
- AUTO_TAG_COLOR_SENTINEL,
39
- } from '../utils/tag-groups';
40
-
41
- /** Known sequence-diagram options that take a value (space-separated). */
42
- const KNOWN_SEQ_OPTIONS = new Set(['active-tag']);
43
-
44
- /** Known sequence-diagram boolean options (bare keyword or `no-` prefix). */
45
- const KNOWN_SEQ_BOOLEANS = new Set(['solid-fill', 'no-title']);
46
-
47
- /** Boolean options that only support the `no-` prefix form (no bare or key-value). */
48
- const NO_PREFIX_ONLY_BOOLEANS = new Set(['activations']);
49
-
50
- /**
51
- * Participant types that can be declared via "Name is a type" syntax.
52
- *
53
- * The 0.16.0 trim retained only the types whose shapes carry semantic
54
- * weight at a glance: stick figure (actor), cylinder (database),
55
- * dashed cylinder (cache), horizontal pipe (queue), plus the default
56
- * rectangle. Any other type word falls back to `default`.
57
- */
58
- export type ParticipantType =
59
- | 'default'
60
- | 'database'
61
- | 'actor'
62
- | 'queue'
63
- | 'cache';
64
-
65
- const VALID_PARTICIPANT_TYPES: ReadonlySet<string> = new Set([
66
- 'database',
67
- 'actor',
68
- 'queue',
69
- 'cache',
70
- ]);
71
-
72
- /**
73
- * Branded participant identifier — a normalized name string that has
74
- * been minted through `addParticipant` and registered in the parser's
75
- * `participantMap`. Distinct from a raw display label or any other
76
- * `string`, so the type system catches "passed label where id expected"
77
- * at compile time.
78
- */
79
- export type ParticipantId = Brand<string, 'ParticipantId'>;
80
-
81
- /**
82
- * A declared or inferred participant in the sequence diagram.
83
- */
84
- export interface SequenceParticipant {
85
- /** Internal identifier (e.g. "AuthService") */
86
- readonly id: ParticipantId;
87
- /** Display label — first-seen casing/spacing of the name */
88
- readonly label: string;
89
- /** Participant shape type */
90
- readonly type: ParticipantType;
91
- /** Source line number (1-based) */
92
- readonly lineNumber: number;
93
- /** Explicit layout position override (0-based from left, negative from right) */
94
- readonly position?: number;
95
- /** Pipe-delimited tag metadata (e.g. `| role: Gateway`) */
96
- readonly metadata?: Readonly<Record<string, string>>;
97
- }
98
-
99
- /**
100
- * A message between two participants.
101
- *
102
- * `kind: 'message'` is the discriminator for the SequenceElement union.
103
- * Pre-1.0 type addition — Epic 105 Story 105.17.
104
- */
105
- export interface SequenceMessage {
106
- readonly kind: 'message';
107
- readonly from: ParticipantId;
108
- readonly to: ParticipantId;
109
- readonly label: string;
110
- readonly lineNumber: number;
111
- readonly async?: boolean;
112
- /** Pipe-delimited tag metadata (e.g. `| c: Caching`) */
113
- readonly metadata?: Readonly<Record<string, string>>;
114
- }
115
-
116
- /**
117
- * A conditional or loop block in the sequence diagram.
118
- */
119
- export interface ElseIfBranch {
120
- readonly label: string;
121
- readonly children: readonly SequenceElement[];
122
- readonly lineNumber: number;
123
- }
124
-
125
- export interface SequenceBlock {
126
- readonly kind: 'block';
127
- readonly type: 'if' | 'loop' | 'parallel';
128
- readonly label: string;
129
- readonly children: readonly SequenceElement[];
130
- readonly elseChildren: readonly SequenceElement[];
131
- readonly elseIfBranches?: readonly ElseIfBranch[];
132
- readonly elseLineNumber?: number;
133
- readonly lineNumber: number;
134
- }
135
-
136
- /**
137
- * A labeled horizontal divider between message phases.
138
- */
139
- export interface SequenceSection {
140
- readonly kind: 'section';
141
- readonly label: string;
142
- readonly lineNumber: number;
143
- }
144
-
145
- /**
146
- * An annotation attached to a message, rendered as a folded-corner box.
147
- */
148
- export interface SequenceNote {
149
- readonly kind: 'note';
150
- readonly text: string;
151
- readonly position: 'right' | 'left';
152
- readonly participantId: ParticipantId;
153
- readonly lineNumber: number;
154
- readonly endLineNumber: number;
155
- }
156
-
157
- export type SequenceElement =
158
- | SequenceMessage
159
- | SequenceBlock
160
- | SequenceSection
161
- | SequenceNote;
162
-
163
- export function isSequenceBlock(el: SequenceElement): el is SequenceBlock {
164
- return el.kind === 'block';
165
- }
166
-
167
- export function isSequenceSection(el: SequenceElement): el is SequenceSection {
168
- return el.kind === 'section';
169
- }
170
-
171
- export function isSequenceNote(el: SequenceElement): el is SequenceNote {
172
- return el.kind === 'note';
173
- }
174
-
175
- /**
176
- * A named group of participants rendered as a labeled box.
177
- */
178
- export interface SequenceGroup {
179
- readonly name: string;
180
- readonly participantIds: readonly ParticipantId[];
181
- readonly lineNumber: number;
182
- /** Pipe-delimited tag metadata (e.g. `[Backend | t: Product]`) */
183
- readonly metadata?: Readonly<Record<string, string>>;
184
- /** Whether this group is collapsed by default */
185
- readonly collapsed?: boolean;
186
- }
187
-
188
- /**
189
- * Parsed result from a .dgmo sequence diagram.
190
- */
191
- export interface ParsedSequenceDgmo {
192
- readonly title: string | null;
193
- readonly titleLineNumber: number | null;
194
- readonly participants: readonly SequenceParticipant[];
195
- readonly messages: readonly SequenceMessage[];
196
- readonly elements: readonly SequenceElement[];
197
- readonly groups: readonly SequenceGroup[];
198
- readonly sections: readonly SequenceSection[];
199
- readonly tagGroups: readonly TagGroup[];
200
- readonly options: Readonly<Record<string, string>>;
201
- readonly diagnostics: readonly DgmoError[];
202
- readonly error: string | null;
203
- }
204
-
205
- // "Name is a type" pattern — e.g. "Auth Server is a database"
206
- // Participant names may contain spaces; [^:]+? stops at colons so that
207
- // note lines like "note right of A: this is an actor" are not falsely matched.
208
- // Remainder after type carries the optional `as <alias>` modifier.
209
- const IS_A_PATTERN = /^([^:]+?)\s+is\s+an?\s+(\w+)(?:\s+(.+))?$/i;
210
-
211
- // Colored participant declaration — e.g. "Tapin2(green)", "API(blue)"
212
- // Scoped to recognized 11-name palette colors only (§1.5) so legitimate
213
- // `funcCall(arg)` lines don't trigger the legacy-color diagnostic.
214
- const COLORED_PARTICIPANT_PATTERN =
215
- /^(\S+?)\((red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white)\)\s*$/;
216
-
217
- // Group heading pattern — "[Backend]", "[Backend] | t: Product"
218
- // Group 1: name (no ] or | inside brackets), Group 2: color in parens, Group 3: after-bracket text
219
- const GROUP_HEADING_PATTERN = /^\[([^\]|]+?)(?:\(([^)]+)\))?\]\s*(.*)$/;
220
- // Fallback: allows anything inside brackets (used to detect pipe-inside-brackets error)
221
- const GROUP_HEADING_FALLBACK = /^\[([^\]]+)\]\s*(.*)$/;
222
-
223
- // Section divider pattern — "== Label ==", "== Label(color) ==", or "== Label" (trailing == optional)
224
- const SECTION_PATTERN = /^==\s+(.+?)(?:\s*==)?\s*$/;
225
-
226
- // Arrow pattern for sequence inference — detects any arrow form
227
- const ARROW_PATTERN = /\S+\s*(?:<-\S+-|<~\S+~|-\S+->|~\S+~>|->|~>|<-|<~)\s*\S+/;
228
-
229
- // Note patterns — colon-free syntax only
230
- // Single-line: "note text", "note left text", "note right of X text", "note left X text"
231
- // Multi-line: "note", "note right", "note right of X", "note left X" (body indented below)
232
- //
233
- // The colon-free positioned form requires participant resolution — the parser
234
- // already has participant collection infrastructure, so we match the general
235
- // structure here and resolve participant vs text in the parsing logic.
236
- const NOTE_BARE = /^note\s+(.+)$/i;
237
- const NOTE_MULTI = /^note(?:\s+(right|left)(?:\s+(?:of\s+)?(.+?))?)?\s*$/i;
238
-
239
- /** Result of parseNoteLine — indicates what the parser should do. */
240
- type NoteParseResult =
241
- | {
242
- kind: 'single';
243
- position: 'right' | 'left';
244
- participantId: ParticipantId;
245
- text: string;
246
- }
247
- | {
248
- kind: 'multi-head';
249
- position: 'right' | 'left';
250
- participantId: ParticipantId;
251
- }
252
- | { kind: 'skip' }
253
- | null; // not a note line at all
254
-
255
- /**
256
- * Parse a note line, resolving participant names from the known participants list.
257
- *
258
- * Supports:
259
- * - `note text` — default position (right), last msg sender
260
- * - `note left of X text` / `note left X text`
261
- * - `note right` — multi-line head
262
- * - `note right of X` / `note left X` — multi-line head
263
- * - Quoted participant: `note left "Auth Service" text`
264
- */
265
- function parseNoteLine(
266
- trimmed: string,
267
- participants: SequenceParticipant[],
268
- participantIds: Set<string>,
269
- sortedParticipantsCache: SequenceParticipant[],
270
- lastMsgFrom: string | null
271
- ): NoteParseResult {
272
- const lower = trimmed.toLowerCase();
273
- if (!lower.startsWith('note')) return null;
274
- // Must be exactly "note" or "note " — not "notebook" etc.
275
- if (trimmed.length > 4 && trimmed[4] !== ' ') return null;
276
-
277
- // 1. Try multi-line head (no text after note): `note`, `note right`, `note right of X`, `note left X`
278
- // NOTE: NOTE_MULTI's (.+?) can greedily capture "participant text" as one group.
279
- // Only trust this match if the captured participant actually exists. Otherwise,
280
- // fall through to the bare-note handler which does proper participant-aware splitting.
281
- const multiMatch = trimmed.match(NOTE_MULTI);
282
- if (multiMatch) {
283
- const position =
284
- (multiMatch[1]?.toLowerCase() as 'right' | 'left') || 'right';
285
- let participantId = multiMatch[2] || null;
286
- if (!participantId) {
287
- if (!lastMsgFrom) return { kind: 'skip' };
288
- participantId = lastMsgFrom;
289
- }
290
- const lookupKey = normalizeName(participantId);
291
- if (participantIds.has(lookupKey)) {
292
- // Resolve to first-seen display id so the renderer can find it
293
- const found = participants.find((p) => normalizeName(p.id) === lookupKey);
294
- return {
295
- kind: 'multi-head',
296
- position,
297
- // Verified participant — `participantIds.has(lookupKey)` confirmed.
298
- participantId: found?.id ?? (participantId as ParticipantId),
299
- };
300
- }
301
- // Participant not found — fall through to bare-note handler for proper resolution
302
- }
303
-
304
- // 2. Bare note: `note text` or `note left [of] X text`
305
- const bareMatch = trimmed.match(NOTE_BARE);
306
- if (bareMatch) {
307
- // Capture group 1 guaranteed present after successful match.
308
- const rest = bareMatch[1]!.trim();
309
- const restLower = rest.toLowerCase();
310
-
311
- // Check for positioned note: `note left/right ...`
312
- if (restLower.startsWith('left') || restLower.startsWith('right')) {
313
- const posWord = restLower.startsWith('left') ? 'left' : 'right';
314
- const position = posWord as 'right' | 'left';
315
- let afterPos = rest.substring(posWord.length).trim();
316
-
317
- // Strip optional `of` keyword — track whether it was present
318
- let hadOf = false;
319
- if (afterPos.toLowerCase().startsWith('of ')) {
320
- afterPos = afterPos.substring(3).trim();
321
- hadOf = true;
322
- }
323
-
324
- if (!afterPos) {
325
- // Just `note left` or `note right` — multi-line head
326
- if (!lastMsgFrom) return { kind: 'skip' };
327
- if (!participantIds.has(normalizeName(lastMsgFrom)))
328
- return { kind: 'skip' };
329
- // Verified — participantIds.has confirmed lastMsgFrom is registered.
330
- return {
331
- kind: 'multi-head',
332
- position,
333
- participantId: lastMsgFrom as ParticipantId,
334
- };
335
- }
336
-
337
- // Try to match a known participant at the start of afterPos
338
- const resolved = resolveParticipantAndText(
339
- afterPos,
340
- participants,
341
- participantIds,
342
- sortedParticipantsCache
343
- );
344
- if (resolved) {
345
- if (resolved.text) {
346
- return {
347
- kind: 'single',
348
- position,
349
- participantId: resolved.participantId,
350
- text: resolved.text,
351
- };
352
- } else {
353
- // No text after participant — multi-line head
354
- return {
355
- kind: 'multi-head',
356
- position,
357
- participantId: resolved.participantId,
358
- };
359
- }
360
- }
361
-
362
- // No known participant matched.
363
- // If `of` was explicit (`note right of Z ...`), the user intended a specific
364
- // participant — skip when it doesn't exist rather than defaulting.
365
- if (hadOf) return { kind: 'skip' };
366
-
367
- // Without `of`, treat remaining text as note content on the last-msg sender
368
- if (!lastMsgFrom) return { kind: 'skip' };
369
- if (!participantIds.has(normalizeName(lastMsgFrom)))
370
- return { kind: 'skip' };
371
- return {
372
- kind: 'single',
373
- position,
374
- // Verified by participantIds.has check above.
375
- participantId: lastMsgFrom as ParticipantId,
376
- text: afterPos,
377
- };
378
- }
379
-
380
- // Plain `note text` — default position, last msg sender
381
- if (!lastMsgFrom) return { kind: 'skip' };
382
- if (!participantIds.has(normalizeName(lastMsgFrom)))
383
- return { kind: 'skip' };
384
- return {
385
- kind: 'single',
386
- position: 'right',
387
- // Verified by participantIds.has check above.
388
- participantId: lastMsgFrom as ParticipantId,
389
- text: rest,
390
- };
391
- }
392
-
393
- return null;
394
- }
395
-
396
- /**
397
- * Try to match a known participant name at the start of a string.
398
- * Returns the matched participant and remaining text, or null if no match.
399
- * Tries longest match first (multi-word participant names).
400
- */
401
- function resolveParticipantAndText(
402
- input: string,
403
- participants: SequenceParticipant[],
404
- participantIds: Set<string>,
405
- sortedParticipantsCache: SequenceParticipant[]
406
- ): { participantId: ParticipantId; text: string } | null {
407
- // Handle quoted participant: `"Auth Service" text`
408
- if (input.startsWith('"') || input.startsWith("'")) {
409
- // input[0] guaranteed by the startsWith check above.
410
- const quote = input[0]!;
411
- const endQuote = input.indexOf(quote, 1);
412
- if (endQuote > 0) {
413
- const name = input.substring(1, endQuote);
414
- const key = normalizeName(name);
415
- if (participantIds.has(key)) {
416
- const text = input.substring(endQuote + 1).trim();
417
- const found = participants.find((p) => normalizeName(p.id) === key);
418
- // Verified by participantIds.has(key) above.
419
- return { participantId: found?.id ?? (name as ParticipantId), text };
420
- }
421
- }
422
- return null;
423
- }
424
-
425
- // Use pre-sorted participants (longest display id first) for greedy
426
- // matching. Compare via the shared normalizer so 'auth service text' matches
427
- // a participant declared as 'Auth Service'.
428
- const sorted = sortedParticipantsCache;
429
- for (const p of sorted) {
430
- const idLen = p.id.length;
431
- if (input.length < idLen) continue;
432
- const candidate = input.substring(0, idLen);
433
- if (normalizeName(candidate) !== normalizeName(p.id)) continue;
434
- const remaining = input.substring(idLen);
435
- // Must be followed by whitespace, end of string, or nothing
436
- if (remaining === '' || remaining[0] === ' ' || remaining[0] === '\t') {
437
- return { participantId: p.id, text: remaining.trim() };
438
- }
439
- }
440
- return null;
441
- }
442
-
443
- /**
444
- * Parse a .dgmo file with `chart: sequence` into a structured representation.
445
- */
446
- export function parseSequenceDgmo(
447
- content: string,
448
- palette?: PaletteColors
449
- ): ParsedSequenceDgmo {
450
- // Diagram-level options accumulator (Readonly<Record<...>> on the public
451
- // type; mutated locally during parse and assigned back via `result.options`).
452
- const optionsAccumulator: Record<string, string> = {};
453
- const result: Writable<ParsedSequenceDgmo> & {
454
- options: Record<string, string>;
455
- } = {
456
- title: null,
457
- titleLineNumber: null,
458
- participants: [],
459
- messages: [],
460
- elements: [],
461
- groups: [],
462
- sections: [],
463
- tagGroups: [],
464
- options: optionsAccumulator,
465
- diagnostics: [],
466
- error: null,
467
- };
468
-
469
- // Per-parse entity alias literal → canonical participantId map
470
- // (TD-18). Distinct from `aliasMap` further down, which is for
471
- // tag-group aliases (per the A1 naming convention). Per C8:
472
- // never persisted, fresh each parse.
473
- const nameAliasMap = new Map<string, string>();
474
- const resolveAlias = (token: string): string => {
475
- const trimmed = token.trim();
476
- return nameAliasMap.get(trimmed) ?? trimmed;
477
- };
478
-
479
- const fail = makeFail(result);
480
-
481
- /** Push a recoverable error and continue parsing. */
482
- const pushError = (line: number, message: string): void => {
483
- const diag = makeDgmoError(line, message);
484
- result.diagnostics.push(diag);
485
- if (!result.error) result.error = formatDgmoError(diag);
486
- };
487
-
488
- /** Push a non-fatal warning (does not set result.error). */
489
- const pushWarning = (line: number, message: string): void => {
490
- result.diagnostics.push(makeDgmoError(line, message, 'warning'));
491
- };
492
-
493
- if (!content?.trim()) {
494
- return fail(0, 'Empty content');
495
- }
496
-
497
- const lines = content.split('\n');
498
- let hasExplicitChart = false;
499
- let contentStarted = false;
500
- // Whether the message body has begun (first message, section, block, or note).
501
- // Unlike `contentStarted` — which any declaration trips to close the
502
- // options/tag-group "headers first" window — `bodyStarted` stays false through
503
- // the entire declaration preamble so bare and typed participant declarations
504
- // can be freely interleaved. It gates bare-name declarations: once real body
505
- // content appears, a bare word is treated as a stray line, not a participant.
506
- let bodyStarted = false;
507
- let firstLineIndex = -1; // line index of the `sequence [Title]` first line (to skip in main loop)
508
-
509
- // Handle first non-empty, non-comment line for `sequence Title` syntax
510
- for (let fi = 0; fi < lines.length; fi++) {
511
- // In-bounds by loop guard.
512
- const fl = lines[fi]!.trim();
513
- if (!fl || fl.startsWith('//')) continue;
514
- const parsed = parseFirstLine(fl);
515
- if (parsed?.chartType === 'sequence') {
516
- hasExplicitChart = true;
517
- firstLineIndex = fi;
518
- if (parsed.title) {
519
- result.title = parsed.title;
520
- result.titleLineNumber = fi + 1;
521
- }
522
- }
523
- break;
524
- }
525
-
526
- // Group parsing state — tracks the active [Group] heading.
527
- // Mutated during parse (participantIds.push); typed Writable so the
528
- // local accumulator can grow before the readonly-typed value is exposed.
529
- let activeGroup: Writable<SequenceGroup> | null = null;
530
-
531
- // Fast lookup set for participant existence checks (mirrors result.participants).
532
- // Holds NORMALIZED participant keys so 'Auth Service' and 'auth service' fold
533
- // to the same entry. Display label survives in result.participants[i].label.
534
- const participantIds = new Set<string>();
535
-
536
- // Map<normalizedKey, first-seen participant.id> — the canonical
537
- // identifier callers store on messages, group memberships, etc.
538
- const idByKey = new Map<string, string>();
539
-
540
- /**
541
- * Get-or-create a participant by normalized key. Returns the FIRST-SEEN
542
- * `participant.id` — which is the user's original casing/spacing.
543
- * Callers should use the returned value on messages, group memberships,
544
- * notes, etc. so all references resolve to the canonical id.
545
- *
546
- * Emits NAME_MERGED warning when an incoming source label normalizes to
547
- * an existing key but its display form differs (case/whitespace).
548
- */
549
- const addParticipant = (
550
- name: string,
551
- lineNumber: number,
552
- extras?: {
553
- type?: ParticipantType;
554
- position?: number;
555
- label?: string;
556
- metadata?: Record<string, string>;
557
- }
558
- ): ParticipantId => {
559
- const key = normalizeName(name);
560
- const trimmed = name.trim() as ParticipantId;
561
- const incomingLabel = extras?.label ?? trimmed;
562
- if (participantIds.has(key)) {
563
- const existing = result.participants.find(
564
- (p) => normalizeName(p.id) === key
565
- );
566
- if (existing) {
567
- if (
568
- displayName(existing.label) !== displayName(incomingLabel) ||
569
- displayName(existing.id) !== displayName(trimmed)
570
- ) {
571
- result.diagnostics.push(
572
- makeDgmoError(
573
- lineNumber,
574
- nameMergedMessage({
575
- incomingDisplay: trimmed,
576
- incomingLine: lineNumber,
577
- existingDisplay: existing.id,
578
- existingLine: existing.lineNumber,
579
- }),
580
- 'warning',
581
- NAME_DIAGNOSTIC_CODES.NAME_MERGED
582
- )
583
- );
584
- }
585
- return existing.id;
586
- }
587
- // Fallback: idByKey lookup hit but participant somehow missing.
588
- return (idByKey.get(key) ?? trimmed) as ParticipantId;
589
- }
590
- participantIds.add(key);
591
-
592
- idByKey.set(key, trimmed);
593
- sortedCacheDirty = true;
594
- result.participants.push({
595
- id: trimmed,
596
- label: incomingLabel,
597
- type: extras?.type ?? inferParticipantType(name),
598
- lineNumber,
599
- ...(extras?.position !== undefined ? { position: extras.position } : {}),
600
- // takePosition() may have emptied the metadata record (position was
601
- // its only key) — don't attach an empty object.
602
- ...(extras?.metadata && Object.keys(extras.metadata).length > 0
603
- ? { metadata: extras.metadata }
604
- : {}),
605
- });
606
- return trimmed;
607
- };
608
-
609
- // Cache sorted participants (longest ID first) for greedy name matching in notes.
610
- // Invalidated whenever a new participant is added.
611
- let sortedParticipantsCache: SequenceParticipant[] = [];
612
- let sortedCacheDirty = true;
613
-
614
- /** Get sorted participants (longest display label first), rebuilding only when dirty. */
615
- const getSortedParticipants = (): SequenceParticipant[] => {
616
- if (sortedCacheDirty) {
617
- sortedParticipantsCache = [...result.participants].sort(
618
- (a, b) => b.label.length - a.label.length
619
- );
620
- sortedCacheDirty = false;
621
- }
622
- return sortedParticipantsCache;
623
- };
624
-
625
- // Track participant → group name for duplicate membership detection
626
- const participantGroupMap = new Map<string, string>();
627
-
628
- // Tag group parsing state
629
- let currentTagGroup: Writable<TagGroup> | null = null;
630
- const aliasMap = new Map<string, string>();
631
-
632
- /**
633
- * Split same-line metadata from a line: "core k: v" (§1.4)
634
- * → { core, meta }. Dispatched via splitNameAndMeta + SEQUENCE_REGISTRY.
635
- */
636
- const splitPipe = (
637
- text: string,
638
- ln?: number
639
- ): { core: string; meta?: Record<string, string>; alias?: string } => {
640
- // §1.4 unified metadata grammar — same-line cut, no pipe.
641
- const registry = withTagAliases(
642
- SEQUENCE_REGISTRY,
643
- new Set(aliasMap.keys())
644
- );
645
- const split = splitNameAndMeta(text, registry, aliasMap);
646
- if (ln != null) {
647
- warnUnknownMetaKeys(
648
- split.meta,
649
- registry,
650
- (msg) => pushWarning(ln, msg),
651
- split.name
652
- );
653
- }
654
- if (Object.keys(split.meta).length > 0) {
655
- // splitNameAndMeta peels a trailing `as <alias>` off the name region
656
- // when same-line metadata is present (the alias must precede the meta,
657
- // which runs to EOL). Propagate it so callers can register the alias —
658
- // the no-meta branch below leaves `as <alias>` in `core` for the
659
- // caller's own peeling.
660
- return {
661
- core: split.name,
662
- meta: split.meta,
663
- ...(split.alias !== undefined && { alias: split.alias }),
664
- };
665
- }
666
- return { core: text };
667
- };
668
-
669
- /**
670
- * Pull the layout-order `position:` key out of a participant's
671
- * parsed metadata (§2.2). Position is a layout directive, not display
672
- * metadata, so it is *removed* from the record after extraction. A
673
- * non-integer value raises an error and yields no position.
674
- */
675
- const takePosition = (
676
- meta: Record<string, string> | undefined,
677
- ln: number
678
- ): number | undefined => {
679
- if (meta?.['position'] === undefined) return undefined;
680
- const raw = meta['position'];
681
- delete meta['position'];
682
- const n = parseInt(raw, 10);
683
- if (!/^-?\d+$/.test(raw.trim()) || Number.isNaN(n)) {
684
- pushError(ln, `'position: ${raw}' must be an integer slot index`);
685
- return undefined;
686
- }
687
- return n;
688
- };
689
-
690
- // Block parsing state — blocks are built mutably during parse, then
691
- // assigned back into the readonly-typed `ParsedSequenceDgmo`.
692
- const blockStack: {
693
- block: Writable<SequenceBlock>;
694
- indent: number;
695
- inElse: boolean;
696
- activeElseIfBranch?: Writable<ElseIfBranch>;
697
- }[] = [];
698
- const currentContainer = (): SequenceElement[] => {
699
- if (blockStack.length === 0) return result.elements;
700
- // In-bounds by the length check above.
701
- const top = blockStack[blockStack.length - 1]!;
702
- if (top.activeElseIfBranch) return top.activeElseIfBranch.children;
703
- return top.inElse ? top.block.elseChildren : top.block.children;
704
- };
705
-
706
- // Track last message sender for default note positioning
707
- let lastMsgFrom: string | null = null;
708
-
709
- for (let i = 0; i < lines.length; i++) {
710
- // In-bounds by loop guard.
711
- const raw = lines[i]!;
712
- const trimmed = raw.trim();
713
- const lineNumber = i + 1;
714
-
715
- // Skip empty lines
716
- if (!trimmed) {
717
- activeGroup = null;
718
- currentTagGroup = null;
719
- continue;
720
- }
721
-
722
- // Skip first line already handled as `sequence [Title]`
723
- if (i === firstLineIndex) continue;
724
-
725
- // Parse group heading — [Group Name] or [Group Name] | k: v
726
- const groupMatch = trimmed.match(GROUP_HEADING_PATTERN);
727
- if (groupMatch) {
728
- // Capture group 1 guaranteed present after successful match.
729
- const groupName = groupMatch[1]!.trim();
730
- const groupColor = groupMatch[2]?.trim();
731
- let groupMeta: Record<string, string> | undefined;
732
-
733
- // Parse metadata and collapse state AFTER the closing bracket
734
- const afterBracket = groupMatch[3]?.trim() || '';
735
- let isCollapsed = false;
736
-
737
- // §1.4 same-line metadata on group header
738
- if (afterBracket) {
739
- const groupRegistry = withTagAliases(
740
- SEQUENCE_REGISTRY,
741
- new Set(aliasMap.keys())
742
- );
743
- const split = splitNameAndMeta(afterBracket, groupRegistry, aliasMap);
744
- warnUnknownMetaKeys(
745
- split.meta,
746
- groupRegistry,
747
- (msg) => pushWarning(lineNumber, msg),
748
- split.name
749
- );
750
- if (Object.keys(split.meta).length > 0) groupMeta = split.meta;
751
- if (groupMeta?.['collapsed']?.toLowerCase() === 'true') {
752
- isCollapsed = true;
753
- delete groupMeta['collapsed'];
754
- if (Object.keys(groupMeta).length === 0) groupMeta = undefined;
755
- }
756
- }
757
-
758
- if (groupColor) {
759
- pushWarning(
760
- lineNumber,
761
- `'(${groupColor})' parens-color syntax removed from sequence diagrams — use 'tag:' groups for coloring`
762
- );
763
- }
764
- contentStarted = true;
765
- activeGroup = {
766
- name: groupName,
767
- participantIds: [],
768
- lineNumber,
769
- ...(groupMeta ? { metadata: groupMeta } : {}),
770
- ...(isCollapsed ? { collapsed: true } : {}),
771
- };
772
- result.groups.push(activeGroup);
773
- continue;
774
- }
775
-
776
- // Detect pipe-inside-brackets error: [Name | meta] → suggest [Name] | meta
777
- if (trimmed.startsWith('[')) {
778
- const fallbackMatch = trimmed.match(GROUP_HEADING_FALLBACK);
779
- // Capture group 1 guaranteed present after successful match.
780
- if (fallbackMatch && fallbackMatch[1]!.includes('|')) {
781
- const rawInside = fallbackMatch[1]!;
782
- const pipeIdx = rawInside.indexOf('|');
783
- const cleanName = rawInside
784
- .substring(0, pipeIdx)
785
- .trim()
786
- .replace(/\([^)]*\)$/, '')
787
- .trim();
788
- const metaPart = rawInside
789
- .substring(pipeIdx)
790
- .replace(/^\|\s*/, '')
791
- .trim();
792
- pushError(
793
- lineNumber,
794
- `Metadata goes after the bracket with no pipe — use '[${cleanName}] ${metaPart}' (e.g. '[Backend] t: Product'), not '[${rawInside.trim()}]'`
795
- );
796
- continue;
797
- }
798
- }
799
-
800
- // Close active group on non-indented, non-group lines
801
- if (activeGroup && measureIndent(raw) === 0) {
802
- activeGroup = null;
803
- }
804
-
805
- // Skip comments — only // is supported
806
- if (trimmed.startsWith('//')) continue;
807
-
808
- // Reject # as comment syntax
809
- if (trimmed.startsWith('#')) {
810
- pushError(lineNumber, 'Use // for comments');
811
- continue;
812
- }
813
-
814
- // ---- Tag group handling ----
815
- // Tag block heading: "tag Name as <alias>"
816
- const tagBlockMatch = matchTagBlockHeading(trimmed);
817
- if (tagBlockMatch) {
818
- if (contentStarted) {
819
- pushError(lineNumber, 'Tag groups must appear before sequence content');
820
- continue;
821
- }
822
- const newTagGroup: Writable<TagGroup> = {
823
- name: tagBlockMatch.name,
824
- ...(tagBlockMatch.alias !== undefined && {
825
- alias: tagBlockMatch.alias,
826
- }),
827
- entries: [],
828
- lineNumber,
829
- };
830
- currentTagGroup = newTagGroup;
831
- if (tagBlockMatch.alias) {
832
- aliasMap.set(
833
- tagBlockMatch.alias.toLowerCase(),
834
- tagBlockMatch.name.toLowerCase()
835
- );
836
- }
837
- aliasMap.set(
838
- normalizeName(tagBlockMatch.name),
839
- tagBlockMatch.name.toLowerCase()
840
- );
841
- result.tagGroups.push(newTagGroup);
842
- continue;
843
- }
844
-
845
- // Tag group entries (indented Value color under tag heading)
846
- // First entry is the default unless another is marked `default`
847
- if (currentTagGroup && !contentStarted && measureIndent(raw) > 0) {
848
- const { text: cleanEntry, isDefault } = stripDefaultModifier(trimmed);
849
- const { label, color } = extractColor(
850
- cleanEntry,
851
- palette,
852
- result.diagnostics,
853
- lineNumber
854
- );
855
- // Bare value (no explicit color) → keep it; finalized below.
856
- if (isDefault) {
857
- currentTagGroup.defaultValue = label;
858
- } else if (currentTagGroup.entries.length === 0) {
859
- currentTagGroup.defaultValue = label;
860
- }
861
- currentTagGroup.entries.push({
862
- value: label,
863
- color: color ?? AUTO_TAG_COLOR_SENTINEL,
864
- lineNumber,
865
- });
866
- continue;
867
- }
868
-
869
- // Non-indented line after tag group — close it
870
- if (currentTagGroup) {
871
- currentTagGroup = null;
872
- }
873
-
874
- // Parse section dividers — "== Label ==" or "== Label(color) =="
875
- // Close blocks first — sections at indent 0 should not nest inside blocks
876
- const sectionMatch = trimmed.match(SECTION_PATTERN);
877
- if (sectionMatch) {
878
- const sectionIndent = measureIndent(raw);
879
- while (blockStack.length > 0) {
880
- // In-bounds by the length check above.
881
- const top = blockStack[blockStack.length - 1]!;
882
- if (sectionIndent > top.indent) break;
883
- blockStack.pop();
884
- }
885
- // Capture group 1 guaranteed present after successful match.
886
- const labelRaw = sectionMatch[1]!.trim();
887
- // Scoped to recognized 11-name palette colors only (§1.5).
888
- const colorMatch = labelRaw.match(
889
- /^(.+?)\((red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white)\)$/
890
- );
891
- if (colorMatch) {
892
- pushWarning(
893
- lineNumber,
894
- `'(${colorMatch[2]})' parens-color syntax removed from sequence diagrams — use 'tag:' groups for coloring`
895
- );
896
- }
897
- contentStarted = true;
898
- bodyStarted = true;
899
- const section: SequenceSection = {
900
- kind: 'section',
901
- // Capture group 1 guaranteed present after successful match.
902
- label: colorMatch ? colorMatch[1]!.trim() : labelRaw,
903
- lineNumber,
904
- };
905
- result.sections.push(section);
906
- currentContainer().push(section);
907
- continue;
908
- }
909
-
910
- // Parse header key: value lines (always top-level)
911
- // Skip 'note' lines — parsed in the indent-aware section below
912
- const colonIndex = trimmed.indexOf(':');
913
- // §1.4: a real header key is a single identifier. Participant
914
- // lines with same-line metadata (`CheckoutSvc o: Checkout`) have
915
- // a space inside the pre-colon region — those are content lines,
916
- // not header options.
917
- const preColon =
918
- colonIndex > 0 ? trimmed.substring(0, colonIndex).trim() : '';
919
- const preColonIsSingleIdent = /^[A-Za-z][\w-]*$/.test(preColon);
920
- if (
921
- colonIndex > 0 &&
922
- preColonIsSingleIdent &&
923
- !trimmed.includes('->') &&
924
- !trimmed.includes('~>') &&
925
- !trimmed.includes('<-') &&
926
- !trimmed.includes('<~') &&
927
- !trimmed.includes('|')
928
- ) {
929
- const key = preColon.toLowerCase();
930
- if (key === 'note' || key.startsWith('note ')) {
931
- // Fall through to indent-aware note parsing below
932
- } else {
933
- const value = trimmed.substring(colonIndex + 1).trim();
934
-
935
- // Enforce headers-before-content
936
- if (contentStarted) {
937
- pushError(
938
- lineNumber,
939
- `Options like '${key}: ${value}' must appear before the first message or declaration`
940
- );
941
- continue;
942
- }
943
-
944
- if (key === 'title') {
945
- result.title = value;
946
- result.titleLineNumber = lineNumber;
947
- continue;
948
- }
949
-
950
- // Store other options
951
- result.options[key] = value;
952
- continue;
953
- }
954
- }
955
-
956
- // Parse space-separated options (no colon): `no-activations`, `solid-fill`, `active-tag Priority`
957
- {
958
- const optLower = trimmed.toLowerCase();
959
- // Negated boolean: `no-activations` → options.activations = 'off'
960
- if (optLower.startsWith('no-')) {
961
- const base = optLower.substring(3);
962
- if (KNOWN_SEQ_BOOLEANS.has(base) || NO_PREFIX_ONLY_BOOLEANS.has(base)) {
963
- if (contentStarted) {
964
- pushError(
965
- lineNumber,
966
- `Options like '${trimmed}' must appear before the first message or declaration`
967
- );
968
- continue;
969
- }
970
- result.options[base] = 'off';
971
- continue;
972
- }
973
- }
974
- // Key-value option: `active-tag Priority`
975
- const spaceMatch = trimmed.match(OPTION_NOCOLON_RE);
976
- if (spaceMatch) {
977
- // Capture groups 1 and 2 guaranteed present after successful match.
978
- const optKey = spaceMatch[1]!.toLowerCase();
979
- const optVal = spaceMatch[2]!.trim();
980
- if (KNOWN_SEQ_OPTIONS.has(optKey) || KNOWN_SEQ_BOOLEANS.has(optKey)) {
981
- if (contentStarted) {
982
- pushError(
983
- lineNumber,
984
- `Options like '${trimmed}' must appear before the first message or declaration`
985
- );
986
- continue;
987
- }
988
- result.options[optKey] = optVal;
989
- continue;
990
- }
991
- }
992
- // Bare boolean keyword: `solid-fill` / `no-title`
993
- if (
994
- KNOWN_SEQ_BOOLEANS.has(optLower) &&
995
- (optLower === 'solid-fill' || optLower === 'no-title')
996
- ) {
997
- if (contentStarted) {
998
- pushError(
999
- lineNumber,
1000
- `Options like '${trimmed}' must appear before the first message or declaration`
1001
- );
1002
- continue;
1003
- }
1004
- result.options[optLower] = 'on';
1005
- continue;
1006
- }
1007
- }
1008
-
1009
- // Parse "Name is a type [position N]" declarations (always top-level).
1010
- // The legacy `aka Alias` modifier is no longer supported — Universal Name
1011
- // Handling makes aliasing unnecessary because forgiving normalization
1012
- // handles casing/whitespace differences automatically.
1013
- // Skip lines starting with 'note' — handled by note parsing below.
1014
- const {
1015
- core: isACore,
1016
- meta: isAMeta,
1017
- alias: isAAlias,
1018
- } = splitPipe(trimmed, lineNumber);
1019
- const isAMatch = !/^note(\s|$)/i.test(trimmed)
1020
- ? isACore.match(IS_A_PATTERN)
1021
- : null;
1022
- if (isAMatch) {
1023
- contentStarted = true;
1024
- // Capture groups 1 and 2 guaranteed present after successful match.
1025
- const id = isAMatch[1]!;
1026
- const typeStr = isAMatch[2]!.toLowerCase();
1027
- const remainder = isAMatch[3]?.trim() || '';
1028
-
1029
- const participantType: ParticipantType = VALID_PARTICIPANT_TYPES.has(
1030
- typeStr
1031
- )
1032
- ? (typeStr as ParticipantType)
1033
- : 'default';
1034
-
1035
- // TD-18: extract trailing `as <alias>` and register it. Order on the
1036
- // line is `Name is a TYPE as <alias> [position: N]`. When same-line
1037
- // metadata is present, splitPipe already peeled the alias (it must
1038
- // precede the metadata) and handed it back as `isAAlias`; otherwise
1039
- // the alias is still in `remainder` for us to peel here.
1040
- if (isAAlias !== undefined) {
1041
- nameAliasMap.set(isAAlias, id);
1042
- }
1043
- const asInRemainder = remainder.match(
1044
- /^(.*?)\s*\bas\s+([A-Za-z][A-Za-z0-9_]{0,11})\s*$/
1045
- );
1046
- if (asInRemainder) {
1047
- // Capture group 2 guaranteed present after successful match.
1048
- nameAliasMap.set(asInRemainder[2]!, id);
1049
- }
1050
-
1051
- // Position is colon-keyed metadata (§2.2) — pull it from the parsed meta.
1052
- const position = takePosition(isAMeta, lineNumber);
1053
-
1054
- // Avoid duplicate participant declarations
1055
- const key = addParticipant(id, lineNumber, {
1056
- type: participantType,
1057
- ...(position !== undefined && { position }),
1058
- ...(isAMeta !== undefined && { metadata: isAMeta }),
1059
- });
1060
- // Track group membership
1061
- if (activeGroup && !activeGroup.participantIds.includes(key)) {
1062
- const existingGroup = participantGroupMap.get(key);
1063
- if (existingGroup) {
1064
- pushError(
1065
- lineNumber,
1066
- `Participant '${id}' is already in group '${existingGroup}' — participants can only belong to one group`
1067
- );
1068
- } else {
1069
- activeGroup.participantIds.push(key);
1070
- // participantGroupMap is keyed by normalized participant key
1071
-
1072
- participantGroupMap.set(key, activeGroup.name);
1073
- }
1074
- }
1075
- continue;
1076
- }
1077
-
1078
- // Legacy `Name(color)` participant declaration at any level (§1.5 hard
1079
- // break). Scoped to the 11-name palette so `funcCall(arg)` doesn't trip.
1080
- const { core: colorCore, meta: colorMeta } = splitPipe(trimmed, lineNumber);
1081
- const coloredMatch = colorCore.match(COLORED_PARTICIPANT_PATTERN);
1082
- if (coloredMatch && !ARROW_PATTERN.test(colorCore)) {
1083
- // Capture groups 1 and 2 guaranteed present after successful match.
1084
- const id = coloredMatch[1]!;
1085
- const color = coloredMatch[2]!.trim();
1086
- pushError(
1087
- lineNumber,
1088
- `'${id}(${color})' parens-color syntax is no longer supported — use 'tag:' groups for coloring`
1089
- );
1090
- contentStarted = true;
1091
- const key = addParticipant(id, lineNumber, {
1092
- ...(colorMeta !== undefined && { metadata: colorMeta }),
1093
- });
1094
- if (activeGroup && !activeGroup.participantIds.includes(key)) {
1095
- const existingGroup = participantGroupMap.get(key);
1096
- if (existingGroup) {
1097
- pushError(
1098
- lineNumber,
1099
- `Participant '${id}' is already in group '${existingGroup}' — participants can only belong to one group`
1100
- );
1101
- } else {
1102
- activeGroup.participantIds.push(key);
1103
-
1104
- participantGroupMap.set(key, activeGroup.name);
1105
- }
1106
- }
1107
- continue;
1108
- }
1109
-
1110
- // Bare participant name — either inside an active group (indented) or top-level declaration
1111
- // Supports pipe metadata: " API | c: Gateway" or "Tapin2 | l:Park"
1112
- {
1113
- const {
1114
- core: bareCore,
1115
- meta: bareMeta,
1116
- alias: bareAlias,
1117
- } = splitPipe(trimmed, lineNumber);
1118
- const inGroup = activeGroup && measureIndent(raw) > 0;
1119
- if (
1120
- /^\S+$/.test(bareCore) &&
1121
- !ARROW_PATTERN.test(bareCore) &&
1122
- (inGroup || !bodyStarted || bareMeta)
1123
- ) {
1124
- contentStarted = true;
1125
- const id = bareCore;
1126
- if (bareAlias !== undefined) nameAliasMap.set(bareAlias, id);
1127
- // Colon-keyed `position: N` (§2.2) arrives as metadata here.
1128
- const position = takePosition(bareMeta, lineNumber);
1129
- const key = addParticipant(id, lineNumber, {
1130
- ...(position !== undefined && { position }),
1131
- ...(bareMeta !== undefined && { metadata: bareMeta }),
1132
- });
1133
- if (activeGroup && !activeGroup.participantIds.includes(key)) {
1134
- const existingGroup = participantGroupMap.get(key);
1135
- if (existingGroup) {
1136
- pushError(
1137
- lineNumber,
1138
- `Participant '${id}' is already in group '${existingGroup}' — participants can only belong to one group`
1139
- );
1140
- } else {
1141
- activeGroup.participantIds.push(key);
1142
-
1143
- participantGroupMap.set(key, activeGroup.name);
1144
- }
1145
- }
1146
- continue;
1147
- }
1148
- }
1149
-
1150
- // ---- Indent-aware parsing for messages and block keywords ----
1151
- const indent = measureIndent(raw);
1152
-
1153
- // Close blocks whose scope has ended (indent decreased)
1154
- while (blockStack.length > 0) {
1155
- // In-bounds by the length check above.
1156
- const top = blockStack[blockStack.length - 1]!;
1157
- if (indent > top.indent) break;
1158
- // Keep block on stack when 'else' or 'else if' matches current indent — handled below
1159
- if (
1160
- indent === top.indent &&
1161
- (top.block.type === 'if' || top.block.type === 'parallel')
1162
- ) {
1163
- const lower = trimmed.toLowerCase();
1164
- if (lower === 'else' || lower.startsWith('else if ')) break;
1165
- }
1166
- blockStack.pop();
1167
- }
1168
-
1169
- // Split pipe metadata before arrow parsing (arrows use $ anchor)
1170
- const { core: arrowCore, meta: arrowMeta } = splitPipe(trimmed, lineNumber);
1171
-
1172
- // Parse message lines first — arrows take priority over keywords
1173
- // Reject "async" keyword prefix — use ~> instead
1174
- const asyncPrefixMatch = arrowCore.match(/^async\s+(.+)$/i);
1175
- // Capture group 1 guaranteed present after successful match.
1176
- if (asyncPrefixMatch && ARROW_PATTERN.test(asyncPrefixMatch[1]!)) {
1177
- pushError(lineNumber, 'Use ~> for async messages: A ~> B: message');
1178
- continue;
1179
- }
1180
-
1181
- // ---- Labeled arrows: -label->, ~label~> ----
1182
- // Must be checked BEFORE plain arrow patterns to avoid partial matches
1183
- const labeledArrow = parseArrow(arrowCore);
1184
- if (labeledArrow && 'error' in labeledArrow) {
1185
- pushError(lineNumber, labeledArrow.error);
1186
- continue;
1187
- }
1188
- if (labeledArrow) {
1189
- contentStarted = true;
1190
- bodyStarted = true;
1191
- const { from, to, label: rawLabel, async: isAsync } = labeledArrow;
1192
- const fromKey = addParticipant(resolveAlias(from), lineNumber);
1193
- const toKey = addParticipant(resolveAlias(to), lineNumber);
1194
- lastMsgFrom = fromKey;
1195
-
1196
- // TD-13/TD-14: validate in-arrow label characters
1197
- const labelResult = parseInArrowLabel(rawLabel, lineNumber);
1198
- labelResult.diagnostics.forEach((d) => result.diagnostics.push(d));
1199
- const label = labelResult.label ?? rawLabel;
1200
-
1201
- const msg: SequenceMessage = {
1202
- kind: 'message',
1203
- from: fromKey,
1204
- to: toKey,
1205
- label,
1206
- lineNumber,
1207
- ...(isAsync ? { async: true } : {}),
1208
- ...(arrowMeta ? { metadata: arrowMeta } : {}),
1209
- };
1210
- result.messages.push(msg);
1211
- currentContainer().push(msg);
1212
- continue;
1213
- }
1214
-
1215
- // ---- Error: old colon-postfix syntax (A -> B: msg) ----
1216
- const colonPostfixSync = arrowCore.match(
1217
- /^(\S+)\s*->\s*([^\s:]+)\s*:\s*(.+)$/
1218
- );
1219
- const colonPostfixAsync = arrowCore.match(
1220
- /^(\S+)\s*~>\s*([^\s:]+)\s*:\s*(.+)$/
1221
- );
1222
- const colonPostfix = colonPostfixSync || colonPostfixAsync;
1223
- if (colonPostfix) {
1224
- // Capture groups 1-3 guaranteed present after successful match.
1225
- const a = colonPostfix[1]!;
1226
- const b = colonPostfix[2]!;
1227
- const msg = colonPostfix[3]!.trim();
1228
- const arrowChar = colonPostfixAsync ? '~' : '-';
1229
- const arrowEnd = colonPostfixAsync ? '~>' : '->';
1230
- pushError(
1231
- lineNumber,
1232
- `Colon syntax is no longer supported. Use '${a} ${arrowChar}${msg}${arrowEnd} ${b}' instead`
1233
- );
1234
- continue;
1235
- }
1236
-
1237
- // ---- Error: plain bidirectional arrows (A <-> B, A <~> B) ----
1238
- const bidiPlainMatch = arrowCore.match(/^(.+?)\s*(?:<->|<~>)\s*(.+)/);
1239
- if (bidiPlainMatch) {
1240
- pushError(
1241
- lineNumber,
1242
- "Bidirectional arrows are no longer supported. Use two separate lines: 'A -msg-> B' and 'B -msg-> A'"
1243
- );
1244
- continue;
1245
- }
1246
-
1247
- // ---- Deprecated bare return arrows: A <- B, A <~ B ----
1248
- const bareReturnSync = arrowCore.match(/^(.+?)\s*<-\s*(.+)$/);
1249
- const bareReturnAsync = arrowCore.match(/^(.+?)\s*<~\s*(.+)$/);
1250
- const bareReturn = bareReturnSync || bareReturnAsync;
1251
- if (bareReturn) {
1252
- const to = bareReturn[1];
1253
- const from = bareReturn[2];
1254
- pushError(
1255
- lineNumber,
1256
- `Left-pointing arrows are no longer supported. Write '${from} -> ${to}' instead`
1257
- );
1258
- continue;
1259
- }
1260
-
1261
- // ---- Bare (unlabeled) call arrows: A -> B, A ~> B ----
1262
- const bareCallSync = arrowCore.match(/^(.+?)\s*->\s*(.+)$/);
1263
- const bareCallAsync = arrowCore.match(/^(.+?)\s*~>\s*(.+)$/);
1264
- const bareCall = bareCallSync || bareCallAsync;
1265
- if (bareCall) {
1266
- contentStarted = true;
1267
- bodyStarted = true;
1268
- // Capture groups 1 and 2 guaranteed present after successful match.
1269
- const from = bareCall[1]!;
1270
- const to = bareCall[2]!;
1271
- const fromKey = addParticipant(resolveAlias(from), lineNumber);
1272
- const toKey = addParticipant(resolveAlias(to), lineNumber);
1273
- lastMsgFrom = fromKey;
1274
-
1275
- const msg: SequenceMessage = {
1276
- kind: 'message',
1277
- from: fromKey,
1278
- to: toKey,
1279
- label: '',
1280
- lineNumber,
1281
- ...(bareCallAsync ? { async: true } : {}),
1282
- ...(arrowMeta ? { metadata: arrowMeta } : {}),
1283
- };
1284
- result.messages.push(msg);
1285
- currentContainer().push(msg);
1286
- continue;
1287
- }
1288
-
1289
- // Parse 'if <label>' block keyword
1290
- const ifMatch = trimmed.match(/^if\s+(.+)$/i);
1291
- if (ifMatch) {
1292
- contentStarted = true;
1293
- bodyStarted = true;
1294
- const block: Writable<SequenceBlock> = {
1295
- kind: 'block',
1296
- type: 'if',
1297
- // Capture group 1 guaranteed present after successful match.
1298
- label: ifMatch[1]!.trim(),
1299
- children: [],
1300
- elseChildren: [],
1301
- lineNumber,
1302
- };
1303
- currentContainer().push(block);
1304
- blockStack.push({ block, indent, inElse: false });
1305
- continue;
1306
- }
1307
-
1308
- // Parse 'loop <label>' block keyword
1309
- const loopMatch = trimmed.match(/^loop\s+(.+)$/i);
1310
- if (loopMatch) {
1311
- contentStarted = true;
1312
- bodyStarted = true;
1313
- const block: Writable<SequenceBlock> = {
1314
- kind: 'block',
1315
- type: 'loop',
1316
- // Capture group 1 guaranteed present after successful match.
1317
- label: loopMatch[1]!.trim(),
1318
- children: [],
1319
- elseChildren: [],
1320
- lineNumber,
1321
- };
1322
- currentContainer().push(block);
1323
- blockStack.push({ block, indent, inElse: false });
1324
- continue;
1325
- }
1326
-
1327
- // Parse 'parallel [label]' block keyword
1328
- const parallelMatch = trimmed.match(/^parallel(?:\s+(.+))?$/i);
1329
- if (parallelMatch) {
1330
- contentStarted = true;
1331
- bodyStarted = true;
1332
- const block: Writable<SequenceBlock> = {
1333
- kind: 'block',
1334
- type: 'parallel',
1335
- label: parallelMatch[1]?.trim() || '',
1336
- children: [],
1337
- elseChildren: [],
1338
- lineNumber,
1339
- };
1340
- currentContainer().push(block);
1341
- blockStack.push({ block, indent, inElse: false });
1342
- continue;
1343
- }
1344
-
1345
- // Parse 'else if <label>' keyword (must come before bare 'else')
1346
- const elseIfMatch = trimmed.match(/^else\s+if\s+(.+)$/i);
1347
- if (elseIfMatch) {
1348
- if (
1349
- blockStack.length > 0 &&
1350
- // In-bounds by the length check above.
1351
- blockStack[blockStack.length - 1]!.indent === indent
1352
- ) {
1353
- // In-bounds by the length check above.
1354
- const top = blockStack[blockStack.length - 1]!;
1355
- if (top.block.type === 'parallel') {
1356
- pushError(
1357
- lineNumber,
1358
- "parallel blocks don't support else if — list all concurrent messages directly inside the block"
1359
- );
1360
- continue;
1361
- }
1362
- if (top.block.type === 'if') {
1363
- const branch: Writable<ElseIfBranch> = {
1364
- // Capture group 1 guaranteed present after successful match.
1365
- label: elseIfMatch[1]!.trim(),
1366
- children: [],
1367
- lineNumber,
1368
- };
1369
- // `elseIfBranches` is optional on SequenceBlock — Writable<>'s
1370
- // shallow strip doesn't unwrap optional readonly arrays, so seed
1371
- // and access through a typed-mutable local alias.
1372
- if (!top.block.elseIfBranches) top.block.elseIfBranches = [];
1373
- const branches = top.block.elseIfBranches as Writable<ElseIfBranch>[];
1374
- branches.push(branch);
1375
- top.activeElseIfBranch = branch;
1376
- top.inElse = false;
1377
- }
1378
- }
1379
- continue;
1380
- }
1381
-
1382
- // Parse 'else' keyword (only applies to 'if' blocks)
1383
- if (trimmed.toLowerCase() === 'else') {
1384
- if (
1385
- blockStack.length > 0 &&
1386
- // In-bounds by the length check above.
1387
- blockStack[blockStack.length - 1]!.indent === indent
1388
- ) {
1389
- // In-bounds by the length check above.
1390
- const top = blockStack[blockStack.length - 1]!;
1391
- if (top.block.type === 'parallel') {
1392
- pushError(
1393
- lineNumber,
1394
- "parallel blocks don't support else — list all concurrent messages directly inside the block"
1395
- );
1396
- continue;
1397
- }
1398
- if (top.block.type === 'if') {
1399
- top.inElse = true;
1400
- delete top.activeElseIfBranch;
1401
- top.block.elseLineNumber = lineNumber;
1402
- }
1403
- }
1404
- continue;
1405
- }
1406
-
1407
- // 'elif <label>' → suggest 'else if <label>'
1408
- const elifMatch = trimmed.match(/^elif\b\s*(.*)$/i);
1409
- if (elifMatch) {
1410
- // Capture group 1 guaranteed present after successful match.
1411
- const tailRaw = elifMatch[1]!.trim();
1412
- const tail = tailRaw ? ' ' + tailRaw : '';
1413
- pushError(
1414
- lineNumber,
1415
- `'elif' is not a keyword. Did you mean 'else if${tail}'?`
1416
- );
1417
- continue;
1418
- }
1419
-
1420
- // 'else <text>' (where text isn't 'if …') → suggest 'else if <text>'
1421
- const elseLabelMatch = trimmed.match(/^else\s+(.+)$/i);
1422
- if (elseLabelMatch) {
1423
- // Capture group 1 guaranteed present after successful match.
1424
- const tail = elseLabelMatch[1]!.trim();
1425
- pushError(
1426
- lineNumber,
1427
- `'else' does not take a label. Did you mean 'else if ${tail}'?`
1428
- );
1429
- continue;
1430
- }
1431
-
1432
- // ---- Note parsing (space-separated only) ----
1433
- // Strategy:
1434
- // 1. Try bare note: `note text` — position defaults, text is everything after `note`
1435
- // 2. For positioned: `note left [of] X text` — needs participant lookup to split name vs text
1436
- // 3. Multi-line: `note`, `note right`, `note right [of] X` (body indented below)
1437
- {
1438
- const noteParsed = parseNoteLine(
1439
- trimmed,
1440
- result.participants,
1441
- participantIds,
1442
- getSortedParticipants(),
1443
- lastMsgFrom
1444
- );
1445
- if (noteParsed) {
1446
- if (noteParsed.kind === 'single') {
1447
- const note: SequenceNote = {
1448
- kind: 'note',
1449
- text: noteParsed.text,
1450
- position: noteParsed.position,
1451
- participantId: noteParsed.participantId,
1452
- lineNumber,
1453
- endLineNumber: lineNumber,
1454
- };
1455
- bodyStarted = true;
1456
- currentContainer().push(note);
1457
- continue;
1458
- }
1459
- if (noteParsed.kind === 'multi-head') {
1460
- // Collect indented body lines
1461
- const noteLines: string[] = [];
1462
- while (i + 1 < lines.length) {
1463
- // In-bounds by the length check above.
1464
- const nextRaw = lines[i + 1]!;
1465
- const nextTrimmed = nextRaw.trim();
1466
- if (!nextTrimmed) break;
1467
- const nextIndent = measureIndent(nextRaw);
1468
- if (nextIndent <= indent) break;
1469
- noteLines.push(nextTrimmed);
1470
- i++;
1471
- }
1472
- if (noteLines.length === 0) continue; // no body yet — skip during live typing
1473
- const note: SequenceNote = {
1474
- kind: 'note',
1475
- text: noteLines.join('\n'),
1476
- position: noteParsed.position,
1477
- participantId: noteParsed.participantId,
1478
- lineNumber,
1479
- endLineNumber: i + 1, // i has advanced past the body lines (1-based)
1480
- };
1481
- bodyStarted = true;
1482
- currentContainer().push(note);
1483
- continue;
1484
- }
1485
- // 'skip' — note was incomplete (no preceding message, unknown participant)
1486
- continue;
1487
- }
1488
- }
1489
-
1490
- // Catch-all: nothing matched this line
1491
- pushWarning(
1492
- lineNumber,
1493
- `Unexpected line: '${trimmed}'. Expected a message ('A -> B' or 'A -label-> B'), participant ('Name' or 'Name is a actor'), note ('note right of A Text'), section ('== Name =='), or block ('if/loop/parallel <label>').`
1494
- );
1495
- }
1496
-
1497
- // Validate: if no explicit chart line, check for arrow-based inference
1498
- if (!hasExplicitChart && result.messages.length === 0) {
1499
- // Check if raw content has arrow patterns for inference
1500
- const hasArrows = lines.some((line) => ARROW_PATTERN.test(line.trim()));
1501
- if (!hasArrows) {
1502
- return fail(1, 'No "sequence" header and no sequence content detected');
1503
- }
1504
- }
1505
-
1506
- // Warn about unused participants (only when the diagram has messages)
1507
- if (result.messages.length > 0) {
1508
- const usedIds = new Set<string>();
1509
- for (const msg of result.messages) {
1510
- // msg.from / msg.to / el.participantId are normalized participant keys
1511
- // (set via addParticipant during parsing)
1512
- // eslint-disable-next-line name-normalize/required-at-insertion
1513
- usedIds.add(msg.from);
1514
- // eslint-disable-next-line name-normalize/required-at-insertion
1515
- usedIds.add(msg.to);
1516
- }
1517
- // Walk elements recursively to find note participant references
1518
- const walkElements = (elements: readonly SequenceElement[]): void => {
1519
- for (const el of elements) {
1520
- if (isSequenceNote(el)) {
1521
- // eslint-disable-next-line name-normalize/required-at-insertion
1522
- usedIds.add(el.participantId);
1523
- } else if (isSequenceBlock(el)) {
1524
- walkElements(el.children);
1525
- walkElements(el.elseChildren);
1526
- if (el.elseIfBranches) {
1527
- for (const branch of el.elseIfBranches) {
1528
- walkElements(branch.children);
1529
- }
1530
- }
1531
- }
1532
- }
1533
- };
1534
- walkElements(result.elements);
1535
-
1536
- for (const p of result.participants) {
1537
- if (!usedIds.has(p.id)) {
1538
- pushWarning(
1539
- p.lineNumber,
1540
- `Participant "${p.label}" is declared but never used in any message or note`
1541
- );
1542
- }
1543
- }
1544
- }
1545
-
1546
- // Warn about empty groups
1547
- for (const group of result.groups) {
1548
- if (group.participantIds.length === 0) {
1549
- pushWarning(
1550
- group.lineNumber,
1551
- `Empty group '${group.name}' — did you mean '== ${group.name} ==' for a section divider?`
1552
- );
1553
- }
1554
- }
1555
-
1556
- // Assign palette colors to bare (colorless) tag values.
1557
- finalizeAutoTagColors(result.tagGroups as Writable<TagGroup>[], palette);
1558
-
1559
- // Validate tag group values on participants and messages
1560
- if (result.tagGroups.length > 0) {
1561
- const entities: Array<{
1562
- metadata: Record<string, string>;
1563
- lineNumber: number;
1564
- }> = [];
1565
- for (const p of result.participants) {
1566
- if (p.metadata)
1567
- entities.push({ metadata: p.metadata, lineNumber: p.lineNumber });
1568
- }
1569
- for (const m of result.messages) {
1570
- if (m.metadata)
1571
- entities.push({ metadata: m.metadata, lineNumber: m.lineNumber });
1572
- }
1573
- for (const g of result.groups) {
1574
- if (g.metadata)
1575
- entities.push({ metadata: g.metadata, lineNumber: g.lineNumber });
1576
- }
1577
- validateTagValues(entities, result.tagGroups, pushWarning, suggest);
1578
- validateTagGroupNames(result.tagGroups, pushWarning, pushError);
1579
- }
1580
-
1581
- return result;
1582
- }
1583
-
1584
- /**
1585
- * Detect whether raw content looks like a sequence diagram.
1586
- * Used by the chart type inference logic.
1587
- */
1588
- export function looksLikeSequence(content: string): boolean {
1589
- if (!content) return false;
1590
- const lines = content.split('\n');
1591
- return lines.some((line) => {
1592
- const trimmed = line.trim();
1593
- if (trimmed.startsWith('//')) return false;
1594
- return ARROW_PATTERN.test(trimmed);
1595
- });
1596
- }