@diagrammo/dgmo 0.53.0 → 0.54.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 (199) hide show
  1. package/dist/advanced.d.ts +36 -0
  2. package/dist/advanced.js +159 -154
  3. package/dist/auto.js +142 -128
  4. package/dist/auto.mjs +775 -466
  5. package/dist/block.js +62 -60
  6. package/dist/chart-meta.js +50 -47
  7. package/dist/{charts-d3-DH2XA45I.js → charts-d3-25E4VWXO.js} +9 -8
  8. package/dist/chunk-2CW5ZTNH.js +58 -0
  9. package/dist/{chunk-PKNNHU7H.js → chunk-2NYXI35Z.js} +17 -10
  10. package/dist/{chunk-W5KMNC5U.js → chunk-2NZOHEP4.js} +9 -10
  11. package/dist/{chunk-6DCOIN4W.js → chunk-3FRO35WW.js} +768 -2
  12. package/dist/{chunk-6AUZPPAV.js → chunk-3KHJJP3J.js} +2 -2
  13. package/dist/{chunk-X3RXGSCZ.js → chunk-3SBHODJ4.js} +11 -11
  14. package/dist/{chunk-KUSAVOWZ.js → chunk-3SEEP2JM.js} +3 -3
  15. package/dist/{chunk-QWIBT6DS.js → chunk-47JNEKJA.js} +10 -10
  16. package/dist/{chunk-LHILKFDP.js → chunk-4T6OCSJ2.js} +9 -9
  17. package/dist/{chunk-XQ4KN6JQ.js → chunk-4ZIKWVSQ.js} +16 -15
  18. package/dist/{chunk-GMAH572Z.js → chunk-5VXU6MND.js} +2 -2
  19. package/dist/{chunk-HORZ5H2L.js → chunk-5XSNLXU5.js} +17 -17
  20. package/dist/{chunk-BFDVEO5R.js → chunk-62Y27T4C.js} +2 -2
  21. package/dist/{chunk-R5NH5BZG.js → chunk-6KL7TFLS.js} +6 -6
  22. package/dist/{chunk-UVDCHR4E.js → chunk-6OYDOQ2Q.js} +2 -2
  23. package/dist/{chunk-QI3BOLQB.js → chunk-7K6NDSXH.js} +4 -4
  24. package/dist/{chunk-27STWBOK.js → chunk-7SP4C6EV.js} +6 -8
  25. package/dist/{chunk-I46X5VUA.js → chunk-7TF3QOOD.js} +25 -1
  26. package/dist/{chunk-FGDSTT3F.js → chunk-7U4OR7CD.js} +2 -2
  27. package/dist/{chunk-VKNCC2GX.js → chunk-7UPFTBOW.js} +13 -13
  28. package/dist/{chunk-FT7TPPZ5.js → chunk-A3BDLDYG.js} +12 -12
  29. package/dist/{chunk-AZNXGWDC.js → chunk-ADCHLNWT.js} +7 -7
  30. package/dist/{chunk-CMVZWP32.js → chunk-AHTTKERF.js} +2 -2
  31. package/dist/{chunk-GAWKIIPO.js → chunk-AJFHNS67.js} +7 -7
  32. package/dist/{chunk-Q7AH665U.js → chunk-AQEYUVGJ.js} +111 -35
  33. package/dist/{chunk-BR7AYN76.js → chunk-B7N5KYXQ.js} +12 -12
  34. package/dist/chunk-BEOAKGNA.js +484 -0
  35. package/dist/{chunk-GABSL6TH.js → chunk-BM7MG6HT.js} +3 -3
  36. package/dist/{chunk-UKP5HWB5.js → chunk-BWYFIVZF.js} +4 -4
  37. package/dist/{chunk-7GUF6SDR.js → chunk-BZJIITYM.js} +9 -9
  38. package/dist/{chunk-RW5PEJ7D.js → chunk-C2UORMIA.js} +3 -3
  39. package/dist/{chunk-FXCB7H5O.js → chunk-CCGWX6AO.js} +2 -2
  40. package/dist/{chunk-AHXIMHDQ.js → chunk-COJW2NEY.js} +9 -9
  41. package/dist/{chunk-XY3UDKKH.js → chunk-COVYNAKJ.js} +4 -4
  42. package/dist/{chunk-GQ644SPD.js → chunk-CSHFU6LT.js} +14 -14
  43. package/dist/{chunk-JTMSWEIW.js → chunk-DLU643TF.js} +4 -4
  44. package/dist/{chunk-QAALSCKI.js → chunk-DPGQ5UMO.js} +12 -12
  45. package/dist/{chunk-I64ECJX3.js → chunk-EAI6XJ7D.js} +3 -3
  46. package/dist/{chunk-DWUMPMV2.js → chunk-EPRF3ZRV.js} +2 -2
  47. package/dist/{chunk-AIMLCDAO.js → chunk-EQOMY4PY.js} +2 -2
  48. package/dist/{chunk-XEAQDIMF.js → chunk-F7XM366V.js} +6 -6
  49. package/dist/{chunk-SG4PRYO3.js → chunk-FFVFZLRT.js} +49 -18
  50. package/dist/{chunk-LC3FFD43.js → chunk-FR6UFVSV.js} +12 -12
  51. package/dist/{chunk-6HTRCTAS.js → chunk-FVMKCZHF.js} +6 -4
  52. package/dist/{chunk-FUKNRLIJ.js → chunk-GK32ZFZY.js} +6 -6
  53. package/dist/{chunk-6KY7OCZC.js → chunk-GKYHWJXI.js} +3 -3
  54. package/dist/{chunk-NNYFQOTK.js → chunk-GQ5ECRP4.js} +10 -8
  55. package/dist/{chunk-PT4SW76W.js → chunk-HAA25PLC.js} +9 -9
  56. package/dist/{chunk-J7HI6G3G.js → chunk-HFEOOQ46.js} +6 -6
  57. package/dist/{chunk-KKHCNSKK.js → chunk-I47VHSPS.js} +5 -5
  58. package/dist/{chunk-YG2BEIQU.js → chunk-ICKXIMBA.js} +11 -11
  59. package/dist/{chunk-A44USUV7.js → chunk-ICZ7M6IZ.js} +23 -17
  60. package/dist/{chunk-HE5J53SZ.js → chunk-IOIMQHQ6.js} +3 -3
  61. package/dist/{chunk-3BEKKRH5.js → chunk-IYNKLCAZ.js} +8 -8
  62. package/dist/{chunk-774C2FF3.js → chunk-J7HQPVYH.js} +2 -2
  63. package/dist/{chunk-UFQJMOVZ.js → chunk-KBVOVDYP.js} +6 -6
  64. package/dist/{chunk-JHOAH4TI.js → chunk-KDK5EKUR.js} +2 -2
  65. package/dist/{chunk-VIRANCNY.js → chunk-KINOX74I.js} +14 -14
  66. package/dist/{chunk-VJHBSPGF.js → chunk-KQ3FNRGR.js} +3 -3
  67. package/dist/{chunk-U7NKYLX2.js → chunk-KQYQECWQ.js} +13 -13
  68. package/dist/{chunk-GMIJ35PW.js → chunk-KS5ZSM5T.js} +13 -13
  69. package/dist/{chunk-FLDC7ZAK.js → chunk-KV4HLIJE.js} +2 -2
  70. package/dist/{chunk-PYO4TJJ3.js → chunk-LPJ6H5X4.js} +11 -11
  71. package/dist/{chunk-LQHVDZYC.js → chunk-LRSW4JLE.js} +4 -4
  72. package/dist/{chunk-VRRDUYOK.js → chunk-M24VXUFG.js} +3 -3
  73. package/dist/{chunk-22OQWBWD.js → chunk-M3H7ZE6C.js} +10 -10
  74. package/dist/{chunk-MA3HJZHB.js → chunk-MTZOXISJ.js} +7 -7
  75. package/dist/{chunk-GWQJKTFS.js → chunk-N7BSBAVP.js} +94 -94
  76. package/dist/{chunk-65V6IRFN.js → chunk-NUTWTV6G.js} +10 -10
  77. package/dist/{chunk-HNBY6CNV.js → chunk-O576GYAP.js} +2 -2
  78. package/dist/{chunk-3CZLWOCI.js → chunk-OJ435DQZ.js} +3 -3
  79. package/dist/{chunk-BD3WGY4T.js → chunk-OR3R27CU.js} +2 -2
  80. package/dist/{chunk-2QC4HXCY.js → chunk-OSXQKC3S.js} +8 -10
  81. package/dist/{chunk-4RGZFA7H.js → chunk-Q5IHQ4NW.js} +11 -1
  82. package/dist/{chunk-ALYZ3MAA.js → chunk-QBYP2FBZ.js} +8 -476
  83. package/dist/{chunk-4ASVFYB2.js → chunk-R5BGGBFF.js} +5 -5
  84. package/dist/{chunk-YR43LJT5.js → chunk-RIEAN4D5.js} +15 -12
  85. package/dist/{chunk-P3H7QVDH.js → chunk-RRGKMJYL.js} +6 -6
  86. package/dist/{chunk-XG4NWFPY.js → chunk-S7FOIURR.js} +13 -13
  87. package/dist/{chunk-NNUMX6WX.js → chunk-S7YFT3PO.js} +40 -15
  88. package/dist/{chunk-SQCBSFT5.js → chunk-SBOAZ7YK.js} +51 -263
  89. package/dist/{chunk-VGNIDAPQ.js → chunk-SSHS42QG.js} +4 -4
  90. package/dist/{chunk-SGPFVDUL.js → chunk-T3FAJAJT.js} +6 -6
  91. package/dist/{chunk-AN2DQB3G.js → chunk-T7Y554OK.js} +12 -12
  92. package/dist/chunk-TGOMHUOV.js +218 -0
  93. package/dist/{chunk-W2DINCHL.js → chunk-TJN2OIVG.js} +31 -16
  94. package/dist/{chunk-CK2GMDXD.js → chunk-TLNVYWPA.js} +2 -2
  95. package/dist/{chunk-CQFNIIQN.js → chunk-U4IN6AHE.js} +2 -2
  96. package/dist/{chunk-HOXKNRON.js → chunk-UEU6IYKY.js} +2 -2
  97. package/dist/{chunk-UK6DKJLD.js → chunk-UIT7E6UF.js} +4 -4
  98. package/dist/{chunk-IQJCVREL.js → chunk-VMSBMIHW.js} +6 -6
  99. package/dist/{chunk-FFXCKZOU.js → chunk-VQFDBNYF.js} +3 -3
  100. package/dist/{chunk-PV2QO7QL.js → chunk-W6PCFRPL.js} +2 -2
  101. package/dist/{chunk-UQRIKAMH.js → chunk-WKNAJ4BR.js} +13 -13
  102. package/dist/{chunk-4WGMGKCZ.js → chunk-WW4BQ744.js} +11 -11
  103. package/dist/{chunk-ZXUAPTCK.js → chunk-WWHECSYZ.js} +2 -2
  104. package/dist/{chunk-4K42RUGC.js → chunk-XWODFBHJ.js} +43 -12
  105. package/dist/{chunk-VAO6ANGC.js → chunk-YENNFS5F.js} +9 -9
  106. package/dist/cli.cjs +160 -146
  107. package/dist/completion-registry.d.ts +129 -0
  108. package/dist/completion-registry.js +1270 -0
  109. package/dist/{compute-G55RG53N.js → compute-JAP5E2E4.js} +2 -2
  110. package/dist/dgmo-router-TGZWFIZO.js +88 -0
  111. package/dist/{dimensions-K6PGYYGT.js → dimensions-L6BXQXXL.js} +8 -8
  112. package/dist/editor.js +18 -0
  113. package/dist/element.js +142 -128
  114. package/dist/element.mjs +775 -466
  115. package/dist/{flowchart-parser-LALR6J6F.js → flowchart-parser-UKYOW3VM.js} +5 -5
  116. package/dist/{flowchart-renderer-DXYB5YRF.js → flowchart-renderer-DCFKUDDR.js} +9 -9
  117. package/dist/highlight.js +18 -0
  118. package/dist/index.js +65 -62
  119. package/dist/{layout-WCAIKGZG.js → layout-25Y3VDYF.js} +5 -5
  120. package/dist/layout-4SPLI4HG.js +15 -0
  121. package/dist/{layout-E6HR6PXH.js → layout-A6OIGZN2.js} +3 -3
  122. package/dist/{layout-3BVDMKXI.js → layout-CH4HFFRD.js} +4 -4
  123. package/dist/{layout-ZE5DRJWX.js → layout-G2ABQJQZ.js} +4 -4
  124. package/dist/{layout-3V2A4MUE.js → layout-ITLPDG6M.js} +6 -5
  125. package/dist/{layout-WSMPBQ4Q.js → layout-NF4EWFLT.js} +3 -3
  126. package/dist/layout-NY7MHJCL.js +15 -0
  127. package/dist/{layout-4IHCMWZN.js → layout-QBNAMEOA.js} +4 -4
  128. package/dist/{layout-OL2XFJXA.js → layout-SP7HJWKA.js} +3 -3
  129. package/dist/{layout-search-34DPZD43.js → layout-search-LUI475P3.js} +9 -4
  130. package/dist/{legend-constants-P5EQWRQQ.js → legend-constants-B77VKB3R.js} +7 -2
  131. package/dist/{parser-5QPFOB5F.js → parser-2UWWNTFL.js} +4 -4
  132. package/dist/{parser-CKA3QMAG.js → parser-3RXYQSA7.js} +5 -5
  133. package/dist/{parser-GKP2MHHL.js → parser-5C3PEZK3.js} +3 -3
  134. package/dist/{parser-275KP7JO.js → parser-66RYGJRB.js} +5 -6
  135. package/dist/{parser-7JHWEWXM.js → parser-6GN5YJBJ.js} +4 -4
  136. package/dist/{parser-XVPTRWZ4.js → parser-6MQM7WT3.js} +4 -4
  137. package/dist/{parser-7JVIDLLR.js → parser-7UI6UQ7B.js} +4 -4
  138. package/dist/{parser-5HQXCNOS.js → parser-B2A7XOAK.js} +3 -3
  139. package/dist/{parser-UWRX52PC.js → parser-CCNJKDDA.js} +4 -4
  140. package/dist/{parser-CRKLXGW7.js → parser-ESHNUFME.js} +4 -4
  141. package/dist/{parser-EXNBDNGY.js → parser-F4FEU57T.js} +3 -3
  142. package/dist/{parser-IVGHXLZP.js → parser-FHRMFUNX.js} +4 -3
  143. package/dist/{parser-XNKJCMMA.js → parser-FPCWDYQV.js} +4 -4
  144. package/dist/{parser-ZQDQTB4X.js → parser-FY3YFFBS.js} +5 -5
  145. package/dist/{parser-VOERD6XR.js → parser-GML6V2KZ.js} +4 -4
  146. package/dist/{parser-3CODK6RG.js → parser-HA3PF5FL.js} +4 -4
  147. package/dist/{parser-GN2MM5CC.js → parser-KTYF6WO5.js} +3 -3
  148. package/dist/{parser-ZGNXYFPS.js → parser-LTA2BOZO.js} +3 -3
  149. package/dist/{parser-FOOFEJQU.js → parser-LU7UAKNB.js} +4 -4
  150. package/dist/{parser-NNIL3HXU.js → parser-MHKYQPC2.js} +3 -3
  151. package/dist/{parser-IKWK2ZU3.js → parser-MVSYFK63.js} +4 -5
  152. package/dist/{parser-3VSWEKBF.js → parser-O7YQYFEN.js} +4 -4
  153. package/dist/{parser-YSLNDFLV.js → parser-OPIXXUY4.js} +4 -5
  154. package/dist/{parser-RNLTBIJ6.js → parser-S4EJPNVN.js} +4 -4
  155. package/dist/{parser-G26MTBFS.js → parser-T3D7BV6O.js} +3 -3
  156. package/dist/{parser-3KIGEEJM.js → parser-VVE4THG3.js} +4 -4
  157. package/dist/{parser-5FGJSQNS.js → parser-WPPJGB4R.js} +4 -4
  158. package/dist/{parser-27Z76SN3.js → parser-X5XLFY4Z.js} +4 -4
  159. package/dist/{parser-6B6SSZT7.js → parser-XCPMEB2Z.js} +3 -3
  160. package/dist/{parser-6PPHAXXH.js → parser-XRDUX2AR.js} +5 -5
  161. package/dist/{parser-FF4BMRSB.js → parser-YPWYT46P.js} +4 -4
  162. package/dist/{parser-4E4I3GNT.js → parser-YULS5MLD.js} +4 -4
  163. package/dist/{renderer-ZBTEX2JK.js → renderer-2DME63VZ.js} +4 -4
  164. package/dist/{renderer-RLMOTXZ6.js → renderer-3VY3VXJE.js} +13 -13
  165. package/dist/{renderer-6WRYVM5R.js → renderer-4A32DX5L.js} +8 -8
  166. package/dist/{renderer-UPF7NRTI.js → renderer-4K5YYX5R.js} +11 -11
  167. package/dist/{renderer-HIFQVEJA.js → renderer-6CFML5G3.js} +9 -9
  168. package/dist/{renderer-7NQSTQMQ.js → renderer-7HMZ5OCJ.js} +4 -4
  169. package/dist/{renderer-NE5W7CQP.js → renderer-B7Y6I7JX.js} +6 -5
  170. package/dist/{renderer-LVZAHRP6.js → renderer-C3RI4OPJ.js} +11 -12
  171. package/dist/{renderer-COLQFSFV.js → renderer-CJ56SUMF.js} +9 -9
  172. package/dist/{renderer-WFSUBKUL.js → renderer-COJZZ4F6.js} +8 -8
  173. package/dist/{renderer-SQ54MDSA.js → renderer-FXMGCSRP.js} +9 -9
  174. package/dist/{renderer-TYHGXAVT.js → renderer-G4UOW3RH.js} +4 -4
  175. package/dist/{renderer-YUXGXGSW.js → renderer-HQ4JFZOV.js} +9 -9
  176. package/dist/{renderer-JTCX6PCG.js → renderer-LHVCKEWF.js} +9 -9
  177. package/dist/{renderer-WNFXYYGL.js → renderer-OZX5KGQM.js} +8 -8
  178. package/dist/{renderer-CWEFGEIC.js → renderer-P3YQES7B.js} +13 -13
  179. package/dist/{renderer-UKXW2QPE.js → renderer-PLVIARM7.js} +9 -8
  180. package/dist/{renderer-5MPN7CXZ.js → renderer-RA4PTAMF.js} +5 -5
  181. package/dist/{renderer-JCZG6AYU.js → renderer-RMAM2SOJ.js} +10 -10
  182. package/dist/{renderer-JWGQSTMR.js → renderer-SES7QZYL.js} +14 -14
  183. package/dist/{renderer-KARQF3F3.js → renderer-TQ5QDZOE.js} +8 -8
  184. package/dist/{renderer-6EN5XXJZ.js → renderer-V5SNTPAM.js} +10 -10
  185. package/dist/{renderer-QD2SJF2I.js → renderer-WVD6G2TS.js} +9 -9
  186. package/dist/{renderer-AK2WO4EI.js → renderer-X2LYSMLU.js} +4 -4
  187. package/dist/{renderer-VMH6L6CP.js → renderer-Y3NXZINM.js} +8 -8
  188. package/dist/{renderer-XTXSOK3L.js → renderer-YEFOOULC.js} +10 -10
  189. package/dist/{renderer-YUKPS27O.js → renderer-YP3P6UVT.js} +8 -8
  190. package/dist/{renderer-D3BRNOMV.js → renderer-YVMETH6V.js} +11 -11
  191. package/dist/{renderer-2C6DJRYN.js → renderer-Z4GNKNXO.js} +8 -8
  192. package/dist/{renderer-radial-WF6VOUOD.js → renderer-radial-VYBZAXXE.js} +9 -9
  193. package/dist/{state-parser-L7DHZIQO.js → state-parser-VIW2WNA5.js} +5 -5
  194. package/dist/{state-renderer-TFEUOP4H.js → state-renderer-H22AQD3U.js} +12 -12
  195. package/package.json +5 -1
  196. package/dist/chunk-YJVA3QUU.js +0 -734
  197. package/dist/dgmo-router-B4S23EBH.js +0 -86
  198. package/dist/layout-3M5UYJHY.js +0 -14
  199. package/dist/layout-KPQWJNAG.js +0 -14
package/dist/element.mjs CHANGED
@@ -429,6 +429,408 @@ var init_colors = __esm({
429
429
  }
430
430
  });
431
431
 
432
+ // src/directives-registry.ts
433
+ function tokensWhere(pred) {
434
+ const out = /* @__PURE__ */ new Set();
435
+ for (const e of DIRECTIVES_REGISTRY) if (pred(e)) out.add(e.token);
436
+ return out;
437
+ }
438
+ var DIRECTIVES_REGISTRY, ALL_REGISTRY_TOKENS, INFRA_TOP_LEVEL_OPTION_SET, INFRA_BEHAVIOR_KEY_SET, INFRA_EDGE_ONLY_KEY_SET, GANTT_OPTION_SET, GANTT_BOOLEAN_SET, MAP_DIRECTIVE_KEY_SET, REGISTRY_DIRECTIVE_TOKENS, REGISTRY_CONTROL_TOKENS, REGISTRY_STATUS_TOKENS, REGISTRY_MODIFIER_TOKENS, REGISTRY_COLON_KEY_TOKENS, REGISTRY_NON_HIGHLIGHT_TOKENS;
439
+ var init_directives_registry = __esm({
440
+ "src/directives-registry.ts"() {
441
+ "use strict";
442
+ DIRECTIVES_REGISTRY = [
443
+ // ── Universal date handling (§ BL-121) ───────────────────
444
+ // Accepted in every date-bearing chart (gantt, pert, countdown, timeline,
445
+ // event-line). Handled inline by each parser's prescan — no parser-Set
446
+ // membership here — so they only contribute highlighting (DIRECTIVE_KEYWORDS).
447
+ { token: "year", category: "directive" },
448
+ { token: "date-order", category: "directive" },
449
+ { token: "no-current-year", category: "directive" },
450
+ // ── Infra ────────────────────────────────────────────────
451
+ // Top-level SLO options that are ALSO accepted as node colon-properties
452
+ // (dual membership). Highlight bare (kept in DIRECTIVE_KEYWORDS) — their
453
+ // rarer colon form stays keyword, an accepted minor false-positive.
454
+ {
455
+ token: "slo-availability",
456
+ category: "directive",
457
+ infra: ["top-level", "behavior"]
458
+ },
459
+ {
460
+ token: "slo-p90-latency-ms",
461
+ category: "directive",
462
+ infra: ["top-level", "behavior"]
463
+ },
464
+ {
465
+ token: "slo-warning-margin",
466
+ category: "directive",
467
+ infra: ["top-level", "behavior"]
468
+ },
469
+ // Top-level-only options (space-separated, bare).
470
+ { token: "default-latency-ms", category: "directive", infra: ["top-level"] },
471
+ { token: "default-uptime", category: "directive", infra: ["top-level"] },
472
+ { token: "default-rps", category: "directive", infra: ["top-level"] },
473
+ // `active-tag` is shared with gantt + map (see below).
474
+ // Node behavior colon-keys — highlight as propertyName.
475
+ { token: "latency-ms", colonKey: true, infra: ["behavior"] },
476
+ { token: "uptime", colonKey: true, infra: ["behavior"] },
477
+ { token: "instances", colonKey: true, infra: ["behavior"] },
478
+ { token: "max-rps", colonKey: true, infra: ["behavior"] },
479
+ { token: "cache-hit", colonKey: true, infra: ["behavior"] },
480
+ { token: "firewall-block", colonKey: true, infra: ["behavior"] },
481
+ { token: "ratelimit-rps", colonKey: true, infra: ["behavior"] },
482
+ { token: "buffer", colonKey: true, infra: ["behavior"] },
483
+ { token: "drain-rate", colonKey: true, infra: ["behavior"] },
484
+ { token: "retention-hours", colonKey: true, infra: ["behavior"] },
485
+ { token: "partitions", colonKey: true, infra: ["behavior"] },
486
+ { token: "concurrency", colonKey: true, infra: ["behavior"] },
487
+ { token: "duration-ms", colonKey: true, infra: ["behavior"] },
488
+ { token: "cold-start-ms", colonKey: true, infra: ["behavior"] },
489
+ { token: "cb-error-threshold", colonKey: true, infra: ["behavior"] },
490
+ { token: "cb-latency-threshold-ms", colonKey: true, infra: ["behavior"] },
491
+ // Edge-only colon-key.
492
+ { token: "rps", colonKey: true, infra: ["edge"] },
493
+ // ── Gantt ────────────────────────────────────────────────
494
+ { token: "start", category: "directive", gantt: ["option"] },
495
+ {
496
+ token: "today-marker",
497
+ category: "directive",
498
+ gantt: ["option", "boolean"]
499
+ },
500
+ {
501
+ token: "critical-path",
502
+ category: "directive",
503
+ gantt: ["option", "boolean"]
504
+ },
505
+ { token: "sprint-length", category: "directive", gantt: ["option"] },
506
+ { token: "sprint-number", category: "directive", gantt: ["option"] },
507
+ { token: "sprint-start", category: "directive", gantt: ["option"] },
508
+ { token: "fill-tint", category: "directive", gantt: ["boolean"] },
509
+ { token: "fill-solid", category: "directive", gantt: ["boolean"] },
510
+ { token: "fill-outline", category: "directive", gantt: ["boolean"] },
511
+ // `chart` is a wireframe element keyword (CONTROL); gantt also accepts it as
512
+ // an option. Highlight family is control.
513
+ { token: "chart", category: "control", gantt: ["option"] },
514
+ // `dependencies` is ON by default → almost never written bare (authors write
515
+ // `no-dependencies`, which DOES highlight). Bare word collides with prose and
516
+ // the specializer is context-free, so it stays plain. Present for parser
517
+ // Set derivation only.
518
+ { token: "dependencies", noHighlight: true, gantt: ["option", "boolean"] },
519
+ // ── Map ──────────────────────────────────────────────────
520
+ // Per-element value→channel ramps (decision #20): the directive names the
521
+ // visual channel — `region-heat` (colour), `poi-size` (marker area),
522
+ // `flow-width` (stroke). The per-element key mirrors it (`heat:`/`size:`/`width:`).
523
+ { token: "region-heat", category: "directive", map: true },
524
+ { token: "poi-size", category: "directive", map: true },
525
+ { token: "flow-width", category: "directive", map: true },
526
+ { token: "locale", category: "directive", map: true },
527
+ { token: "caption", category: "directive", map: true },
528
+ { token: "no-coastline", category: "directive", map: true },
529
+ { token: "no-relief", category: "directive", map: true },
530
+ { token: "no-context-labels", category: "directive", map: true },
531
+ { token: "no-region-labels", category: "directive", map: true },
532
+ { token: "no-region-heat-value", category: "directive", map: true },
533
+ { token: "no-poi-labels", category: "directive", map: true },
534
+ { token: "no-colorize", category: "directive", map: true },
535
+ { token: "no-cities", category: "directive", map: true },
536
+ { token: "no-cluster-pois", category: "directive", map: true },
537
+ // ── Clock channel (BL-122): live local-time cards on POIs flagged `clock` ──
538
+ // Activation is the per-POI `clock` flag (peeled in the map parser, not a
539
+ // directive). `hours 9-17` / `days mon-fri` are the global availability
540
+ // window (reused verbatim from the clock chart type).
541
+ { token: "hours", category: "directive", map: true },
542
+ { token: "days", category: "directive", map: true },
543
+ // ── Shared across types ──────────────────────────────────
544
+ // `active-tag`: infra top-level option + gantt option + map directive.
545
+ // Treemap also accepts it (§24C.6, decision #48) — validated inline in its
546
+ // parser, so no chart-scoped Set is derived for it; highlighting comes from
547
+ // the global directive category here.
548
+ {
549
+ token: "active-tag",
550
+ category: "directive",
551
+ infra: ["top-level"],
552
+ gantt: ["option"],
553
+ map: true
554
+ },
555
+ // `title`: gantt option. (The data-chart `title` directive was removed in
556
+ // decision #48 — it now raises E_TITLE_DIRECTIVE; the token stays here for
557
+ // gantt.)
558
+ { token: "title", category: "directive", gantt: ["option"] },
559
+ // `sort`: gantt option (also general, hand-listed).
560
+ { token: "sort", category: "directive", gantt: ["option"] },
561
+ // `lane-by <group>`: swimlane axis (view-state). NB not `swimlane` — that's a
562
+ // chart type. Kanban reads it via its own KNOWN_OPTIONS; gantt via the switch.
563
+ { token: "lane-by", category: "directive", gantt: ["option"] },
564
+ // `no-title`: gantt boolean + map directive (also general, hand-listed).
565
+ { token: "no-title", category: "directive", gantt: ["boolean"], map: true },
566
+ // `no-legend`: universal (decision #48) — every chart that renders a legend
567
+ // accepts it, and charts without one take it as a harmless no-op. Parsed for
568
+ // most types via GLOBAL_BOOLEANS in utils/parsing; gantt derives its boolean
569
+ // Set from here, and map from `map: true`.
570
+ { token: "no-legend", category: "directive", gantt: ["boolean"], map: true },
571
+ // ══════════════════════════════════════════════════════════
572
+ // Highlight-only vocab (no extractable parser Set).
573
+ // ══════════════════════════════════════════════════════════
574
+ // These chart types validate their directives with inline checks rather than
575
+ // a literal Set, so there is no parser literal to delete — the registry is
576
+ // simply their single declaration site for highlighting. Grouped by chart
577
+ // type. Categories route to the specializer-read sets in keywords.ts.
578
+ // ── Gantt (extras beyond the option/boolean Sets) ────────
579
+ { token: "era", category: "directive" },
580
+ { token: "marker", category: "directive" },
581
+ { token: "holiday", category: "directive" },
582
+ // `workweek` is also the canonical weekday-window directive on clock and map
583
+ // clock-cards (decision #48; `days` stays as the legacy alias).
584
+ { token: "workweek", category: "directive", map: true },
585
+ { token: "no-dependencies", category: "directive" },
586
+ // ── Tech-radar ───────────────────────────────────────────
587
+ { token: "rings", category: "directive" },
588
+ // Blip listing is default-on everywhere (decision #48): `no-blip-legend`
589
+ // suppresses it; `show-blip-legend` stays as the parse-accepted legacy no-op.
590
+ { token: "no-blip-legend", category: "directive" },
591
+ { token: "show-blip-legend", category: "directive" },
592
+ { token: "trend", category: "directive" },
593
+ // ── Tags / shared directives ─────────────────────────────
594
+ { token: "tags", category: "directive" },
595
+ { token: "import", category: "directive" },
596
+ { token: "hide", category: "directive" },
597
+ { token: "direction", category: "directive" },
598
+ // ── Clock ────────────────────────────────────────────────
599
+ // The board's flat directives — highlight-only (the parser validates them
600
+ // inline, no extractable Set). `no-title` + `direction` are already listed
601
+ // above (shared). Values (analog is a bare flag; color-by's
602
+ // place|work|daylight|time|none) highlight as plain tokens.
603
+ { token: "analog", category: "directive" },
604
+ { token: "hours", category: "directive" },
605
+ { token: "days", category: "directive" },
606
+ { token: "no-sun", category: "directive" },
607
+ { token: "time-24", category: "directive" },
608
+ { token: "color-by", category: "directive" },
609
+ // ── Boxes-and-lines ──────────────────────────────────────
610
+ // `heat <Label> [low] [high]` is the value→colour ramp directive, but `heat`
611
+ // is NOT registered as a directive keyword: it is dual-use with the `heat:`
612
+ // metadata key (decision #20), and a token in DIRECTIVE_KEYWORDS would block the
613
+ // ATTRIBUTE_KEYS colon-gate from reclassifying `heat:` as a propertyName. So
614
+ // `heat` lives only in the reserved-key registry + ATTRIBUTE_KEYS and highlights
615
+ // as a property in both positions — exactly the treemap `heat` precedent.
616
+ // Box values render by default since decision #48; `show-values` stays as
617
+ // the parse-accepted legacy no-op (suppress with the shared `no-value`).
618
+ { token: "show-values", category: "directive" },
619
+ // Treemap's legacy plural spelling of the same suppression (decision #48
620
+ // made the shared singular `no-value` canonical). Registered so a legacy
621
+ // document still highlights it as a directive rather than plain text.
622
+ { token: "no-values", category: "directive" },
623
+ // ── Swimlane ─────────────────────────────────────────────
624
+ { token: "lane", category: "directive" },
625
+ // ── ER ───────────────────────────────────────────────────
626
+ { token: "notation", category: "directive" },
627
+ // ── Class ────────────────────────────────────────────────
628
+ { token: "extends", category: "directive" },
629
+ { token: "implements", category: "directive" },
630
+ { token: "abstract", category: "directive" },
631
+ { token: "interface", category: "directive" },
632
+ { token: "enum", category: "directive" },
633
+ // ── C4 ───────────────────────────────────────────────────
634
+ { token: "containers", category: "directive" },
635
+ { token: "components", category: "directive" },
636
+ { token: "deployment", category: "directive" },
637
+ { token: "technology", category: "directive" },
638
+ // ── Infra (directives beyond the option/behavior Sets) ───
639
+ { token: "sub-node-label", category: "directive" },
640
+ { token: "show-sub-node-count", category: "directive" },
641
+ { token: "animate", category: "directive" },
642
+ // ── Sequence ─────────────────────────────────────────────
643
+ { token: "activations", category: "directive" },
644
+ { token: "no-activations", category: "directive" },
645
+ // ── Map element leaders (not in DIRECTIVE_SET) ───────────
646
+ { token: "poi", category: "directive" },
647
+ { token: "route", category: "directive" },
648
+ // ── Data charts ──────────────────────────────────────────
649
+ { token: "stacked", category: "directive" },
650
+ { token: "no-name", category: "directive" },
651
+ { token: "no-value", category: "directive" },
652
+ { token: "no-percent", category: "directive" },
653
+ { token: "series", category: "directive" },
654
+ { token: "orientation", category: "directive" },
655
+ { token: "x-label", category: "directive" },
656
+ { token: "y-label", category: "directive" },
657
+ { token: "size-label", category: "directive" },
658
+ { token: "columns", category: "directive" },
659
+ { token: "rows", category: "directive" },
660
+ { token: "labels", category: "directive" },
661
+ { token: "rotate", category: "directive" },
662
+ { token: "scale", category: "directive" },
663
+ { token: "values", category: "directive" },
664
+ { token: "orientation-horizontal", category: "directive" },
665
+ // ── Slope ────────────────────────────────────────────────
666
+ { token: "period", category: "directive" },
667
+ // ── Quadrant ─────────────────────────────────────────────
668
+ { token: "x-axis", category: "directive" },
669
+ { token: "y-axis", category: "directive" },
670
+ { token: "top-right", category: "directive" },
671
+ { token: "top-left", category: "directive" },
672
+ { token: "bottom-right", category: "directive" },
673
+ { token: "bottom-left", category: "directive" },
674
+ // ── Layout (cross-chart) ─────────────────────────────────
675
+ { token: "direction-tb", category: "directive" },
676
+ { token: "direction-lr", category: "directive" },
677
+ { token: "orientation-vertical", category: "directive" },
678
+ // ── Pyramid ──────────────────────────────────────────────
679
+ { token: "inverted", category: "directive" },
680
+ // ── Color / notes (cross-chart) ──────────────────────────
681
+ { token: "color", category: "directive" },
682
+ { token: "no-notes", category: "directive" },
683
+ // ── Emphasis family (§1.11, decision #49) ────────────────
684
+ // `highlight <Name>…` / `dim <Name>…` — the figure/ground axis, orthogonal
685
+ // to color (§1.5) and to the chart-wide `fill-*` family (§1.9). Both are
686
+ // chart-level directives taking a name list, validated inline by each
687
+ // adopting parser (no extractable Set), so the registry is their single
688
+ // declaration site for highlighting.
689
+ //
690
+ // `highlight` shipped on `family` long before this entry existed and was
691
+ // never registered — it had completions but highlighted as plain text. Both
692
+ // tokens are registered together so the whole family lights up at once.
693
+ //
694
+ // Supported on `sankey` and `family` today; every further chart type is a
695
+ // separate decision. Registering them globally is correct regardless: the
696
+ // Lezer specializer is context-free (it cannot know the chart type), and the
697
+ // same is already true of every other chart-scoped token here.
698
+ { token: "highlight", category: "directive" },
699
+ { token: "dim", category: "directive" },
700
+ // ── RACI ─────────────────────────────────────────────────
701
+ { token: "roles", category: "directive" },
702
+ // ── Cycle ────────────────────────────────────────────────
703
+ { token: "direction-counterclockwise", category: "directive" },
704
+ { token: "circle-nodes", category: "directive" },
705
+ // ── Journey-map ──────────────────────────────────────────
706
+ { token: "persona", category: "directive" },
707
+ // ── Function ─────────────────────────────────────────────
708
+ { token: "x", category: "directive" },
709
+ { token: "shade", category: "directive" },
710
+ // ── Wordcloud ────────────────────────────────────────────
711
+ { token: "max", category: "directive" },
712
+ { token: "size", category: "directive" },
713
+ // ── Arc ──────────────────────────────────────────────────
714
+ { token: "order", category: "directive" },
715
+ // ── PERT ─────────────────────────────────────────────────
716
+ { token: "time-unit", category: "directive" },
717
+ { token: "default-confidence", category: "directive" },
718
+ { token: "node-detail", category: "directive" },
719
+ { token: "trials", category: "directive" },
720
+ { token: "seed", category: "directive" },
721
+ { token: "scrubber-trials", category: "directive" },
722
+ // `start-date` is also gantt's canonical project-start option (decision #48;
723
+ // bare `start` above stays as the legacy alias).
724
+ { token: "start-date", category: "directive", gantt: ["option"] },
725
+ { token: "end-date", category: "directive" },
726
+ // ── Goal ─────────────────────────────────────────────────
727
+ // Mode flags, value-key leaders, and opt-outs. `now`/`target` collide with
728
+ // label prose ("start now", "on target"), but the arrow-label demotion pass
729
+ // (see highlight-api LABEL_WORD_NODES) treats any keyword-bearing label as
730
+ // text and demotes them, so they highlight only as line leaders.
731
+ { token: "thermometer", category: "directive" },
732
+ { token: "gauge", category: "directive" },
733
+ { token: "now", category: "directive" },
734
+ { token: "target", category: "directive" },
735
+ { token: "no-note", category: "directive" },
736
+ { token: "no-auto-color", category: "directive" },
737
+ // ── Control-flow + wireframe element keywords (CONTROL) ──
738
+ { token: "if", category: "control" },
739
+ { token: "else", category: "control" },
740
+ { token: "loop", category: "control" },
741
+ { token: "parallel", category: "control" },
742
+ { token: "note", category: "control" },
743
+ { token: "nav", category: "control" },
744
+ { token: "tabs", category: "control" },
745
+ { token: "table", category: "control" },
746
+ { token: "image", category: "control" },
747
+ { token: "modal", category: "control" },
748
+ { token: "skeleton", category: "control" },
749
+ { token: "alert", category: "control" },
750
+ { token: "progress", category: "control" },
751
+ { token: "mobile", category: "control" },
752
+ // ── Kanban status keywords (STATUS) ──────────────────────
753
+ { token: "na", category: "status" },
754
+ { token: "todo", category: "status" },
755
+ { token: "wip", category: "status" },
756
+ { token: "done", category: "status" },
757
+ { token: "blocked", category: "status" },
758
+ { token: "in-progress", category: "status" },
759
+ { token: "backlog", category: "status" },
760
+ { token: "ready", category: "status" },
761
+ // ── Modifiers + ER column types/modifiers (MODIFIER) ─────
762
+ // `collapsed` is the bare trailing group-line flag made canonical by
763
+ // decision #48 (`[Fulfillment] collapsed`). It is deliberately a bare
764
+ // MODIFIER rather than a colon key: the legacy `collapsed: true` form still
765
+ // parses and still highlights (as `modifier` instead of `propertyName`),
766
+ // whereas leaving it colon-only left the canonical bare form as plain text.
767
+ { token: "collapsed", category: "modifier" },
768
+ { token: "as", category: "modifier" },
769
+ { token: "alias", category: "modifier" },
770
+ { token: "aka", category: "modifier" },
771
+ { token: "position", category: "modifier" },
772
+ { token: "default", category: "modifier" },
773
+ { token: "pk", category: "modifier" },
774
+ { token: "fk", category: "modifier" },
775
+ { token: "nullable", category: "modifier" },
776
+ { token: "unique", category: "modifier" },
777
+ { token: "int", category: "modifier" },
778
+ { token: "varchar", category: "modifier" },
779
+ { token: "text", category: "modifier" },
780
+ { token: "boolean", category: "modifier" },
781
+ { token: "date", category: "modifier" },
782
+ { token: "timestamp", category: "modifier" },
783
+ { token: "float", category: "modifier" },
784
+ { token: "decimal", category: "modifier" },
785
+ // ── Bracket ──────────────────────────────────────────────
786
+ // `beats` / `vs` are the infix match keywords; the rest are line directives.
787
+ // (`seed` and `no-legend` already appear above — shared, not re-listed.)
788
+ { token: "beats", category: "control" },
789
+ { token: "vs", category: "control" },
790
+ { token: "rounds", category: "directive" },
791
+ { token: "accent", category: "directive" },
792
+ { token: "no-round", category: "modifier" },
793
+ { token: "no-rounds", category: "modifier" },
794
+ { token: "single-elim", category: "modifier" },
795
+ { token: "double-elim", category: "modifier" },
796
+ { token: "seeded", category: "modifier" }
797
+ ];
798
+ ALL_REGISTRY_TOKENS = tokensWhere(() => true);
799
+ INFRA_TOP_LEVEL_OPTION_SET = tokensWhere(
800
+ (e) => !!e.infra?.includes("top-level")
801
+ );
802
+ INFRA_BEHAVIOR_KEY_SET = tokensWhere(
803
+ (e) => !!e.infra?.includes("behavior")
804
+ );
805
+ INFRA_EDGE_ONLY_KEY_SET = tokensWhere(
806
+ (e) => !!e.infra?.includes("edge")
807
+ );
808
+ GANTT_OPTION_SET = tokensWhere(
809
+ (e) => !!e.gantt?.includes("option")
810
+ );
811
+ GANTT_BOOLEAN_SET = tokensWhere(
812
+ (e) => !!e.gantt?.includes("boolean")
813
+ );
814
+ MAP_DIRECTIVE_KEY_SET = tokensWhere((e) => e.map === true);
815
+ REGISTRY_DIRECTIVE_TOKENS = tokensWhere(
816
+ (e) => e.category === "directive" && !e.colonKey && !e.noHighlight
817
+ );
818
+ REGISTRY_CONTROL_TOKENS = tokensWhere(
819
+ (e) => e.category === "control" && !e.colonKey && !e.noHighlight
820
+ );
821
+ REGISTRY_STATUS_TOKENS = tokensWhere(
822
+ (e) => e.category === "status" && !e.colonKey && !e.noHighlight
823
+ );
824
+ REGISTRY_MODIFIER_TOKENS = tokensWhere(
825
+ (e) => e.category === "modifier" && !e.colonKey && !e.noHighlight
826
+ );
827
+ REGISTRY_COLON_KEY_TOKENS = tokensWhere((e) => !!e.colonKey);
828
+ REGISTRY_NON_HIGHLIGHT_TOKENS = tokensWhere(
829
+ (e) => !!e.noHighlight
830
+ );
831
+ }
832
+ });
833
+
432
834
  // src/utils/reserved-key-registry.ts
433
835
  function staticRegistry(keys) {
434
836
  return { keys: new Set(keys), tagAliases: /* @__PURE__ */ new Set() };
@@ -683,6 +1085,19 @@ function parseFirstLine(line11) {
683
1085
  title: trimmed.substring(spaceIdx + 1).trim() || void 0
684
1086
  };
685
1087
  }
1088
+ function detectBadChartTypeDeclaration(line11, knownOptionKeys) {
1089
+ const trimmed = line11.trim();
1090
+ if (!trimmed || trimmed.includes(":")) return null;
1091
+ const spaceIdx = trimmed.indexOf(" ");
1092
+ const token = spaceIdx === -1 ? trimmed : trimmed.substring(0, spaceIdx);
1093
+ if (!CHART_TYPE_TOKEN_RE.test(token)) return null;
1094
+ const lower = token.toLowerCase();
1095
+ if (knownOptionKeys.has(lower) || ALL_REGISTRY_TOKENS.has(lower)) return null;
1096
+ if (spaceIdx === -1) return /\d/.test(token) ? null : token;
1097
+ const rest = trimmed.substring(spaceIdx + 1).trim();
1098
+ if (!rest || !TITLE_LIKE_RE.test(rest)) return null;
1099
+ return token;
1100
+ }
686
1101
  function normalizeNumericToken(token) {
687
1102
  if (!token.includes(",") && !token.includes("_")) return null;
688
1103
  if (token.includes(",") && token.includes("_")) return null;
@@ -1028,12 +1443,13 @@ function warnUnknownMetaKeys(meta, registry, warn, nameToScan) {
1028
1443
  }
1029
1444
  }
1030
1445
  }
1031
- var RECOGNIZED_COLOR_SET, ALL_CHART_TYPES, OPTION_NOCOLON_RE, GLOBAL_BOOLEANS, FILL_FAMILY_TOKENS, RESERVED_KEY_RE, AS_ALIAS_RE, POTENTIAL_META_RE;
1446
+ var RECOGNIZED_COLOR_SET, ALL_CHART_TYPES, OPTION_NOCOLON_RE, GLOBAL_BOOLEANS, FILL_FAMILY_TOKENS, CHART_TYPE_TOKEN_RE, TITLE_LIKE_RE, RESERVED_KEY_RE, AS_ALIAS_RE, POTENTIAL_META_RE;
1032
1447
  var init_parsing = __esm({
1033
1448
  "src/utils/parsing.ts"() {
1034
1449
  "use strict";
1035
1450
  init_colors();
1036
1451
  init_diagnostics();
1452
+ init_directives_registry();
1037
1453
  init_reserved_key_registry();
1038
1454
  RECOGNIZED_COLOR_SET = new Set(
1039
1455
  RECOGNIZED_COLOR_NAMES
@@ -1107,6 +1523,8 @@ var init_parsing = __esm({
1107
1523
  "fill-solid",
1108
1524
  "fill-outline"
1109
1525
  ]);
1526
+ CHART_TYPE_TOKEN_RE = /^[A-Za-z][A-Za-z0-9-]*$/;
1527
+ TITLE_LIKE_RE = /^[A-Za-z][A-Za-z\s'&,.\-—–]*$/;
1110
1528
  RESERVED_KEY_RE = /^[A-Za-z][A-Za-z0-9_-]*$/;
1111
1529
  AS_ALIAS_RE = /\s+as\s+([A-Za-z][A-Za-z0-9_]{0,11})$/;
1112
1530
  POTENTIAL_META_RE = /\b([\w-]+)\s*:/;
@@ -4943,13 +5361,21 @@ __export(legend_constants_exports, {
4943
5361
  LEGEND_PILL_PAD: () => LEGEND_PILL_PAD,
4944
5362
  LEGEND_TOGGLE_DOT_R: () => LEGEND_TOGGLE_DOT_R,
4945
5363
  LEGEND_TOGGLE_OFF_OPACITY: () => LEGEND_TOGGLE_OFF_OPACITY,
5364
+ legendChromeColors: () => legendChromeColors,
4946
5365
  measureLegendText: () => measureLegendText,
4947
5366
  truncateLegendText: () => truncateLegendText
4948
5367
  });
5368
+ function legendChromeColors(palette, isDark) {
5369
+ return {
5370
+ groupBg: isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.textMuted, 98),
5371
+ pillBorder: mix(palette.textMuted, palette.bg, 50)
5372
+ };
5373
+ }
4949
5374
  var LEGEND_HEIGHT, LEGEND_PILL_PAD, LEGEND_PILL_FONT_SIZE, LEGEND_CAPSULE_PAD, LEGEND_DOT_R, LEGEND_ENTRY_FONT_SIZE, LEGEND_ENTRY_DOT_GAP, LEGEND_ENTRY_TRAIL, LEGEND_GROUP_GAP, LEGEND_EYE_SIZE, LEGEND_EYE_GAP, LEGEND_ICON_W, LEGEND_MAX_ENTRY_ROWS, measureLegendText, truncateLegendText, EYE_OPEN_PATH, EYE_CLOSED_PATH, CONTROLS_ICON_PATH, LEGEND_TOGGLE_DOT_R, LEGEND_TOGGLE_OFF_OPACITY, LEGEND_GEAR_PILL_W;
4950
5375
  var init_legend_constants = __esm({
4951
5376
  "src/utils/legend-constants.ts"() {
4952
5377
  "use strict";
5378
+ init_color_utils();
4953
5379
  init_text_measure();
4954
5380
  LEGEND_HEIGHT = 28;
4955
5381
  LEGEND_PILL_PAD = 16;
@@ -5528,8 +5954,7 @@ function renderLegendD3(container, config, state, palette, isDark, callbacks, co
5528
5954
  } else {
5529
5955
  legendG.attr("data-legend-active", null);
5530
5956
  }
5531
- const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
5532
- const pillBorder = mix(palette.textMuted, palette.bg, 50);
5957
+ const { groupBg, pillBorder } = legendChromeColors(palette, isDark);
5533
5958
  if (currentLayout.activeCapsule) {
5534
5959
  renderCapsule(
5535
5960
  legendG,
@@ -6032,7 +6457,7 @@ function renderTimelineGroupLegend(g, groups, groupColorMap, textColor, palette,
6032
6457
  const PAD_X = 10;
6033
6458
  const FONT_SIZE = 11;
6034
6459
  const GAP = 8;
6035
- const pillBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
6460
+ const pillBg = legendChromeColors(palette, isDark).groupBg;
6036
6461
  let legendX = 0;
6037
6462
  for (const grp of groups) {
6038
6463
  const color = groupColorMap.get(grp.name) ?? textColor;
@@ -8967,6 +9392,30 @@ A
8967
9392
 
8968
9393
  Orphan
8969
9394
  latency-ms: 20
9395
+ `
9396
+ },
9397
+ INFRA_SPLIT_SUM: {
9398
+ // src/infra/parser.ts — makeDgmoError(..., 'warning', 'W_INFRA_SPLIT_SUM')
9399
+ code: "W_INFRA_SPLIT_SUM",
9400
+ severity: "warning",
9401
+ chartType: "infra",
9402
+ title: "Infra splits do not sum to 100%",
9403
+ message: (p) => `Sync splits from '${String(p.label ?? "")}' sum to ${String(
9404
+ p.sum ?? ""
9405
+ )}%, not 100% \u2014 the traffic leaving a node must add up to the traffic entering it, so the computed downstream RPS will be wrong. Async ('~>') edges are derived streams and are excluded from this sum.`,
9406
+ hint: "Adjust the split percentages so the sync edges from this node total 100%, or drop them and let dgmo split evenly.",
9407
+ example: `infra
9408
+
9409
+ edge
9410
+ rps: 1000
9411
+ -> LB
9412
+
9413
+ LB
9414
+ -> A split: 50%
9415
+ -> B split: 30%
9416
+
9417
+ A
9418
+ B
8970
9419
  `
8971
9420
  },
8972
9421
  FLOWCHART_NODE_SUFFIX: {
@@ -14247,9 +14696,13 @@ function parseChart(content, palette) {
14247
14696
  return fail(lineNumber, msg2);
14248
14697
  }
14249
14698
  }
14250
- if (!trimmed.includes(" ") && !trimmed.includes(":") && !/\d/.test(trimmed)) {
14251
- let msg2 = `Unsupported chart type: ${trimmed}. Supported types: ${[...VALID_TYPES].join(", ")}.`;
14252
- const hint2 = suggest(trimmed.toLowerCase(), [...VALID_TYPES]);
14699
+ const badType = detectBadChartTypeDeclaration(
14700
+ trimmed,
14701
+ KNOWN_FIRST_TOKENS
14702
+ );
14703
+ if (badType) {
14704
+ let msg2 = `Unsupported chart type: ${badType}. Supported types: ${[...VALID_TYPES].join(", ")}.`;
14705
+ const hint2 = suggest(badType.toLowerCase(), [...VALID_TYPES]);
14253
14706
  if (hint2) msg2 += ` ${hint2}`;
14254
14707
  return fail(lineNumber, msg2);
14255
14708
  }
@@ -14435,6 +14888,14 @@ function parseChart(content, palette) {
14435
14888
  ...multiValue && { expectedValues: seriesCount }
14436
14889
  });
14437
14890
  if (dataValues) {
14891
+ if (multiValue) {
14892
+ const surplus = surplusValueDiagnostic(
14893
+ dataValues,
14894
+ seriesCount,
14895
+ lineNumber
14896
+ );
14897
+ if (surplus) result.diagnostics.push(surplus);
14898
+ }
14438
14899
  const { label: rawLabel, color: pointColor } = extractColor(
14439
14900
  dataValues.label,
14440
14901
  palette,
@@ -14526,34 +14987,74 @@ function parseChart(content, palette) {
14526
14987
  return result;
14527
14988
  }
14528
14989
  function parseDataRowValues(line11, options) {
14990
+ const quoted = line11.match(/^(["'])(.*?)\1\s+(\S.*)$/);
14991
+ if (quoted) {
14992
+ const label2 = quoted[2].trim();
14993
+ if (!label2) return null;
14994
+ const valueTokens = quoted[3].trim().split(/\s+/);
14995
+ const numeric2 = trailingNumerics(valueTokens, 0);
14996
+ if (numeric2.length !== valueTokens.length) return null;
14997
+ const limit = options?.multiValue ? options.expectedValues ?? Infinity : 1;
14998
+ return {
14999
+ label: label2,
15000
+ values: numeric2.slice(0, limit),
15001
+ bareLabel: label2,
15002
+ trailingNumericCount: numeric2.length,
15003
+ quotedLabel: true
15004
+ };
15005
+ }
14529
15006
  const tokens = line11.split(/\s+/);
14530
15007
  if (tokens.length < 2) return null;
15008
+ const numeric = trailingNumerics(tokens, 1);
15009
+ if (numeric.length === 0) return null;
15010
+ const bareLabel = tokens.slice(0, tokens.length - numeric.length).join(" ");
14531
15011
  if (options?.multiValue) {
14532
15012
  const limit = options.expectedValues ?? Infinity;
14533
- const values = [];
14534
- let idx = tokens.length - 1;
14535
- while (idx >= 1 && values.length < limit) {
14536
- const tok = tokens[idx];
14537
- const normTok = normalizeNumericToken(tok) ?? tok;
14538
- const num2 = parseFloat(normTok);
14539
- if (isNaN(num2) || !isFinite(Number(normTok))) break;
14540
- values.unshift(num2);
14541
- idx--;
14542
- }
14543
- if (values.length === 0) return null;
14544
- const label2 = tokens.slice(0, idx + 1).join(" ");
15013
+ const take = Math.min(numeric.length, limit);
15014
+ const values = numeric.slice(numeric.length - take);
15015
+ const label2 = tokens.slice(0, tokens.length - take).join(" ");
14545
15016
  if (!label2) return null;
14546
- return { label: label2, values };
15017
+ return {
15018
+ label: label2,
15019
+ values,
15020
+ bareLabel,
15021
+ trailingNumericCount: numeric.length,
15022
+ quotedLabel: false
15023
+ };
14547
15024
  }
14548
- const lastToken = tokens[tokens.length - 1];
14549
- const normalizedLast = normalizeNumericToken(lastToken) ?? lastToken;
14550
- const num = parseFloat(normalizedLast);
14551
- if (isNaN(num) || !isFinite(Number(normalizedLast))) return null;
14552
15025
  const label = tokens.slice(0, -1).join(" ");
14553
15026
  if (!label) return null;
14554
- return { label, values: [num] };
15027
+ return {
15028
+ label,
15029
+ // numeric.length >= 1 by the guard above.
15030
+ values: [numeric[numeric.length - 1]],
15031
+ bareLabel,
15032
+ trailingNumericCount: numeric.length,
15033
+ quotedLabel: false
15034
+ };
14555
15035
  }
14556
- var VALID_TYPES, KNOWN_OPTIONS3, DEFAULT_HOLE, KNOWN_BOOLEANS2;
15036
+ function surplusValueDiagnostic(row, expectedValues, lineNumber) {
15037
+ if (row.quotedLabel || row.trailingNumericCount <= expectedValues)
15038
+ return null;
15039
+ const expectation = expectedValues === 1 ? "but only 1 value is expected" : `but ${expectedValues} series defined`;
15040
+ return makeDgmoError(
15041
+ lineNumber,
15042
+ `Data point "${row.bareLabel}" has ${row.trailingNumericCount} value(s), ${expectation}. If the label ends in a number, quote it: "${row.label}" ${row.values.join(" ")}`,
15043
+ "warning"
15044
+ );
15045
+ }
15046
+ function trailingNumerics(tokens, minIndex) {
15047
+ const values = [];
15048
+ for (let idx = tokens.length - 1; idx >= minIndex; idx--) {
15049
+ const tok = tokens[idx];
15050
+ const normTok = normalizeNumericToken(tok) ?? tok;
15051
+ const num = parseFloat(normTok);
15052
+ if (isNaN(num) || !isFinite(Number(normTok))) break;
15053
+ values.unshift(num);
15054
+ }
15055
+ return values;
15056
+ }
15057
+ var VALID_TYPES, KNOWN_OPTIONS3, DEFAULT_HOLE, KNOWN_BOOLEANS2, KNOWN_FIRST_TOKENS;
14557
15058
  var init_chart = __esm({
14558
15059
  "src/chart.ts"() {
14559
15060
  "use strict";
@@ -14595,6 +15096,67 @@ var init_chart = __esm({
14595
15096
  "fill",
14596
15097
  "hole"
14597
15098
  ]);
15099
+ KNOWN_FIRST_TOKENS = /* @__PURE__ */ new Set([
15100
+ ...KNOWN_OPTIONS3,
15101
+ ...KNOWN_BOOLEANS2,
15102
+ ...GLOBAL_BOOLEANS
15103
+ ]);
15104
+ }
15105
+ });
15106
+
15107
+ // src/utils/emphasis.ts
15108
+ function isEmphasisToken(token) {
15109
+ return EMPHASIS_TOKENS.includes(token.toLowerCase());
15110
+ }
15111
+ function parseEmphasisDirective(line11, lineNumber) {
15112
+ const m = line11.trim().match(/^(highlight|dim)\s+(.+)$/i);
15113
+ if (!m) return null;
15114
+ const kind = m[1].toLowerCase();
15115
+ const raw = m[2].trim();
15116
+ if (!raw) return null;
15117
+ const hasComma = raw.includes(",");
15118
+ const names = (hasComma ? raw.split(",") : [raw]).map((n) => n.trim()).filter(Boolean);
15119
+ if (names.length === 0) return null;
15120
+ return { kind, names, ambiguous: !hasComma, raw, lineNumber };
15121
+ }
15122
+ function resolveEmphasis(directive, elementNames, expandHighlight) {
15123
+ if (!directive) return EMPTY;
15124
+ const norm = (s) => s.trim().toLowerCase().replace(/\s+/g, " ");
15125
+ const byNorm = /* @__PURE__ */ new Map();
15126
+ for (const name of elementNames) byNorm.set(norm(name), name);
15127
+ const match = (listed) => {
15128
+ const hit2 = /* @__PURE__ */ new Set();
15129
+ const unknown2 = [];
15130
+ for (const name of listed) {
15131
+ const found = byNorm.get(norm(name));
15132
+ if (found === void 0) unknown2.push(name);
15133
+ else hit2.add(found);
15134
+ }
15135
+ return { hit: hit2, unknown: unknown2 };
15136
+ };
15137
+ let { hit, unknown } = match(directive.names);
15138
+ if (directive.ambiguous && hit.size === 0) {
15139
+ const tokens = directive.raw.split(/\s+/).filter(Boolean);
15140
+ if (tokens.length > 1) {
15141
+ const retry = match(tokens);
15142
+ if (retry.hit.size > 0) ({ hit, unknown } = retry);
15143
+ }
15144
+ }
15145
+ if (hit.size === 0) return { dimmed: /* @__PURE__ */ new Set(), unknown };
15146
+ if (directive.kind === "dim") return { dimmed: hit, unknown };
15147
+ const lit = expandHighlight ? new Set(expandHighlight(hit)) : hit;
15148
+ const dimmed = /* @__PURE__ */ new Set();
15149
+ for (const name of byNorm.values()) if (!lit.has(name)) dimmed.add(name);
15150
+ return { dimmed, unknown };
15151
+ }
15152
+ var EMPHASIS_DIM_OPACITY, EMPHASIS_DIM_TEXT_OPACITY, EMPHASIS_TOKENS, EMPTY;
15153
+ var init_emphasis = __esm({
15154
+ "src/utils/emphasis.ts"() {
15155
+ "use strict";
15156
+ EMPHASIS_DIM_OPACITY = 0.28;
15157
+ EMPHASIS_DIM_TEXT_OPACITY = 0.5;
15158
+ EMPHASIS_TOKENS = Object.freeze(["highlight", "dim"]);
15159
+ EMPTY = { dimmed: /* @__PURE__ */ new Set(), unknown: [] };
14598
15160
  }
14599
15161
  });
14600
15162
 
@@ -14713,10 +15275,14 @@ function parseExtendedChartFull(content, palette) {
14713
15275
  return result;
14714
15276
  }
14715
15277
  }
14716
- if (!trimmed.includes(" ") && !trimmed.includes(":") && !/\d/.test(trimmed)) {
15278
+ const badType = detectBadChartTypeDeclaration(
15279
+ trimmed,
15280
+ KNOWN_EXTENDED_FIRST_TOKENS
15281
+ );
15282
+ if (badType) {
14717
15283
  const validTypes = [...VALID_EXTENDED_TYPES];
14718
- let msg = `Unsupported chart type: ${trimmed}. Supported types: ${validTypes.join(", ")}.`;
14719
- const hint = suggest(trimmed.toLowerCase(), validTypes);
15284
+ let msg = `Unsupported chart type: ${badType}. Supported types: ${validTypes.join(", ")}.`;
15285
+ const hint = suggest(badType.toLowerCase(), validTypes);
14720
15286
  if (hint) msg += ` ${hint}`;
14721
15287
  const diag = makeDgmoError(lineNumber, msg);
14722
15288
  result.diagnostics.push(diag);
@@ -14744,7 +15310,7 @@ function parseExtendedChartFull(content, palette) {
14744
15310
  continue;
14745
15311
  }
14746
15312
  const arrowMatch = trimmed.match(
14747
- /^(.+?)\s*(->|--)\s*(.+?)\s+(-?[\d_]+(?:\.[\d]+)?)(?:\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white))?\s*$/
15313
+ /^(.+?)\s*(->|--)\s*(.+?)\s+(-?[\d,_]+(?:\.[\d]+)?)(?:\s+([A-Za-z][A-Za-z0-9_-]*))?\s*$/
14748
15314
  );
14749
15315
  if (arrowMatch) {
14750
15316
  const [, rawSource, arrow, rawTarget, rawVal, rawLinkColor] = arrowMatch;
@@ -14793,12 +15359,9 @@ function parseExtendedChartFull(content, palette) {
14793
15359
  }
14794
15360
  if (sankeyStack.length > 0) {
14795
15361
  const valColorMatch = trimmed.match(
14796
- /(-?[\d_]+(?:\.[\d]+)?)\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white)\s*$/
15362
+ /(-?[\d,_]+(?:\.[\d]+)?)\s+([A-Za-z][A-Za-z0-9_-]*)\s*$/
14797
15363
  );
14798
- const strippedLine = valColorMatch ? trimmed.replace(
14799
- /\s+(red|orange|yellow|green|blue|purple|teal|cyan|gray|black|white)\s*$/,
14800
- ""
14801
- ) : trimmed;
15364
+ const strippedLine = valColorMatch ? trimmed.replace(/\s+[A-Za-z][A-Za-z0-9_-]*\s*$/, "") : trimmed;
14802
15365
  const dataRow2 = parseDataRowValues(strippedLine);
14803
15366
  if (dataRow2?.values.length === 1) {
14804
15367
  const source = sankeyStack.at(-1).name;
@@ -14839,8 +15402,9 @@ function parseExtendedChartFull(content, palette) {
14839
15402
  const isBareKeywordOption = spaceIdx2 < 0 && /^(fill-tint|fill-solid|fill-outline|no-name|no-value|no-percent|shade|no-title)$/i.test(
14840
15403
  trimmed
14841
15404
  );
14842
- const isLayoutDirective = spaceIdx2 >= 0 && trimmed.substring(0, spaceIdx2).toLowerCase() === "layout";
14843
- if (!hasNumericSuffix && !isBareKeywordOption && !isLayoutDirective) {
15405
+ const leadToken = spaceIdx2 >= 0 ? trimmed.substring(0, spaceIdx2).toLowerCase() : "";
15406
+ const isValuedDirective = spaceIdx2 >= 0 && (leadToken === "layout" || isEmphasisToken(leadToken));
15407
+ if (!hasNumericSuffix && !isBareKeywordOption && !isValuedDirective) {
14844
15408
  while (sankeyStack.length && sankeyStack.at(-1).indent >= indent) {
14845
15409
  sankeyStack.pop();
14846
15410
  }
@@ -14879,6 +15443,11 @@ function parseExtendedChartFull(content, palette) {
14879
15443
  }
14880
15444
  continue;
14881
15445
  }
15446
+ if (isEmphasisToken(firstToken)) {
15447
+ const directive = parseEmphasisDirective(trimmed, lineNumber);
15448
+ if (directive) result.emphasis = directive;
15449
+ continue;
15450
+ }
14882
15451
  if (firstToken === "title") {
14883
15452
  result.diagnostics.push(emit(TITLE_DIRECTIVE_DX, lineNumber));
14884
15453
  continue;
@@ -15176,7 +15745,7 @@ function getExtendedChartLegendGroups(parsed, colors) {
15176
15745
  }
15177
15746
  return [];
15178
15747
  }
15179
- var VALID_EXTENDED_TYPES, KNOWN_EXTENDED_OPTIONS, EXTENDED_CHART_DOORS;
15748
+ var VALID_EXTENDED_TYPES, KNOWN_EXTENDED_OPTIONS, KNOWN_EXTENDED_FIRST_TOKENS, EXTENDED_CHART_DOORS;
15180
15749
  var init_data_chart_parser = __esm({
15181
15750
  "src/data-chart-parser.ts"() {
15182
15751
  "use strict";
@@ -15184,6 +15753,7 @@ var init_data_chart_parser = __esm({
15184
15753
  init_colors();
15185
15754
  init_parsing();
15186
15755
  init_chart();
15756
+ init_emphasis();
15187
15757
  VALID_EXTENDED_TYPES = /* @__PURE__ */ new Set([
15188
15758
  "sankey",
15189
15759
  "chord",
@@ -15205,7 +15775,13 @@ var init_data_chart_parser = __esm({
15205
15775
  "no-percent",
15206
15776
  "columns",
15207
15777
  "rows",
15208
- "x"
15778
+ "x",
15779
+ "highlight",
15780
+ "dim"
15781
+ ]);
15782
+ KNOWN_EXTENDED_FIRST_TOKENS = /* @__PURE__ */ new Set([
15783
+ ...KNOWN_EXTENDED_OPTIONS,
15784
+ ...GLOBAL_BOOLEANS
15209
15785
  ]);
15210
15786
  EXTENDED_CHART_DOORS = /* @__PURE__ */ new Set([
15211
15787
  parseSankey,
@@ -16865,6 +17441,13 @@ function parseC4(content, palette) {
16865
17441
  const optMatch = trimmed.match(OPTION_NOCOLON_RE);
16866
17442
  if (optMatch) {
16867
17443
  const key = optMatch[1].trim().toLowerCase();
17444
+ if (key === "layout") {
17445
+ pushError(
17446
+ lineNumber,
17447
+ `'layout' is not a valid C4 option \u2014 C4 diagrams have a single layered layout with no algorithm to select. Use the 'direction-tb' (default) or 'direction-lr' booleans to change orientation (\xA77.7).`
17448
+ );
17449
+ continue;
17450
+ }
16868
17451
  if (KNOWN_C4_OPTIONS.has(key)) {
16869
17452
  options[key] = optMatch[2].trim();
16870
17453
  continue;
@@ -17463,7 +18046,7 @@ var init_parser16 = __esm({
17463
18046
  "cloud",
17464
18047
  "external"
17465
18048
  ]);
17466
- KNOWN_C4_OPTIONS = /* @__PURE__ */ new Set(["layout", "active-tag"]);
18049
+ KNOWN_C4_OPTIONS = /* @__PURE__ */ new Set(["active-tag"]);
17467
18050
  KNOWN_C4_BOOLEANS = /* @__PURE__ */ new Set([
17468
18051
  "direction-tb",
17469
18052
  "direction-lr",
@@ -17494,390 +18077,6 @@ var init_parser16 = __esm({
17494
18077
  }
17495
18078
  });
17496
18079
 
17497
- // src/directives-registry.ts
17498
- function tokensWhere(pred) {
17499
- const out = /* @__PURE__ */ new Set();
17500
- for (const e of DIRECTIVES_REGISTRY) if (pred(e)) out.add(e.token);
17501
- return out;
17502
- }
17503
- var DIRECTIVES_REGISTRY, INFRA_TOP_LEVEL_OPTION_SET, INFRA_BEHAVIOR_KEY_SET, INFRA_EDGE_ONLY_KEY_SET, GANTT_OPTION_SET, GANTT_BOOLEAN_SET, MAP_DIRECTIVE_KEY_SET, REGISTRY_DIRECTIVE_TOKENS, REGISTRY_CONTROL_TOKENS, REGISTRY_STATUS_TOKENS, REGISTRY_MODIFIER_TOKENS, REGISTRY_COLON_KEY_TOKENS, REGISTRY_NON_HIGHLIGHT_TOKENS;
17504
- var init_directives_registry = __esm({
17505
- "src/directives-registry.ts"() {
17506
- "use strict";
17507
- DIRECTIVES_REGISTRY = [
17508
- // ── Universal date handling (§ BL-121) ───────────────────
17509
- // Accepted in every date-bearing chart (gantt, pert, countdown, timeline,
17510
- // event-line). Handled inline by each parser's prescan — no parser-Set
17511
- // membership here — so they only contribute highlighting (DIRECTIVE_KEYWORDS).
17512
- { token: "year", category: "directive" },
17513
- { token: "date-order", category: "directive" },
17514
- { token: "no-current-year", category: "directive" },
17515
- // ── Infra ────────────────────────────────────────────────
17516
- // Top-level SLO options that are ALSO accepted as node colon-properties
17517
- // (dual membership). Highlight bare (kept in DIRECTIVE_KEYWORDS) — their
17518
- // rarer colon form stays keyword, an accepted minor false-positive.
17519
- {
17520
- token: "slo-availability",
17521
- category: "directive",
17522
- infra: ["top-level", "behavior"]
17523
- },
17524
- {
17525
- token: "slo-p90-latency-ms",
17526
- category: "directive",
17527
- infra: ["top-level", "behavior"]
17528
- },
17529
- {
17530
- token: "slo-warning-margin",
17531
- category: "directive",
17532
- infra: ["top-level", "behavior"]
17533
- },
17534
- // Top-level-only options (space-separated, bare).
17535
- { token: "default-latency-ms", category: "directive", infra: ["top-level"] },
17536
- { token: "default-uptime", category: "directive", infra: ["top-level"] },
17537
- { token: "default-rps", category: "directive", infra: ["top-level"] },
17538
- // `active-tag` is shared with gantt + map (see below).
17539
- // Node behavior colon-keys — highlight as propertyName.
17540
- { token: "latency-ms", colonKey: true, infra: ["behavior"] },
17541
- { token: "uptime", colonKey: true, infra: ["behavior"] },
17542
- { token: "instances", colonKey: true, infra: ["behavior"] },
17543
- { token: "max-rps", colonKey: true, infra: ["behavior"] },
17544
- { token: "cache-hit", colonKey: true, infra: ["behavior"] },
17545
- { token: "firewall-block", colonKey: true, infra: ["behavior"] },
17546
- { token: "ratelimit-rps", colonKey: true, infra: ["behavior"] },
17547
- { token: "buffer", colonKey: true, infra: ["behavior"] },
17548
- { token: "drain-rate", colonKey: true, infra: ["behavior"] },
17549
- { token: "retention-hours", colonKey: true, infra: ["behavior"] },
17550
- { token: "partitions", colonKey: true, infra: ["behavior"] },
17551
- { token: "concurrency", colonKey: true, infra: ["behavior"] },
17552
- { token: "duration-ms", colonKey: true, infra: ["behavior"] },
17553
- { token: "cold-start-ms", colonKey: true, infra: ["behavior"] },
17554
- { token: "cb-error-threshold", colonKey: true, infra: ["behavior"] },
17555
- { token: "cb-latency-threshold-ms", colonKey: true, infra: ["behavior"] },
17556
- // Edge-only colon-key.
17557
- { token: "rps", colonKey: true, infra: ["edge"] },
17558
- // ── Gantt ────────────────────────────────────────────────
17559
- { token: "start", category: "directive", gantt: ["option"] },
17560
- {
17561
- token: "today-marker",
17562
- category: "directive",
17563
- gantt: ["option", "boolean"]
17564
- },
17565
- {
17566
- token: "critical-path",
17567
- category: "directive",
17568
- gantt: ["option", "boolean"]
17569
- },
17570
- { token: "sprint-length", category: "directive", gantt: ["option"] },
17571
- { token: "sprint-number", category: "directive", gantt: ["option"] },
17572
- { token: "sprint-start", category: "directive", gantt: ["option"] },
17573
- { token: "fill-tint", category: "directive", gantt: ["boolean"] },
17574
- { token: "fill-solid", category: "directive", gantt: ["boolean"] },
17575
- { token: "fill-outline", category: "directive", gantt: ["boolean"] },
17576
- // `chart` is a wireframe element keyword (CONTROL); gantt also accepts it as
17577
- // an option. Highlight family is control.
17578
- { token: "chart", category: "control", gantt: ["option"] },
17579
- // `dependencies` is ON by default → almost never written bare (authors write
17580
- // `no-dependencies`, which DOES highlight). Bare word collides with prose and
17581
- // the specializer is context-free, so it stays plain. Present for parser
17582
- // Set derivation only.
17583
- { token: "dependencies", noHighlight: true, gantt: ["option", "boolean"] },
17584
- // ── Map ──────────────────────────────────────────────────
17585
- // Per-element value→channel ramps (decision #20): the directive names the
17586
- // visual channel — `region-heat` (colour), `poi-size` (marker area),
17587
- // `flow-width` (stroke). The per-element key mirrors it (`heat:`/`size:`/`width:`).
17588
- { token: "region-heat", category: "directive", map: true },
17589
- { token: "poi-size", category: "directive", map: true },
17590
- { token: "flow-width", category: "directive", map: true },
17591
- { token: "locale", category: "directive", map: true },
17592
- { token: "caption", category: "directive", map: true },
17593
- { token: "no-coastline", category: "directive", map: true },
17594
- { token: "no-relief", category: "directive", map: true },
17595
- { token: "no-context-labels", category: "directive", map: true },
17596
- { token: "no-region-labels", category: "directive", map: true },
17597
- { token: "no-region-heat-value", category: "directive", map: true },
17598
- { token: "no-poi-labels", category: "directive", map: true },
17599
- { token: "no-colorize", category: "directive", map: true },
17600
- { token: "no-cities", category: "directive", map: true },
17601
- { token: "no-cluster-pois", category: "directive", map: true },
17602
- // ── Clock channel (BL-122): live local-time cards on POIs flagged `clock` ──
17603
- // Activation is the per-POI `clock` flag (peeled in the map parser, not a
17604
- // directive). `hours 9-17` / `days mon-fri` are the global availability
17605
- // window (reused verbatim from the clock chart type).
17606
- { token: "hours", category: "directive", map: true },
17607
- { token: "days", category: "directive", map: true },
17608
- // ── Shared across types ──────────────────────────────────
17609
- // `active-tag`: infra top-level option + gantt option + map directive.
17610
- // Treemap also accepts it (§24C.6, decision #48) — validated inline in its
17611
- // parser, so no chart-scoped Set is derived for it; highlighting comes from
17612
- // the global directive category here.
17613
- {
17614
- token: "active-tag",
17615
- category: "directive",
17616
- infra: ["top-level"],
17617
- gantt: ["option"],
17618
- map: true
17619
- },
17620
- // `title`: gantt option. (The data-chart `title` directive was removed in
17621
- // decision #48 — it now raises E_TITLE_DIRECTIVE; the token stays here for
17622
- // gantt.)
17623
- { token: "title", category: "directive", gantt: ["option"] },
17624
- // `sort`: gantt option (also general, hand-listed).
17625
- { token: "sort", category: "directive", gantt: ["option"] },
17626
- // `lane-by <group>`: swimlane axis (view-state). NB not `swimlane` — that's a
17627
- // chart type. Kanban reads it via its own KNOWN_OPTIONS; gantt via the switch.
17628
- { token: "lane-by", category: "directive", gantt: ["option"] },
17629
- // `no-title`: gantt boolean + map directive (also general, hand-listed).
17630
- { token: "no-title", category: "directive", gantt: ["boolean"], map: true },
17631
- // `no-legend`: universal (decision #48) — every chart that renders a legend
17632
- // accepts it, and charts without one take it as a harmless no-op. Parsed for
17633
- // most types via GLOBAL_BOOLEANS in utils/parsing; gantt derives its boolean
17634
- // Set from here, and map from `map: true`.
17635
- { token: "no-legend", category: "directive", gantt: ["boolean"], map: true },
17636
- // ══════════════════════════════════════════════════════════
17637
- // Highlight-only vocab (no extractable parser Set).
17638
- // ══════════════════════════════════════════════════════════
17639
- // These chart types validate their directives with inline checks rather than
17640
- // a literal Set, so there is no parser literal to delete — the registry is
17641
- // simply their single declaration site for highlighting. Grouped by chart
17642
- // type. Categories route to the specializer-read sets in keywords.ts.
17643
- // ── Gantt (extras beyond the option/boolean Sets) ────────
17644
- { token: "era", category: "directive" },
17645
- { token: "marker", category: "directive" },
17646
- { token: "holiday", category: "directive" },
17647
- // `workweek` is also the canonical weekday-window directive on clock and map
17648
- // clock-cards (decision #48; `days` stays as the legacy alias).
17649
- { token: "workweek", category: "directive", map: true },
17650
- { token: "no-dependencies", category: "directive" },
17651
- // ── Tech-radar ───────────────────────────────────────────
17652
- { token: "rings", category: "directive" },
17653
- // Blip listing is default-on everywhere (decision #48): `no-blip-legend`
17654
- // suppresses it; `show-blip-legend` stays as the parse-accepted legacy no-op.
17655
- { token: "no-blip-legend", category: "directive" },
17656
- { token: "show-blip-legend", category: "directive" },
17657
- { token: "trend", category: "directive" },
17658
- // ── Tags / shared directives ─────────────────────────────
17659
- { token: "tags", category: "directive" },
17660
- { token: "import", category: "directive" },
17661
- { token: "hide", category: "directive" },
17662
- { token: "direction", category: "directive" },
17663
- // ── Clock ────────────────────────────────────────────────
17664
- // The board's flat directives — highlight-only (the parser validates them
17665
- // inline, no extractable Set). `no-title` + `direction` are already listed
17666
- // above (shared). Values (analog is a bare flag; color-by's
17667
- // place|work|daylight|time|none) highlight as plain tokens.
17668
- { token: "analog", category: "directive" },
17669
- { token: "hours", category: "directive" },
17670
- { token: "days", category: "directive" },
17671
- { token: "no-sun", category: "directive" },
17672
- { token: "time-24", category: "directive" },
17673
- { token: "color-by", category: "directive" },
17674
- // ── Boxes-and-lines ──────────────────────────────────────
17675
- // `heat <Label> [low] [high]` is the value→colour ramp directive, but `heat`
17676
- // is NOT registered as a directive keyword: it is dual-use with the `heat:`
17677
- // metadata key (decision #20), and a token in DIRECTIVE_KEYWORDS would block the
17678
- // ATTRIBUTE_KEYS colon-gate from reclassifying `heat:` as a propertyName. So
17679
- // `heat` lives only in the reserved-key registry + ATTRIBUTE_KEYS and highlights
17680
- // as a property in both positions — exactly the treemap `heat` precedent.
17681
- // Box values render by default since decision #48; `show-values` stays as
17682
- // the parse-accepted legacy no-op (suppress with the shared `no-value`).
17683
- { token: "show-values", category: "directive" },
17684
- // Treemap's legacy plural spelling of the same suppression (decision #48
17685
- // made the shared singular `no-value` canonical). Registered so a legacy
17686
- // document still highlights it as a directive rather than plain text.
17687
- { token: "no-values", category: "directive" },
17688
- // ── Swimlane ─────────────────────────────────────────────
17689
- { token: "lane", category: "directive" },
17690
- // ── ER ───────────────────────────────────────────────────
17691
- { token: "notation", category: "directive" },
17692
- // ── Class ────────────────────────────────────────────────
17693
- { token: "extends", category: "directive" },
17694
- { token: "implements", category: "directive" },
17695
- { token: "abstract", category: "directive" },
17696
- { token: "interface", category: "directive" },
17697
- { token: "enum", category: "directive" },
17698
- // ── C4 ───────────────────────────────────────────────────
17699
- { token: "containers", category: "directive" },
17700
- { token: "components", category: "directive" },
17701
- { token: "deployment", category: "directive" },
17702
- { token: "technology", category: "directive" },
17703
- // ── Infra (directives beyond the option/behavior Sets) ───
17704
- { token: "sub-node-label", category: "directive" },
17705
- { token: "show-sub-node-count", category: "directive" },
17706
- { token: "animate", category: "directive" },
17707
- // ── Sequence ─────────────────────────────────────────────
17708
- { token: "activations", category: "directive" },
17709
- { token: "no-activations", category: "directive" },
17710
- // ── Map element leaders (not in DIRECTIVE_SET) ───────────
17711
- { token: "poi", category: "directive" },
17712
- { token: "route", category: "directive" },
17713
- // ── Data charts ──────────────────────────────────────────
17714
- { token: "stacked", category: "directive" },
17715
- { token: "no-name", category: "directive" },
17716
- { token: "no-value", category: "directive" },
17717
- { token: "no-percent", category: "directive" },
17718
- { token: "series", category: "directive" },
17719
- { token: "orientation", category: "directive" },
17720
- { token: "x-label", category: "directive" },
17721
- { token: "y-label", category: "directive" },
17722
- { token: "size-label", category: "directive" },
17723
- { token: "columns", category: "directive" },
17724
- { token: "rows", category: "directive" },
17725
- { token: "labels", category: "directive" },
17726
- { token: "rotate", category: "directive" },
17727
- { token: "scale", category: "directive" },
17728
- { token: "values", category: "directive" },
17729
- { token: "orientation-horizontal", category: "directive" },
17730
- // ── Slope ────────────────────────────────────────────────
17731
- { token: "period", category: "directive" },
17732
- // ── Quadrant ─────────────────────────────────────────────
17733
- { token: "x-axis", category: "directive" },
17734
- { token: "y-axis", category: "directive" },
17735
- { token: "top-right", category: "directive" },
17736
- { token: "top-left", category: "directive" },
17737
- { token: "bottom-right", category: "directive" },
17738
- { token: "bottom-left", category: "directive" },
17739
- // ── Layout (cross-chart) ─────────────────────────────────
17740
- { token: "direction-tb", category: "directive" },
17741
- { token: "direction-lr", category: "directive" },
17742
- { token: "orientation-vertical", category: "directive" },
17743
- // ── Pyramid ──────────────────────────────────────────────
17744
- { token: "inverted", category: "directive" },
17745
- // ── Color / notes (cross-chart) ──────────────────────────
17746
- { token: "color", category: "directive" },
17747
- { token: "no-notes", category: "directive" },
17748
- // ── RACI ─────────────────────────────────────────────────
17749
- { token: "roles", category: "directive" },
17750
- // ── Cycle ────────────────────────────────────────────────
17751
- { token: "direction-counterclockwise", category: "directive" },
17752
- { token: "circle-nodes", category: "directive" },
17753
- // ── Journey-map ──────────────────────────────────────────
17754
- { token: "persona", category: "directive" },
17755
- // ── Function ─────────────────────────────────────────────
17756
- { token: "x", category: "directive" },
17757
- { token: "shade", category: "directive" },
17758
- // ── Wordcloud ────────────────────────────────────────────
17759
- { token: "max", category: "directive" },
17760
- { token: "size", category: "directive" },
17761
- // ── Arc ──────────────────────────────────────────────────
17762
- { token: "order", category: "directive" },
17763
- // ── PERT ─────────────────────────────────────────────────
17764
- { token: "time-unit", category: "directive" },
17765
- { token: "default-confidence", category: "directive" },
17766
- { token: "node-detail", category: "directive" },
17767
- { token: "trials", category: "directive" },
17768
- { token: "seed", category: "directive" },
17769
- { token: "scrubber-trials", category: "directive" },
17770
- // `start-date` is also gantt's canonical project-start option (decision #48;
17771
- // bare `start` above stays as the legacy alias).
17772
- { token: "start-date", category: "directive", gantt: ["option"] },
17773
- { token: "end-date", category: "directive" },
17774
- // ── Goal ─────────────────────────────────────────────────
17775
- // Mode flags, value-key leaders, and opt-outs. `now`/`target` collide with
17776
- // label prose ("start now", "on target"), but the arrow-label demotion pass
17777
- // (see highlight-api LABEL_WORD_NODES) treats any keyword-bearing label as
17778
- // text and demotes them, so they highlight only as line leaders.
17779
- { token: "thermometer", category: "directive" },
17780
- { token: "gauge", category: "directive" },
17781
- { token: "now", category: "directive" },
17782
- { token: "target", category: "directive" },
17783
- { token: "no-note", category: "directive" },
17784
- { token: "no-auto-color", category: "directive" },
17785
- // ── Control-flow + wireframe element keywords (CONTROL) ──
17786
- { token: "if", category: "control" },
17787
- { token: "else", category: "control" },
17788
- { token: "loop", category: "control" },
17789
- { token: "parallel", category: "control" },
17790
- { token: "note", category: "control" },
17791
- { token: "nav", category: "control" },
17792
- { token: "tabs", category: "control" },
17793
- { token: "table", category: "control" },
17794
- { token: "image", category: "control" },
17795
- { token: "modal", category: "control" },
17796
- { token: "skeleton", category: "control" },
17797
- { token: "alert", category: "control" },
17798
- { token: "progress", category: "control" },
17799
- { token: "mobile", category: "control" },
17800
- // ── Kanban status keywords (STATUS) ──────────────────────
17801
- { token: "na", category: "status" },
17802
- { token: "todo", category: "status" },
17803
- { token: "wip", category: "status" },
17804
- { token: "done", category: "status" },
17805
- { token: "blocked", category: "status" },
17806
- { token: "in-progress", category: "status" },
17807
- { token: "backlog", category: "status" },
17808
- { token: "ready", category: "status" },
17809
- // ── Modifiers + ER column types/modifiers (MODIFIER) ─────
17810
- // `collapsed` is the bare trailing group-line flag made canonical by
17811
- // decision #48 (`[Fulfillment] collapsed`). It is deliberately a bare
17812
- // MODIFIER rather than a colon key: the legacy `collapsed: true` form still
17813
- // parses and still highlights (as `modifier` instead of `propertyName`),
17814
- // whereas leaving it colon-only left the canonical bare form as plain text.
17815
- { token: "collapsed", category: "modifier" },
17816
- { token: "as", category: "modifier" },
17817
- { token: "alias", category: "modifier" },
17818
- { token: "aka", category: "modifier" },
17819
- { token: "position", category: "modifier" },
17820
- { token: "default", category: "modifier" },
17821
- { token: "pk", category: "modifier" },
17822
- { token: "fk", category: "modifier" },
17823
- { token: "nullable", category: "modifier" },
17824
- { token: "unique", category: "modifier" },
17825
- { token: "int", category: "modifier" },
17826
- { token: "varchar", category: "modifier" },
17827
- { token: "text", category: "modifier" },
17828
- { token: "boolean", category: "modifier" },
17829
- { token: "date", category: "modifier" },
17830
- { token: "timestamp", category: "modifier" },
17831
- { token: "float", category: "modifier" },
17832
- { token: "decimal", category: "modifier" },
17833
- // ── Bracket ──────────────────────────────────────────────
17834
- // `beats` / `vs` are the infix match keywords; the rest are line directives.
17835
- // (`seed` and `no-legend` already appear above — shared, not re-listed.)
17836
- { token: "beats", category: "control" },
17837
- { token: "vs", category: "control" },
17838
- { token: "rounds", category: "directive" },
17839
- { token: "accent", category: "directive" },
17840
- { token: "no-round", category: "modifier" },
17841
- { token: "no-rounds", category: "modifier" },
17842
- { token: "single-elim", category: "modifier" },
17843
- { token: "double-elim", category: "modifier" },
17844
- { token: "seeded", category: "modifier" }
17845
- ];
17846
- INFRA_TOP_LEVEL_OPTION_SET = tokensWhere(
17847
- (e) => !!e.infra?.includes("top-level")
17848
- );
17849
- INFRA_BEHAVIOR_KEY_SET = tokensWhere(
17850
- (e) => !!e.infra?.includes("behavior")
17851
- );
17852
- INFRA_EDGE_ONLY_KEY_SET = tokensWhere(
17853
- (e) => !!e.infra?.includes("edge")
17854
- );
17855
- GANTT_OPTION_SET = tokensWhere(
17856
- (e) => !!e.gantt?.includes("option")
17857
- );
17858
- GANTT_BOOLEAN_SET = tokensWhere(
17859
- (e) => !!e.gantt?.includes("boolean")
17860
- );
17861
- MAP_DIRECTIVE_KEY_SET = tokensWhere((e) => e.map === true);
17862
- REGISTRY_DIRECTIVE_TOKENS = tokensWhere(
17863
- (e) => e.category === "directive" && !e.colonKey && !e.noHighlight
17864
- );
17865
- REGISTRY_CONTROL_TOKENS = tokensWhere(
17866
- (e) => e.category === "control" && !e.colonKey && !e.noHighlight
17867
- );
17868
- REGISTRY_STATUS_TOKENS = tokensWhere(
17869
- (e) => e.category === "status" && !e.colonKey && !e.noHighlight
17870
- );
17871
- REGISTRY_MODIFIER_TOKENS = tokensWhere(
17872
- (e) => e.category === "modifier" && !e.colonKey && !e.noHighlight
17873
- );
17874
- REGISTRY_COLON_KEY_TOKENS = tokensWhere((e) => !!e.colonKey);
17875
- REGISTRY_NON_HIGHLIGHT_TOKENS = tokensWhere(
17876
- (e) => !!e.noHighlight
17877
- );
17878
- }
17879
- });
17880
-
17881
18080
  // src/infra/types.ts
17882
18081
  var INFRA_BEHAVIOR_KEYS, EDGE_ONLY_KEYS;
17883
18082
  var init_types2 = __esm({
@@ -18632,8 +18831,35 @@ function parseInfra(content) {
18632
18831
  }
18633
18832
  validateTagGroupNames(result.tagGroups, warn, setError);
18634
18833
  checkReachability(result);
18834
+ checkSplitSums(result);
18635
18835
  return result;
18636
18836
  }
18837
+ function checkSplitSums(result) {
18838
+ const outbound = /* @__PURE__ */ new Map();
18839
+ for (const edge of result.edges) {
18840
+ if (edge.async) continue;
18841
+ const list = outbound.get(edge.sourceId) ?? [];
18842
+ list.push(edge);
18843
+ outbound.set(edge.sourceId, list);
18844
+ }
18845
+ for (const [sourceId, edges] of outbound) {
18846
+ if (edges.length <= 1) continue;
18847
+ const declared = edges.filter((e) => e.split !== null);
18848
+ if (declared.length === 0) continue;
18849
+ const sum = declared.reduce((s, e) => s + (e.split ?? 0), 0);
18850
+ const allDeclared = declared.length === edges.length;
18851
+ if (allDeclared ? Math.abs(sum - 100) > 0.01 : sum > 100) {
18852
+ const label = result.nodes.find((n) => n.id === sourceId)?.label ?? sourceId;
18853
+ result.diagnostics.push(
18854
+ // In-bounds: declared.length > 0 here.
18855
+ emit(GRAPH_DX.INFRA_SPLIT_SUM, declared[0].lineNumber, {
18856
+ label,
18857
+ sum: String(Number(sum.toFixed(2)))
18858
+ })
18859
+ );
18860
+ }
18861
+ }
18862
+ }
18637
18863
  function checkReachability(result) {
18638
18864
  if (result.nodes.length === 0) return;
18639
18865
  const entries = result.nodes.filter((n) => n.isEdge);
@@ -25926,6 +26152,21 @@ var init_diagnostics9 = __esm({
25926
26152
  hint: "Add a trailing number (e.g. `Lonely 40`) so the leaf gets a size.",
25927
26153
  example: "treemap T\nA\n Lonely"
25928
26154
  },
26155
+ LEAF_VALUE_UNPARSEABLE: {
26156
+ code: "W_TREEMAP_LEAF_VALUE_UNPARSEABLE",
26157
+ severity: "warning",
26158
+ chartType: "treemap",
26159
+ title: "Leaf value could not be read as a number",
26160
+ message: (p) => {
26161
+ const token = p.token ?? "1,24,000";
26162
+ return `Leaf "${p.label ?? "Cluster"}" \u2014 could not read "${token}" as a number, so it renders with zero size. Remove the grouping separators: ${token.replace(
26163
+ /[,_]/g,
26164
+ ""
26165
+ )}`;
26166
+ },
26167
+ hint: "Grouping separators must split the digits into even groups \u2014 `1,240,000` or `1_240_000`. Anything else must be written plainly, e.g. `1240000`.",
26168
+ example: "treemap T\nA\n Cluster 1,24,000"
26169
+ },
25929
26170
  BRANCH_VALUE_IGNORED: {
25930
26171
  code: "W_TREEMAP_BRANCH_VALUE_IGNORED",
25931
26172
  severity: "warning",
@@ -25985,6 +26226,7 @@ function parseTreemap(content, palette) {
25985
26226
  const lines = content.split("\n");
25986
26227
  let contentStarted = false;
25987
26228
  let nodeCounter = 0;
26229
+ const badValueTokens = /* @__PURE__ */ new Map();
25988
26230
  let currentTagGroup = null;
25989
26231
  const aliasMap = /* @__PURE__ */ new Map();
25990
26232
  const indentStack = [];
@@ -26079,7 +26321,8 @@ function parseTreemap(content, palette) {
26079
26321
  lineNumber,
26080
26322
  ++nodeCounter,
26081
26323
  aliasMap,
26082
- result.diagnostics
26324
+ result.diagnostics,
26325
+ badValueTokens
26083
26326
  );
26084
26327
  if (node.heat !== void 0) result.hasHeat = true;
26085
26328
  attachNode4(node, indent, indentStack, result);
@@ -26106,8 +26349,12 @@ function parseTreemap(content, palette) {
26106
26349
  delete node.value;
26107
26350
  }
26108
26351
  } else if (node.value === void 0) {
26352
+ const bad = badValueTokens.get(n);
26109
26353
  result.diagnostics.push(
26110
- emit(TREEMAP_DX.LEAF_NO_VALUE, node.lineNumber, {
26354
+ bad !== void 0 ? emit(TREEMAP_DX.LEAF_VALUE_UNPARSEABLE, node.lineNumber, {
26355
+ label: node.label,
26356
+ token: bad
26357
+ }) : emit(TREEMAP_DX.LEAF_NO_VALUE, node.lineNumber, {
26111
26358
  label: node.label
26112
26359
  })
26113
26360
  );
@@ -26214,7 +26461,7 @@ function resolveDefaultColorMode(result, pushWarning) {
26214
26461
  if (result.tagGroups.length > 0) return "tag";
26215
26462
  return "branch";
26216
26463
  }
26217
- function parseNodeLine3(trimmed, lineNumber, counter, aliasMap, diagnostics) {
26464
+ function parseNodeLine3(trimmed, lineNumber, counter, aliasMap, diagnostics, badValueTokens) {
26218
26465
  const registry = withTagAliases(TREEMAP_REGISTRY, new Set(aliasMap.keys()));
26219
26466
  const split = splitNameAndMeta(
26220
26467
  trimmed,
@@ -26233,13 +26480,15 @@ function parseNodeLine3(trimmed, lineNumber, counter, aliasMap, diagnostics) {
26233
26480
  const metadata = { ...split.meta };
26234
26481
  let heat;
26235
26482
  if ("heat" in metadata) {
26236
- const h = parseFloat(metadata["heat"].replace(/_/g, ""));
26483
+ const rawHeat = metadata["heat"];
26484
+ const h = parseFloat(normalizeNumericToken(rawHeat) ?? rawHeat);
26237
26485
  if (Number.isFinite(h)) heat = h;
26238
26486
  delete metadata["heat"];
26239
26487
  }
26240
26488
  const rawName = split.name;
26241
26489
  let label;
26242
26490
  let value;
26491
+ let badValueToken;
26243
26492
  if (isFullyQuoted(rawName)) {
26244
26493
  label = stripQuotes(rawName);
26245
26494
  } else {
@@ -26247,11 +26496,16 @@ function parseNodeLine3(trimmed, lineNumber, counter, aliasMap, diagnostics) {
26247
26496
  if (vm) {
26248
26497
  const labelPart = vm[1].trim();
26249
26498
  const token = vm[2];
26250
- const cleaned = token.replace(/_/g, "");
26251
- const parsed = parseFloat(cleaned);
26499
+ const normalized = normalizeNumericToken(token);
26500
+ const hasSeparators = /[,_]/.test(token);
26501
+ const parsed = hasSeparators && normalized === null ? NaN : parseFloat(normalized ?? token);
26502
+ const peeled = isFullyQuoted(labelPart) ? stripQuotes(labelPart) : labelPart;
26252
26503
  if (Number.isFinite(parsed)) {
26253
26504
  value = parsed;
26254
- label = isFullyQuoted(labelPart) ? stripQuotes(labelPart) : labelPart;
26505
+ label = peeled;
26506
+ } else if (hasSeparators) {
26507
+ label = peeled;
26508
+ badValueToken = token;
26255
26509
  } else {
26256
26510
  label = rawName;
26257
26511
  }
@@ -26259,7 +26513,7 @@ function parseNodeLine3(trimmed, lineNumber, counter, aliasMap, diagnostics) {
26259
26513
  label = rawName;
26260
26514
  }
26261
26515
  }
26262
- return {
26516
+ const node = {
26263
26517
  id: `treemap-node-${counter}`,
26264
26518
  label,
26265
26519
  ...value !== void 0 && { value },
@@ -26268,6 +26522,8 @@ function parseNodeLine3(trimmed, lineNumber, counter, aliasMap, diagnostics) {
26268
26522
  children: [],
26269
26523
  lineNumber
26270
26524
  };
26525
+ if (badValueToken !== void 0) badValueTokens.set(node, badValueToken);
26526
+ return node;
26271
26527
  }
26272
26528
  function attachNode4(node, indent, indentStack, result) {
26273
26529
  while (indentStack.length > 0) {
@@ -26292,7 +26548,7 @@ var init_parser31 = __esm({
26292
26548
  init_tag_groups();
26293
26549
  init_parsing();
26294
26550
  init_reserved_key_registry();
26295
- VALUE_TOKEN_RE = /^(.+?)\s+(-?\d[\d_.]*)$/;
26551
+ VALUE_TOKEN_RE = /^(.+?)\s+(-?\d[\d_,.]*)$/;
26296
26552
  }
26297
26553
  });
26298
26554
 
@@ -26637,8 +26893,9 @@ __export(parser_exports27, {
26637
26893
  parseGoal: () => parseGoal
26638
26894
  });
26639
26895
  function parseGoalNumber(token) {
26640
- if (token.includes(",")) return null;
26641
- const n = parseFloat(token.replace(/_/g, ""));
26896
+ const normalized = normalizeNumericToken(token);
26897
+ if (normalized === null && /[,_]/.test(token)) return null;
26898
+ const n = parseFloat(normalized ?? token);
26642
26899
  return Number.isFinite(n) ? n : null;
26643
26900
  }
26644
26901
  function parseGoal(content, palette) {
@@ -33243,8 +33500,7 @@ function renderLegendSvg(groups, options) {
33243
33500
  if (!layout.activeCapsule && layout.pills.length === 0) {
33244
33501
  return { svg: "", height: 0, width: 0 };
33245
33502
  }
33246
- const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
33247
- const pillBorder = mix(palette.textMuted, palette.bg, 50);
33503
+ const { groupBg, pillBorder } = legendChromeColors(palette, isDark);
33248
33504
  const parts = [];
33249
33505
  let bottom = 0;
33250
33506
  let left = Infinity;
@@ -33278,7 +33534,6 @@ var init_legend_svg = __esm({
33278
33534
  init_tag_groups();
33279
33535
  init_legend_constants();
33280
33536
  init_legend_layout();
33281
- init_color_utils();
33282
33537
  init_fonts();
33283
33538
  }
33284
33539
  });
@@ -34403,6 +34658,25 @@ function renderSankey(svg, chart, width, height, colors, bgColor, textColor, top
34403
34658
  if (links.length === 0) return;
34404
34659
  const solid = chart.fillMode === "solid";
34405
34660
  const names = Array.from(new Set(links.flatMap((l) => [l.source, l.target])));
34661
+ const walk = (seeds, up) => {
34662
+ const seen = new Set(seeds);
34663
+ for (let pass = 0; pass < names.length; pass++) {
34664
+ let grew = false;
34665
+ for (const l of links) {
34666
+ const from = up ? l.target : l.source;
34667
+ const to = up ? l.source : l.target;
34668
+ if (seen.has(from) && !seen.has(to)) {
34669
+ seen.add(to);
34670
+ grew = true;
34671
+ }
34672
+ }
34673
+ if (!grew) break;
34674
+ }
34675
+ return seen;
34676
+ };
34677
+ const flowClosure = (seeds) => /* @__PURE__ */ new Set([...walk(seeds, true), ...walk(seeds, false)]);
34678
+ const { dimmed } = resolveEmphasis(chart.emphasis, names, flowClosure);
34679
+ const linkDimmed = (l) => dimmed.has(l.source) || dimmed.has(l.target);
34406
34680
  const node = /* @__PURE__ */ new Map();
34407
34681
  names.forEach(
34408
34682
  (name, i) => node.set(name, {
@@ -34500,7 +34774,10 @@ function renderSankey(svg, chart, width, height, colors, bgColor, textColor, top
34500
34774
  const ribbon = svg.append("path").attr(
34501
34775
  "d",
34502
34776
  `M${sx},${sy} C${cx0},${sy} ${cx0},${ty} ${tx},${ty} L${tx},${ty + th} C${cx0},${ty + th} ${cx0},${sy + th} ${sx},${sy + th} Z`
34503
- ).attr("fill", color).attr("fill-opacity", 0.6);
34777
+ ).attr("fill", color).attr(
34778
+ "fill-opacity",
34779
+ linkDimmed(l) ? RIBBON_FILL_OPACITY * EMPHASIS_DIM_OPACITY : RIBBON_FILL_OPACITY
34780
+ );
34504
34781
  tagDatum(ribbon, {
34505
34782
  line: l.lineNumber,
34506
34783
  // U+241F (symbol-for-unit-separator) — collision-proof AND XML-legal;
@@ -34515,7 +34792,9 @@ function renderSankey(svg, chart, width, height, colors, bgColor, textColor, top
34515
34792
  const nodeLine = links.find(
34516
34793
  (l) => l.source === n.name || l.target === n.name
34517
34794
  )?.lineNumber;
34795
+ const nodeDim = dimmed.has(n.name);
34518
34796
  const nr = svg.append("rect").attr("x", n.x).attr("y", n.y).attr("width", NODE_W).attr("height", Math.max(1, n.h)).attr("fill", solid ? n.raw : mix(n.raw, bgColor, 75)).attr("stroke", n.raw).attr("stroke-width", 1);
34797
+ if (nodeDim) nr.attr("opacity", EMPHASIS_DIM_OPACITY);
34519
34798
  tagDatum(nr, {
34520
34799
  ...nodeLine !== void 0 && { line: nodeLine },
34521
34800
  key: `node:${n.name}`,
@@ -34524,10 +34803,11 @@ function renderSankey(svg, chart, width, height, colors, bgColor, textColor, top
34524
34803
  color: n.raw
34525
34804
  });
34526
34805
  const lastLayer = n.rank === maxRank;
34527
- svg.append("text").attr("x", lastLayer ? n.x - 6 : n.x + NODE_W + 6).attr("y", n.y + n.h / 2 + 4).attr("text-anchor", lastLayer ? "end" : "start").attr("fill", textColor).attr("font-size", 13).attr("font-family", FONT_FAMILY).text(n.name);
34806
+ const label = svg.append("text").attr("x", lastLayer ? n.x - 6 : n.x + NODE_W + 6).attr("y", n.y + n.h / 2 + 4).attr("text-anchor", lastLayer ? "end" : "start").attr("fill", textColor).attr("font-size", 13).attr("font-family", FONT_FAMILY).text(n.name);
34807
+ if (nodeDim) label.attr("opacity", EMPHASIS_DIM_TEXT_OPACITY);
34528
34808
  }
34529
34809
  }
34530
- var NODE_W, NODE_GAP;
34810
+ var NODE_W, NODE_GAP, RIBBON_FILL_OPACITY;
34531
34811
  var init_sankey = __esm({
34532
34812
  "src/charts-d3/sankey.ts"() {
34533
34813
  "use strict";
@@ -34535,8 +34815,10 @@ var init_sankey = __esm({
34535
34815
  init_color_utils();
34536
34816
  init_text_measure();
34537
34817
  init_shared();
34818
+ init_emphasis();
34538
34819
  NODE_W = 20;
34539
34820
  NODE_GAP = 14;
34821
+ RIBBON_FILL_OPACITY = 0.6;
34540
34822
  }
34541
34823
  });
34542
34824
 
@@ -34694,7 +34976,7 @@ function renderFunction(svg, chart, width, height, colors, textColor, mutedColor
34694
34976
  }
34695
34977
  const gen = d3line2().defined((p) => isFinite(p[1]) && p[1] >= yLo - 1 && p[1] <= yHi + 1).x((p) => x(p[0])).y((p) => y(p[1]));
34696
34978
  for (const c of curves) {
34697
- const path = svg.append("path").attr("d", gen(c.pts) ?? "").attr("fill", "none").attr("stroke", c.color).attr("stroke-width", 2.5).attr("stroke-linejoin", "round");
34979
+ const path = svg.append("path").attr("d", gen(c.pts) ?? "").attr("fill", "none").attr("stroke", c.color).attr("stroke-width", 1.75).attr("stroke-linejoin", "round").attr("data-series-name", c.name);
34698
34980
  tagDatum(path, { line: c.line, key: c.name, name: c.name, color: c.color });
34699
34981
  }
34700
34982
  drawXAxisTitle(
@@ -35521,8 +35803,21 @@ function renderEventLine(container, parsed, palette, isDark, onClickItem, export
35521
35803
  "fill",
35522
35804
  parsed.options.fillMode ? p.color : mix(p.color, themeBaseBg(palette, isDark), SHELF_TINT)
35523
35805
  );
35524
- const edgeY = titleNearTop ? shelfTop : shelfTop + shelfH - SHELF_EDGE;
35525
- cardG.append("rect").attr("x", 0).attr("y", edgeY).attr("width", CARD_W).attr("height", SHELF_EDGE).attr("clip-path", `url(#${clipId})`).attr("fill", p.color).attr("fill-opacity", p.future ? 0.4 : 1);
35806
+ if (SHELF_EDGE_STYLE !== "none" && !parsed.options.fillMode) {
35807
+ const lip = SHELF_EDGE_STYLE === "left" ? {
35808
+ // vertical lip on the spine-relative leading edge
35809
+ x: 0,
35810
+ y: shelfTop,
35811
+ width: SHELF_LIP,
35812
+ height: shelfH
35813
+ } : {
35814
+ x: 0,
35815
+ y: titleNearTop ? shelfTop : shelfTop + shelfH - SHELF_LIP,
35816
+ width: CARD_W,
35817
+ height: SHELF_LIP
35818
+ };
35819
+ cardG.append("rect").attr("x", lip.x).attr("y", lip.y).attr("width", lip.width).attr("height", lip.height).attr("clip-path", `url(#${clipId})`).attr("fill", p.color).attr("fill-opacity", p.future ? 0.4 : 1);
35820
+ }
35526
35821
  const shelfText = parsed.options.fillMode ? contrastText(p.color, palette.textOnFillLight, palette.textOnFillDark) : p.color;
35527
35822
  const titleMid = headBandTop + p.headerH / 2;
35528
35823
  const titleTop = titleMid - (p.titleLines.length - 1) * TITLE_LINE_H / 2;
@@ -35959,7 +36254,7 @@ function renderBody(cardG, lines, bodyColor, palette, startBaseline = CARD_BODY_
35959
36254
  y += DESC_LINE_H;
35960
36255
  }
35961
36256
  }
35962
- var CARD_W, H_MARGIN, TITLE_AREA, LEGEND_BAND, LEADER_ABOVE, LEADER_BELOW, DESC_FONT, DESC_LINE_H, CARD_PAD, CARD_BODY_TOP, TITLE_LINE_H, TITLE_MAX_W, DOT_R, MIN_SPACING, LANE_GAP, NEUTRAL_TAG, CARD_INSET, FAN_GAP, DATE_SUBTITLE_H, DATE_SUBTITLE_FONT, SHELF_TINT, SHELF_EDGE, ERA_BLOCK, ERA_BRACKET_CAP, ERA_BRACKET_PAD, ERA_SEAM_GAP, BREAK_HALF_H, BREAK_AMP, BREAK_GAP, ERA_LEG, ERA_LABEL_FONT, HL, DIM, ERA_HL, COLLAPSED, OFF, HIDDEN_ATTR, HOVER_CSS, DIMMABLE_SEL, PIN_ATTR;
36257
+ var CARD_W, H_MARGIN, TITLE_AREA, LEGEND_BAND, LEADER_ABOVE, LEADER_BELOW, DESC_FONT, DESC_LINE_H, CARD_PAD, CARD_BODY_TOP, TITLE_LINE_H, TITLE_MAX_W, DOT_R, MIN_SPACING, LANE_GAP, NEUTRAL_TAG, CARD_INSET, FAN_GAP, DATE_SUBTITLE_H, DATE_SUBTITLE_FONT, SHELF_TINT, SHELF_EDGE, SHELF_EDGE_STYLE, SHELF_LIP, ERA_BLOCK, ERA_BRACKET_CAP, ERA_BRACKET_PAD, ERA_SEAM_GAP, BREAK_HALF_H, BREAK_AMP, BREAK_GAP, ERA_LEG, ERA_LABEL_FONT, HL, DIM, ERA_HL, COLLAPSED, OFF, HIDDEN_ATTR, HOVER_CSS, DIMMABLE_SEL, PIN_ATTR;
35963
36258
  var init_renderer2 = __esm({
35964
36259
  "src/event-line/renderer.ts"() {
35965
36260
  "use strict";
@@ -35997,6 +36292,8 @@ var init_renderer2 = __esm({
35997
36292
  DATE_SUBTITLE_FONT = 10;
35998
36293
  SHELF_TINT = 13;
35999
36294
  SHELF_EDGE = 4;
36295
+ SHELF_EDGE_STYLE = "spine";
36296
+ SHELF_LIP = 2;
36000
36297
  ERA_BLOCK = 30;
36001
36298
  ERA_BRACKET_CAP = 8;
36002
36299
  ERA_BRACKET_PAD = 14;
@@ -36103,7 +36400,7 @@ function renderFigureBody(fig, view, parts, partColor, emphKey, form, palette, i
36103
36400
  out += `<path${dataLine} d="${d}" fill="${color}" fill-opacity="0.5" stroke="${color}" stroke-width="1.5"/>`;
36104
36401
  }
36105
36402
  } else {
36106
- const lightFill = mix(color, palette.bg, 70);
36403
+ const lightFill = mix(color, themeBaseBg(palette, isDark), 25);
36107
36404
  for (const d of paths) {
36108
36405
  out += `<path${dataLine} d="${d}" fill="${lightFill}" stroke="${color}" stroke-width="1.5"/>`;
36109
36406
  }
@@ -45818,6 +46115,8 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
45818
46115
  const YIELD_EVERY_MS = 30;
45819
46116
  const searchStart = performance.now();
45820
46117
  let lastYield = searchStart;
46118
+ const budgetMs = opts?.budgetMs ?? DEFAULT_SEARCH_BUDGET_MS;
46119
+ const overBudget = () => budgetMs > 0 && performance.now() - searchStart > budgetMs;
45821
46120
  const collapsedGroupLabels = /* @__PURE__ */ new Set();
45822
46121
  if (collapseInfo) {
45823
46122
  const missing = /* @__PURE__ */ new Set();
@@ -46116,6 +46415,7 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
46116
46415
  } catch {
46117
46416
  }
46118
46417
  await step("Optimizing layout");
46418
+ if (pool.length && overBudget()) break;
46119
46419
  }
46120
46420
  if (!pool.length)
46121
46421
  return place({ ranker: "network-simplex", nodesep: 50, ranksep: 60 });
@@ -46170,6 +46470,7 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
46170
46470
  cfgOf.set(lay, cfg);
46171
46471
  } catch {
46172
46472
  }
46473
+ if (overBudget()) break;
46173
46474
  }
46174
46475
  const extraKey = /* @__PURE__ */ new Map();
46175
46476
  for (const lay of extra)
@@ -46206,7 +46507,7 @@ async function layoutBoxesAndLinesSearch(parsed, collapseInfo, opts) {
46206
46507
  opts?.onTopConfigs?.(topConfigs);
46207
46508
  return best;
46208
46509
  }
46209
- var DEFAULT_LAMBDA, ESCALATE_THRESHOLD, ESCALATE_MAX_N, ESCALATE_SEEDS, ESCALATE_REFINE, splineGen, PATH_TOKEN_RE, FLAT_CACHE, GROUP_LABEL_ZONE3;
46510
+ var DEFAULT_LAMBDA, ESCALATE_THRESHOLD, ESCALATE_MAX_N, ESCALATE_SEEDS, ESCALATE_REFINE, DEFAULT_SEARCH_BUDGET_MS, splineGen, PATH_TOKEN_RE, FLAT_CACHE, GROUP_LABEL_ZONE3;
46210
46511
  var init_layout_search = __esm({
46211
46512
  "src/boxes-and-lines/layout-search.ts"() {
46212
46513
  "use strict";
@@ -46219,6 +46520,7 @@ var init_layout_search = __esm({
46219
46520
  ESCALATE_MAX_N = 45;
46220
46521
  ESCALATE_SEEDS = 18;
46221
46522
  ESCALATE_REFINE = 10;
46523
+ DEFAULT_SEARCH_BUDGET_MS = 5e3;
46222
46524
  splineGen = d3line3().x((d) => d.x).y((d) => d.y).curve(curveBasis5);
46223
46525
  PATH_TOKEN_RE = /[MLQC]|-?\d*\.?\d+(?:e-?\d+)?/gi;
46224
46526
  FLAT_CACHE = /* @__PURE__ */ new WeakMap();
@@ -48053,12 +48355,13 @@ var init_renderer13 = __esm({
48053
48355
  init_legend_constants();
48054
48356
  init_text_measure();
48055
48357
  init_visual_conventions();
48358
+ init_emphasis();
48056
48359
  init_card_model();
48057
48360
  KEY_X2 = 10;
48058
48361
  KEY_VALUE_GAP2 = 6;
48059
48362
  BAR_DOT_R = 3.5;
48060
48363
  TITLE_RESERVE = 40;
48061
- DIM_OPACITY = 0.28;
48364
+ DIM_OPACITY = EMPHASIS_DIM_OPACITY;
48062
48365
  ROMAN = [
48063
48366
  [10, "X"],
48064
48367
  [9, "IX"],
@@ -53899,6 +54202,19 @@ function collapseGroups(parsed, collapsedIds, defaultLatencyMs = 0, defaultUptim
53899
54202
  }
53900
54203
  function resolveSplits(outbound, diagnostics) {
53901
54204
  if (outbound.length === 0) return [];
54205
+ const async = outbound.filter((e) => e.async);
54206
+ if (async.length > 0) {
54207
+ const sync = outbound.filter((e) => !e.async);
54208
+ const asyncResolved = async.map((e) => ({
54209
+ edge: e,
54210
+ split: e.split ?? 100
54211
+ }));
54212
+ const syncResolved = resolveSplits(sync, diagnostics);
54213
+ const byEdge = /* @__PURE__ */ new Map();
54214
+ for (const r of [...asyncResolved, ...syncResolved])
54215
+ byEdge.set(r.edge, r.split);
54216
+ return outbound.filter((e) => byEdge.has(e)).map((e) => ({ edge: e, split: byEdge.get(e) }));
54217
+ }
53902
54218
  if (outbound.length === 1) {
53903
54219
  return [{ edge: outbound[0], split: 100 }];
53904
54220
  }
@@ -54006,8 +54322,9 @@ function computeInfra(parsed, params = {}) {
54006
54322
  const computedLatency = /* @__PURE__ */ new Map();
54007
54323
  const computedUptime = /* @__PURE__ */ new Map();
54008
54324
  const localAvailability = /* @__PURE__ */ new Map();
54325
+ const defaultRps = parseFloat(parsed.options["default-rps"] ?? "") || 0;
54009
54326
  const edgeNode = effectiveParsed.nodes.find((n) => n.isEdge);
54010
- const baseRps = params.rps ?? (edgeNode ? getNumProp(edgeNode, "rps", 0) : 0);
54327
+ const baseRps = params.rps ?? (edgeNode ? getNumProp(edgeNode, "rps", defaultRps) : 0);
54011
54328
  if (edgeNode) {
54012
54329
  computedRps.set(edgeNode.id, baseRps);
54013
54330
  computedLatency.set(edgeNode.id, 0);
@@ -54052,13 +54369,14 @@ function computeInfra(parsed, params = {}) {
54052
54369
  } else {
54053
54370
  targetIds = [edge.targetId];
54054
54371
  }
54372
+ const edgeDownstreamLatency = edge.async ? 0 : downstreamLatency;
54055
54373
  for (const targetId of targetIds) {
54056
54374
  const perTarget = fanoutedRps / targetIds.length;
54057
54375
  const existing = computedRps.get(targetId) ?? 0;
54058
54376
  computedRps.set(targetId, existing + perTarget);
54059
54377
  const prevLatency = computedLatency.get(targetId) ?? 0;
54060
- if (downstreamLatency > prevLatency) {
54061
- computedLatency.set(targetId, downstreamLatency);
54378
+ if (edgeDownstreamLatency > prevLatency) {
54379
+ computedLatency.set(targetId, edgeDownstreamLatency);
54062
54380
  }
54063
54381
  const prevUptime = computedUptime.get(targetId) ?? 1;
54064
54382
  if (cumulativeUptime < prevUptime) {
@@ -54142,7 +54460,8 @@ function computeInfra(parsed, params = {}) {
54142
54460
  const coldStartMs = isServerless(node) ? getNumProp(node, "cold-start-ms", 0) : 0;
54143
54461
  const coldLatency = nodeLatency + coldStartMs;
54144
54462
  const outbound = outboundMap.get(nodeId3) ?? [];
54145
- if (outbound.length === 0) {
54463
+ const syncOutbound = outbound.filter((e) => !e.async);
54464
+ if (syncOutbound.length === 0) {
54146
54465
  const rps = computedRps.get(nodeId3) ?? 0;
54147
54466
  let result;
54148
54467
  if (rps <= 0) {
@@ -54175,7 +54494,7 @@ function computeInfra(parsed, params = {}) {
54175
54494
  nodeLeafCache.set(nodeId3, result);
54176
54495
  return result;
54177
54496
  }
54178
- const resolved = resolveSplits(outbound, []);
54497
+ const resolved = resolveSplits(outbound, []).filter((r) => !r.edge.async);
54179
54498
  const paths = [];
54180
54499
  for (const { edge, split } of resolved) {
54181
54500
  const fanout = edge.fanout != null && edge.fanout >= 1 ? edge.fanout : 1;
@@ -54391,17 +54710,7 @@ function computeInfra(parsed, params = {}) {
54391
54710
  const edgeKey = `${edge.sourceId}->${edge.targetId}`;
54392
54711
  const rps = computedEdgeRps.get(edgeKey) ?? 0;
54393
54712
  const outbound = outboundMap.get(edge.sourceId) ?? [];
54394
- let resolvedSplit = edge.split ?? 100;
54395
- if (outbound.length > 1 && edge.split === null) {
54396
- const declared = outbound.filter((e) => e.split !== null);
54397
- if (declared.length === 0) {
54398
- resolvedSplit = 100 / outbound.length;
54399
- } else {
54400
- const declaredSum = declared.reduce((s, e) => s + (e.split ?? 0), 0);
54401
- const undeclared = outbound.filter((e) => e.split === null);
54402
- resolvedSplit = undeclared.length > 0 ? (100 - declaredSum) / undeclared.length : 0;
54403
- }
54404
- }
54713
+ const resolvedSplit = resolveSplits(outbound, []).find((r) => r.edge === edge)?.split ?? edge.split ?? 100;
54405
54714
  return {
54406
54715
  sourceId: edge.sourceId,
54407
54716
  targetId: edge.targetId,
@@ -66782,7 +67091,7 @@ var init_renderer22 = __esm({
66782
67091
  init_text_measure();
66783
67092
  NODE_FONT_SIZE8 = 13;
66784
67093
  DESC_FONT_SIZE7 = 11;
66785
- EDGE_LABEL_FONT_SIZE14 = 11;
67094
+ EDGE_LABEL_FONT_SIZE14 = 13;
66786
67095
  DESC_LINE_HEIGHT8 = 15;
66787
67096
  TITLE_AREA_HEIGHT = 50;
66788
67097
  }
@@ -85096,7 +85405,7 @@ var CSS = AUTO_BASE_CSS + "\n" + BLOCK_CSS + "\n" + darkScopedBlockCss(BLOCK_CSS
85096
85405
 
85097
85406
  // src/auto/shared.ts
85098
85407
  init_safe_href();
85099
- var VERSION = "0.53.0";
85408
+ var VERSION = "0.54.0";
85100
85409
  var ARIA_LABEL_MAX = 200;
85101
85410
  var COPIED_INTERACTION_MS = 1500;
85102
85411
  var LOG_PREFIX = "[dgmo]";