@diagrammo/dgmo 0.49.0 → 0.50.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 (192) hide show
  1. package/.cursorrules +6 -2
  2. package/.github/copilot-instructions.md +6 -2
  3. package/.windsurfrules +6 -2
  4. package/SKILL.md +6 -2
  5. package/dist/advanced.d.ts +419 -12
  6. package/dist/advanced.js +557 -195
  7. package/dist/auto.js +129 -119
  8. package/dist/auto.mjs +11543 -4815
  9. package/dist/block.js +145 -48
  10. package/dist/{charts-d3-EW3YQTEA.js → charts-d3-DKUD7MQE.js} +5 -5
  11. package/dist/{chunk-5FGDJIJX.js → chunk-2C56LEML.js} +3 -3
  12. package/dist/{chunk-35BBZDFE.js → chunk-2JJ2KNPI.js} +4 -4
  13. package/dist/{chunk-ZTLWVRNS.js → chunk-2QYTMIRR.js} +2 -2
  14. package/dist/{chunk-VC3TR4NL.js → chunk-3G6TMNVF.js} +3 -3
  15. package/dist/{chunk-ZDUQGL3N.js → chunk-3LKM6FMW.js} +2 -2
  16. package/dist/{chunk-4BD7EVPK.js → chunk-3P2DKJCG.js} +2 -2
  17. package/dist/{chunk-6CLKNXVP.js → chunk-3TDRHT4J.js} +457 -78
  18. package/dist/chunk-3XPUDB7B.js +420 -0
  19. package/dist/{chunk-DDTJ5N7Q.js → chunk-3ZPC476N.js} +6 -6
  20. package/dist/{chunk-BPIJLRYE.js → chunk-4BMJQFNK.js} +3 -3
  21. package/dist/{chunk-N4J5KGWG.js → chunk-4S723I74.js} +8 -8
  22. package/dist/{chunk-4YNP6O47.js → chunk-5JSWBPZZ.js} +3 -3
  23. package/dist/{chunk-KUBBJ2CD.js → chunk-5Z736BSU.js} +6 -6
  24. package/dist/{chunk-MJCG5K2O.js → chunk-6BFZKBGD.js} +2 -2
  25. package/dist/{chunk-XCC2RENT.js → chunk-6IAA2QQ2.js} +2 -2
  26. package/dist/{chunk-FUCAB3KQ.js → chunk-6M5PJFQG.js} +3 -3
  27. package/dist/{chunk-T7IKPCMI.js → chunk-6X4AKWLF.js} +3 -3
  28. package/dist/{chunk-JFPY3UJ7.js → chunk-6ZT4U37Q.js} +8 -8
  29. package/dist/{chunk-CLR4A2N2.js → chunk-7G2ER4CY.js} +4 -4
  30. package/dist/chunk-7PQYTZMA.js +681 -0
  31. package/dist/{chunk-LG4TWW4U.js → chunk-7RB5Q6MB.js} +8 -8
  32. package/dist/{chunk-7OVSJI2V.js → chunk-A3WQVFBS.js} +3 -3
  33. package/dist/{chunk-X2CH42BN.js → chunk-ASS5YYIZ.js} +7 -7
  34. package/dist/{chunk-3NFJWFNP.js → chunk-BDXC7MDK.js} +1045 -124
  35. package/dist/chunk-BTUTZMPT.js +482 -0
  36. package/dist/{chunk-UVCXZEGU.js → chunk-CVV36CWI.js} +2 -2
  37. package/dist/{chunk-TC3YWBFO.js → chunk-DMJJ3CEQ.js} +29 -8
  38. package/dist/{chunk-NVT6C2VE.js → chunk-EJTKV7CY.js} +4 -4
  39. package/dist/{chunk-4NQ3IKUM.js → chunk-ER5SCQWQ.js} +5 -5
  40. package/dist/{chunk-7VLUGEMJ.js → chunk-F5UDBSJY.js} +4 -4
  41. package/dist/{chunk-IHLSHCPZ.js → chunk-FAL7NOK3.js} +5 -5
  42. package/dist/{chunk-YRVLP7UU.js → chunk-FB2WHOEQ.js} +4 -4
  43. package/dist/chunk-FL4IOQ37.js +379 -0
  44. package/dist/{chunk-6VE3VLWM.js → chunk-GBE54KY6.js} +6 -5
  45. package/dist/{chunk-ZXL6MB4R.js → chunk-GL5UZ6NY.js} +3 -3
  46. package/dist/{chunk-3BIAFYD2.js → chunk-GPWHQ5FJ.js} +2 -2
  47. package/dist/chunk-GROQGHCT.js +960 -0
  48. package/dist/{chunk-GB3YZTAM.js → chunk-H73FP42Q.js} +5 -5
  49. package/dist/{chunk-O5MQPK7E.js → chunk-I4IFRCUB.js} +3 -3
  50. package/dist/{chunk-IUK7AGD4.js → chunk-I7F2A35O.js} +2 -2
  51. package/dist/{chunk-YTN572Q2.js → chunk-IA3FJYWR.js} +2 -2
  52. package/dist/{chunk-RI4NIMFP.js → chunk-JFYIP7XU.js} +75 -35
  53. package/dist/{chunk-UWDO3O52.js → chunk-JZNMXEIV.js} +2 -2
  54. package/dist/chunk-KJXIHOVL.js +940 -0
  55. package/dist/{chunk-26RVGM44.js → chunk-L2ZTFHAY.js} +2 -2
  56. package/dist/{chunk-PU3AUQPA.js → chunk-M5RXSRO2.js} +2 -2
  57. package/dist/{chunk-XKTN7CZF.js → chunk-NFFIKVNO.js} +4 -4
  58. package/dist/{chunk-SVDWQGX2.js → chunk-NISQC5KW.js} +139 -78
  59. package/dist/{chunk-4HVNZ7WF.js → chunk-O4VUFXIH.js} +3 -3
  60. package/dist/{chunk-F743CM2N.js → chunk-O76UD6VJ.js} +3 -3
  61. package/dist/{chunk-TLKMTQYL.js → chunk-OHQEMBLI.js} +3 -3
  62. package/dist/{chunk-BGJV5ATD.js → chunk-ONA5KPV3.js} +2 -2
  63. package/dist/{chunk-ZNQX6QWH.js → chunk-OTAL43EH.js} +6 -2
  64. package/dist/{chunk-FMKTYMP4.js → chunk-OTP5KVLO.js} +3 -3
  65. package/dist/{chunk-5ZZLLBFK.js → chunk-PGEGPSCZ.js} +2 -2
  66. package/dist/{chunk-XJ7J6WOO.js → chunk-PJUGRJHJ.js} +4 -4
  67. package/dist/{chunk-6SA7FJE4.js → chunk-PLZI6KPM.js} +10 -10
  68. package/dist/{chunk-AJ2PEY7J.js → chunk-PMN3Q7RJ.js} +2 -2
  69. package/dist/chunk-Q4V4XQWV.js +158 -0
  70. package/dist/{chunk-5XCM7UUW.js → chunk-QDFNCATJ.js} +99 -35
  71. package/dist/{chunk-CEHZOPGM.js → chunk-QFQ35BTO.js} +2 -2
  72. package/dist/{chunk-4UDKKGHH.js → chunk-QUJGFNFN.js} +2 -2
  73. package/dist/{chunk-IBC35BTK.js → chunk-R56LASIN.js} +2 -2
  74. package/dist/{chunk-SJEGO2QL.js → chunk-RV26LTUU.js} +4 -4
  75. package/dist/chunk-RV6A2B5S.js +354 -0
  76. package/dist/{chunk-65RTDUZH.js → chunk-S5GRONXL.js} +3 -3
  77. package/dist/{chunk-K6HNAY5V.js → chunk-SCHXJTZL.js} +2 -2
  78. package/dist/{chunk-ALRHIR6I.js → chunk-SGFZZAUO.js} +4 -4
  79. package/dist/{chunk-KV6Y2HE6.js → chunk-SU2VBK4L.js} +4 -4
  80. package/dist/{chunk-D5AVQCDN.js → chunk-T4BRO3Z6.js} +3 -3
  81. package/dist/{chunk-IFK3GIUW.js → chunk-TSA237IM.js} +2 -2
  82. package/dist/{chunk-SQDFK5ZQ.js → chunk-TWCLK2GA.js} +3 -3
  83. package/dist/{chunk-DKEPS5LH.js → chunk-U2HGPLZS.js} +36 -2
  84. package/dist/{chunk-EYN7VFKL.js → chunk-URSQLCTR.js} +3 -3
  85. package/dist/{chunk-BCT5G2VN.js → chunk-UUS2GIJZ.js} +5 -5
  86. package/dist/{chunk-LVHPTBO3.js → chunk-UXOSVCLJ.js} +4 -4
  87. package/dist/{chunk-HBVD67XG.js → chunk-WCUHAQKB.js} +2 -2
  88. package/dist/{chunk-YYX3SU6H.js → chunk-WWN566I7.js} +257 -101
  89. package/dist/{chunk-YM2BSPYW.js → chunk-X2NMFTG3.js} +2 -2
  90. package/dist/{chunk-ZREZOIGS.js → chunk-XCLHOZAM.js} +6 -6
  91. package/dist/{chunk-KQAEKMIX.js → chunk-XDXNDSKO.js} +2 -2
  92. package/dist/{chunk-RTERS6S5.js → chunk-XMVQ5F4M.js} +4 -4
  93. package/dist/{chunk-4FIV4K4Z.js → chunk-YA7D2N2O.js} +7 -7
  94. package/dist/{chunk-B57V3DWN.js → chunk-YB3FW24O.js} +3 -3
  95. package/dist/chunk-YNRDWYZP.js +295 -0
  96. package/dist/{chunk-2WMBWZY2.js → chunk-YZLMKW4T.js} +3 -3
  97. package/dist/{chunk-SGHSY3LU.js → chunk-Z6XJQ6KO.js} +81 -6
  98. package/dist/cli.cjs +153 -144
  99. package/dist/clock.d.ts +10 -0
  100. package/dist/clock.js +235 -0
  101. package/dist/countdown.d.ts +12 -0
  102. package/dist/countdown.js +450 -0
  103. package/dist/dgmo-router-QOMFCOOV.js +84 -0
  104. package/dist/{dimensions-WSIJXT5Q.js → dimensions-P5FSX2QN.js} +5 -5
  105. package/dist/editor.js +39 -1
  106. package/dist/element.js +131 -121
  107. package/dist/element.mjs +11543 -4815
  108. package/dist/{flowchart-parser-IVZN7CQN.js → flowchart-parser-OE4WHLTF.js} +4 -4
  109. package/dist/{flowchart-renderer-GMI2U3DD.js → flowchart-renderer-GPCSMTOR.js} +6 -6
  110. package/dist/highlight.d.ts +17 -1
  111. package/dist/highlight.js +156 -5
  112. package/dist/index.js +50 -45
  113. package/dist/{layout-HQMBPJVS.js → layout-46366KBT.js} +2 -2
  114. package/dist/{layout-D42XE4RE.js → layout-5BG6DFCU.js} +3 -3
  115. package/dist/{layout-K2ZN3Q42.js → layout-T3OZU5QW.js} +4 -4
  116. package/dist/{layout-S3EK4IYO.js → layout-TJIWF7EM.js} +4 -4
  117. package/dist/{layout-GZ2CWONU.js → layout-VYELEAW7.js} +4 -4
  118. package/dist/{layout-4Z5JM7PT.js → layout-WZHNDT2O.js} +4 -4
  119. package/dist/{parser-OBQG655V.js → parser-45DENHFZ.js} +4 -4
  120. package/dist/{parser-MCJWF5G3.js → parser-4AI5ZN5F.js} +3 -3
  121. package/dist/{parser-SMBJO3XN.js → parser-4P36TUVD.js} +4 -4
  122. package/dist/{parser-ZYC3XGNS.js → parser-5AURY4A6.js} +4 -4
  123. package/dist/{parser-WISPH7LW.js → parser-A2BEFMGK.js} +4 -4
  124. package/dist/parser-B5FVS7O6.js +11 -0
  125. package/dist/{parser-KPMN5KKE.js → parser-C2HF2C2E.js} +4 -4
  126. package/dist/parser-CSFUN3CT.js +14 -0
  127. package/dist/{parser-TITK4YW6.js → parser-D3QT6GWN.js} +5 -5
  128. package/dist/{parser-ZKF5K2H2.js → parser-D5637H5H.js} +4 -4
  129. package/dist/{parser-IKZFTLOJ.js → parser-DFFEDKHH.js} +3 -3
  130. package/dist/parser-DOKMZXVZ.js +13 -0
  131. package/dist/parser-FMPCVFRK.js +15 -0
  132. package/dist/{parser-UBGPTIZD.js → parser-HYRTF3DO.js} +3 -3
  133. package/dist/{parser-TNYX5S7E.js → parser-I2UIJKVQ.js} +4 -4
  134. package/dist/{parser-K647FRI4.js → parser-IBWNAPY4.js} +3 -3
  135. package/dist/{parser-JD7OQ2E6.js → parser-KUQWRPIT.js} +4 -4
  136. package/dist/{parser-HVJFTOXL.js → parser-LWKKBNSA.js} +3 -3
  137. package/dist/{parser-4ROBKVUR.js → parser-O4QFCUW7.js} +5 -5
  138. package/dist/{parser-BL7Z72EY.js → parser-PGMQDD4G.js} +4 -4
  139. package/dist/{parser-QJYS7KQQ.js → parser-Q4QKCIWQ.js} +4 -4
  140. package/dist/{parser-UGA23PYK.js → parser-QTOB4KFL.js} +4 -4
  141. package/dist/{parser-DERPY5KC.js → parser-QYYM4QVW.js} +4 -4
  142. package/dist/{parser-GFSOPDB7.js → parser-RZMQP7MM.js} +4 -4
  143. package/dist/parser-RZPVAQVX.js +11 -0
  144. package/dist/parser-SOUJOEYI.js +13 -0
  145. package/dist/{parser-MBJSBTL5.js → parser-UQE5OWZI.js} +5 -5
  146. package/dist/{parser-MRBPXXIV.js → parser-WAV5JRPT.js} +4 -4
  147. package/dist/{parser-4BKIFG2C.js → parser-YLKYBRIT.js} +5 -5
  148. package/dist/{parser-D6ONJQFR.js → parser-YS7TOS2K.js} +3 -3
  149. package/dist/{parser-CLMFZWCC.js → parser-ZSOWUBML.js} +5 -5
  150. package/dist/{parser-XWUDFJ7P.js → parser-ZTBATMLX.js} +4 -4
  151. package/dist/{renderer-LWGJFT3D.js → renderer-35HSIDMZ.js} +5 -5
  152. package/dist/{renderer-BB5PREMY.js → renderer-4ITCHBT7.js} +4 -4
  153. package/dist/{renderer-YHXPN3GF.js → renderer-67NPCBIS.js} +7 -7
  154. package/dist/{renderer-TPIBXNOS.js → renderer-6GHYK7DX.js} +6 -6
  155. package/dist/{renderer-VICQ4WNY.js → renderer-6RNGHC5Z.js} +5 -5
  156. package/dist/renderer-72LGR2YF.js +14 -0
  157. package/dist/{renderer-W5RMOURB.js → renderer-7SWMIFMM.js} +6 -6
  158. package/dist/{renderer-6L3NBEY5.js → renderer-BBCEFILK.js} +6 -6
  159. package/dist/{renderer-VFUVRNLK.js → renderer-BSW7SRIZ.js} +4 -4
  160. package/dist/{renderer-CGW7QPNA.js → renderer-BY533UWU.js} +7 -7
  161. package/dist/{renderer-V3JEMWMM.js → renderer-BZSBPTZJ.js} +6 -6
  162. package/dist/{renderer-DKESNMU5.js → renderer-CXUC4KAD.js} +6 -6
  163. package/dist/{renderer-XEKIRPI6.js → renderer-FZUKFUNZ.js} +5 -5
  164. package/dist/renderer-G2NLGNKF.js +17 -0
  165. package/dist/{renderer-THD6V5TL.js → renderer-GO3FE3RA.js} +9 -7
  166. package/dist/{renderer-ROV756WT.js → renderer-HPGR6USK.js} +5 -5
  167. package/dist/renderer-ICJN2SA2.js +24 -0
  168. package/dist/{renderer-4IUPINAE.js → renderer-INEXO7B6.js} +7 -7
  169. package/dist/{renderer-SQ3JUAI7.js → renderer-KDYCFRBX.js} +6 -6
  170. package/dist/renderer-KY4KQBTF.js +19 -0
  171. package/dist/{renderer-MJDDU27W.js → renderer-LBN65V45.js} +10 -10
  172. package/dist/{renderer-UZF3JROD.js → renderer-MGBWOCXG.js} +8 -8
  173. package/dist/{renderer-MFJ7HY2H.js → renderer-O4JKUGKO.js} +5 -5
  174. package/dist/{renderer-QL5KO6SK.js → renderer-OOGFODWP.js} +7 -7
  175. package/dist/{renderer-A5VFZQQK.js → renderer-Q6ZKPLS6.js} +6 -6
  176. package/dist/{renderer-TYXRHMRI.js → renderer-SZQ326M5.js} +9 -9
  177. package/dist/{renderer-6T3KK4S3.js → renderer-TKG6LS4U.js} +7 -7
  178. package/dist/{renderer-QNHUXCRL.js → renderer-TQMZGDAX.js} +9 -9
  179. package/dist/{renderer-radial-MHO2VDXN.js → renderer-radial-JELMHQ24.js} +6 -6
  180. package/dist/{state-parser-RX3GXOIB.js → state-parser-3REXRK3R.js} +4 -4
  181. package/dist/{state-renderer-AJUKGNWU.js → state-renderer-XYLNRWXR.js} +6 -6
  182. package/docs/ai-integration.md +6 -2
  183. package/docs/language-reference.md +253 -22
  184. package/gallery/fixtures/body.dgmo +17 -0
  185. package/gallery/fixtures/bracket.dgmo +30 -0
  186. package/gallery/fixtures/clock.dgmo +8 -0
  187. package/gallery/fixtures/countdown.dgmo +3 -0
  188. package/gallery/fixtures/goal.dgmo +4 -0
  189. package/package.json +12 -4
  190. package/dist/dgmo-router-ODKPEJ3P.js +0 -79
  191. package/dist/parser-667RGSUW.js +0 -13
  192. package/dist/parser-J3P2D6IP.js +0 -13
@@ -0,0 +1,450 @@
1
+ // src/countdown/resolve.ts
2
+ var DAY_MS = 864e5;
3
+ var WEEK_MS = 7 * DAY_MS;
4
+ function ordinalWord(n) {
5
+ const abs = Math.abs(n);
6
+ const tens = abs % 100;
7
+ let suffix = "th";
8
+ if (tens < 11 || tens > 13) {
9
+ const ones = abs % 10;
10
+ suffix = ones === 1 ? "st" : ones === 2 ? "nd" : ones === 3 ? "rd" : "th";
11
+ }
12
+ return `${n}${suffix}`;
13
+ }
14
+ function applyOrdinalTemplate(template, n) {
15
+ return template.replace(/\bNth\b/g, ordinalWord(n)).replace(/\bN\b/g, String(n));
16
+ }
17
+ function dayStart(ms) {
18
+ const d = new Date(ms);
19
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
20
+ }
21
+ function nthWeekdayOfMonth(year, month, weekday, nth, hour, minute) {
22
+ if (nth < 0) {
23
+ const last = new Date(year, month + 1, 0).getDate();
24
+ for (let d = last; d >= 1; d--) {
25
+ if (new Date(year, month, d).getDay() === weekday) {
26
+ return new Date(year, month, d, hour, minute).getTime();
27
+ }
28
+ }
29
+ return null;
30
+ }
31
+ const firstDow = new Date(year, month, 1).getDay();
32
+ const offset = (weekday - firstDow + 7) % 7;
33
+ const day = 1 + offset + (nth - 1) * 7;
34
+ if (day > new Date(year, month + 1, 0).getDate()) return null;
35
+ return new Date(year, month, day, hour, minute).getTime();
36
+ }
37
+ function resolveNext(rule, now) {
38
+ const nowD = new Date(now);
39
+ const { hour, minute } = rule;
40
+ const passes = rule.allDay ? (t) => dayStart(t) >= dayStart(now) : (t) => t > now;
41
+ const dayExists = (y, m, d) => new Date(y, m, d).getMonth() === m;
42
+ switch (rule.kind) {
43
+ case "month-day": {
44
+ const month = rule.month;
45
+ const day = rule.day;
46
+ for (let y = nowD.getFullYear(); ; y++) {
47
+ if (!dayExists(y, month, day)) continue;
48
+ const t = new Date(y, month, day, hour, minute).getTime();
49
+ if (passes(t)) return t;
50
+ }
51
+ }
52
+ case "nth-weekday":
53
+ case "last-weekday": {
54
+ const weekday = rule.weekday;
55
+ const nth = rule.kind === "last-weekday" ? -1 : rule.nth;
56
+ let y = nowD.getFullYear();
57
+ let m = nowD.getMonth();
58
+ for (let i = 0; i < 24; i++) {
59
+ const t = nthWeekdayOfMonth(y, m, weekday, nth, hour, minute);
60
+ if (t !== null && passes(t)) return t;
61
+ m++;
62
+ if (m > 11) {
63
+ m = 0;
64
+ y++;
65
+ }
66
+ }
67
+ return now + WEEK_MS;
68
+ }
69
+ case "weekly": {
70
+ const weekday = rule.weekday;
71
+ for (let i = 0; i < 8; i++) {
72
+ const d = new Date(
73
+ nowD.getFullYear(),
74
+ nowD.getMonth(),
75
+ nowD.getDate() + i,
76
+ hour,
77
+ minute
78
+ );
79
+ if (d.getDay() === weekday && passes(d.getTime())) return d.getTime();
80
+ }
81
+ return now + WEEK_MS;
82
+ }
83
+ case "interval": {
84
+ const n = rule.intervalN ?? 1;
85
+ const anchor = rule.anchorMs ?? now;
86
+ if (rule.intervalUnit === "month") {
87
+ const a = new Date(anchor);
88
+ const day = a.getDate();
89
+ for (let k = 0; ; k++) {
90
+ const y = a.getFullYear();
91
+ const m = a.getMonth() + k * n;
92
+ const yy = y + Math.floor(m / 12);
93
+ const mm = (m % 12 + 12) % 12;
94
+ if (!dayExists(yy, mm, day)) continue;
95
+ const t2 = new Date(yy, mm, day, hour, minute).getTime();
96
+ if (passes(t2)) return t2;
97
+ }
98
+ }
99
+ const step = (rule.intervalUnit === "week" ? WEEK_MS : DAY_MS) * n;
100
+ let t = anchor;
101
+ if (!passes(t)) {
102
+ const k = Math.max(0, Math.floor((now - anchor) / step));
103
+ t = anchor + k * step;
104
+ while (!passes(t)) t += step;
105
+ }
106
+ return t;
107
+ }
108
+ }
109
+ }
110
+ function ordinalFor(resolvedMs, since) {
111
+ return new Date(resolvedMs).getFullYear() - since;
112
+ }
113
+ function roundBy(value, mode) {
114
+ return mode === "down" ? Math.floor(value) : mode === "nearest" ? Math.round(value) : Math.ceil(value);
115
+ }
116
+ function pad2(n) {
117
+ return n < 10 ? "0" + n : String(n);
118
+ }
119
+ function plural(n, unit) {
120
+ return `${n} ${unit}${n === 1 ? "" : "s"}`;
121
+ }
122
+ function formatCount(remainingMs, opts) {
123
+ const { units, round, fields } = opts;
124
+ if (units === "days")
125
+ return plural(roundBy(remainingMs / DAY_MS, round), "day");
126
+ if (units === "weeks")
127
+ return plural(roundBy(remainingMs / WEEK_MS, round), "week");
128
+ const totalSec = Math.floor(remainingMs / 1e3);
129
+ if (units === "clock") {
130
+ const h2 = Math.floor(totalSec / 3600);
131
+ const m2 = Math.floor(totalSec % 3600 / 60);
132
+ const s2 = totalSec % 60;
133
+ return `${pad2(h2)}:${pad2(m2)}:${pad2(s2)}`;
134
+ }
135
+ if (units === "words") {
136
+ const days2 = Math.ceil(remainingMs / DAY_MS);
137
+ if (days2 <= 0) return "now";
138
+ if (days2 === 1) return "tomorrow";
139
+ if (days2 < 14) return `${days2} days`;
140
+ if (days2 < 60) return `${Math.round(days2 / 7)} weeks`;
141
+ return `${Math.round(days2 / 30)} months`;
142
+ }
143
+ const days = Math.floor(totalSec / 86400);
144
+ const h = Math.floor(totalSec % 86400 / 3600);
145
+ const m = Math.floor(totalSec % 3600 / 60);
146
+ const s = totalSec % 60;
147
+ const has = (f) => fields.includes(f);
148
+ const clockParts = [];
149
+ if (has("h")) clockParts.push(pad2(h));
150
+ if (has("m")) clockParts.push(pad2(m));
151
+ if (has("s")) clockParts.push(pad2(s));
152
+ const clock = clockParts.join(":");
153
+ const dayPrefix = has("d") && days > 0 ? `${days}d${clock ? " " : ""}` : "";
154
+ return dayPrefix + clock || plural(days, "day");
155
+ }
156
+ function formatWordsDetail(remainingMs) {
157
+ const totalMin = Math.floor(Math.max(0, remainingMs) / 6e4);
158
+ const d = Math.floor(totalMin / 1440);
159
+ const h = Math.floor(totalMin % 1440 / 60);
160
+ const m = totalMin % 60;
161
+ const parts = [];
162
+ if (d > 0) parts.push(`${d} day${d === 1 ? "" : "s"}`);
163
+ if (d > 0 || h > 0) parts.push(`${h} hour${h === 1 ? "" : "s"}`);
164
+ parts.push(`${m} minute${m === 1 ? "" : "s"}`);
165
+ return parts.join(" ");
166
+ }
167
+ function breakdown(nowMs, targetMs) {
168
+ const n = new Date(nowMs);
169
+ const t = new Date(targetMs);
170
+ let y = t.getFullYear() - n.getFullYear();
171
+ let mo = t.getMonth() - n.getMonth();
172
+ let d = t.getDate() - n.getDate();
173
+ let h = t.getHours() - n.getHours();
174
+ let mi = t.getMinutes() - n.getMinutes();
175
+ let s = t.getSeconds() - n.getSeconds();
176
+ if (s < 0) {
177
+ s += 60;
178
+ mi--;
179
+ }
180
+ if (mi < 0) {
181
+ mi += 60;
182
+ h--;
183
+ }
184
+ if (h < 0) {
185
+ h += 24;
186
+ d--;
187
+ }
188
+ if (d < 0) {
189
+ d += new Date(t.getFullYear(), t.getMonth(), 0).getDate();
190
+ mo--;
191
+ }
192
+ if (mo < 0) {
193
+ mo += 12;
194
+ y--;
195
+ }
196
+ return { years: y, months: mo, days: d, hours: h, minutes: mi, seconds: s };
197
+ }
198
+ function humanUnits(nowMs, targetMs) {
199
+ const b = breakdown(nowMs, targetMs);
200
+ return [
201
+ [b.years, "year"],
202
+ [b.months, "month"],
203
+ [b.days, "day"],
204
+ [b.hours, "hour"],
205
+ [b.minutes, "minute"]
206
+ ];
207
+ }
208
+ function formatHuman(nowMs, targetMs) {
209
+ const units = humanUnits(nowMs, targetMs);
210
+ let i = 0;
211
+ while (i < units.length - 1 && units[i][0] === 0) i++;
212
+ const rest = units.slice(i);
213
+ const say = (arr) => arr.map(([n, u]) => `${n} ${u}${n === 1 ? "" : "s"}`).join(", ");
214
+ const primary = rest.slice(0, 2).filter((p, idx) => p[0] > 0 || idx === 0);
215
+ const secondary = rest.slice(2).filter((p) => p[0] > 0);
216
+ return { big: say(primary) || "now", sub: say(secondary) };
217
+ }
218
+ function formatCompound(nowMs, targetMs, maxUnits = 2) {
219
+ const all = humanUnits(nowMs, targetMs);
220
+ let i = 0;
221
+ while (i < all.length - 1 && all[i][0] === 0) i++;
222
+ const chosen = all.slice(i, i + maxUnits).filter((p, idx) => p[0] > 0 || idx === 0);
223
+ if (!chosen.length) return "now";
224
+ return chosen.map(([n, u]) => `${n} ${u}${n === 1 ? "" : "s"}`).join(", ");
225
+ }
226
+ var WEEKDAY_ABBR = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
227
+ var MONTH_ABBR = [
228
+ "Jan",
229
+ "Feb",
230
+ "Mar",
231
+ "Apr",
232
+ "May",
233
+ "Jun",
234
+ "Jul",
235
+ "Aug",
236
+ "Sep",
237
+ "Oct",
238
+ "Nov",
239
+ "Dec"
240
+ ];
241
+ function formatDate(ms) {
242
+ const d = new Date(ms);
243
+ return `${WEEKDAY_ABBR[d.getDay()]} ${MONTH_ABBR[d.getMonth()]} ${d.getDate()} ${d.getFullYear()}`;
244
+ }
245
+ function formatFooter(resolvedMs, hasTime) {
246
+ const parts = [formatDate(resolvedMs)];
247
+ if (hasTime) {
248
+ const d = new Date(resolvedMs);
249
+ parts.push(`${pad2(d.getHours())}:${pad2(d.getMinutes())}`);
250
+ }
251
+ return parts.join(" \xB7 ");
252
+ }
253
+
254
+ // src/countdown/ticker.ts
255
+ var DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/;
256
+ function dayStartLocal(ms) {
257
+ const d = new Date(ms);
258
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate()).getTime();
259
+ }
260
+ function dayDeltaLocal(aMs, bMs) {
261
+ const a = new Date(aMs);
262
+ const b = new Date(bMs);
263
+ const a0 = new Date(a.getFullYear(), a.getMonth(), a.getDate()).getTime();
264
+ const b0 = new Date(b.getFullYear(), b.getMonth(), b.getDate()).getTime();
265
+ return Math.round((b0 - a0) / DAY_MS);
266
+ }
267
+ function pad22(n) {
268
+ return n < 10 ? "0" + n : String(n);
269
+ }
270
+ function polar(cx, cy, r, deg) {
271
+ const a = (deg - 90) * Math.PI / 180;
272
+ return [cx + r * Math.cos(a), cy + r * Math.sin(a)];
273
+ }
274
+ function arcPath(cx, cy, r, frac) {
275
+ frac = Math.max(0, Math.min(0.9999, frac));
276
+ const [x0, y0] = polar(cx, cy, r, 0);
277
+ const [x1, y1] = polar(cx, cy, r, frac * 360);
278
+ return `M ${x0} ${y0} A ${r} ${r} 0 ${frac > 0.5 ? 1 : 0} 1 ${x1} ${y1}`;
279
+ }
280
+ function updateGauges(svg, remaining) {
281
+ const rem = Math.abs(remaining);
282
+ const vals = {
283
+ h: Math.floor(rem / 36e5),
284
+ m: Math.floor(rem % 36e5 / 6e4),
285
+ s: Math.floor(rem % 6e4 / 1e3)
286
+ };
287
+ const fracs = {
288
+ h: vals["h"] % 24 / 24,
289
+ m: vals["m"] / 60,
290
+ s: vals["s"] / 60
291
+ };
292
+ svg.querySelectorAll("[data-dgmo-gauge-val]").forEach((el) => {
293
+ const k = el.getAttribute("data-dgmo-gauge-val");
294
+ const v = pad22(vals[k] ?? 0);
295
+ if (el.textContent !== v) el.textContent = v;
296
+ });
297
+ svg.querySelectorAll("[data-dgmo-gauge-arc]").forEach((el) => {
298
+ const k = el.getAttribute("data-dgmo-gauge-arc");
299
+ const cx = Number(el.getAttribute("data-cx"));
300
+ const cy = Number(el.getAttribute("data-cy"));
301
+ const r = Number(el.getAttribute("data-r"));
302
+ const f = fracs[k] ?? 0;
303
+ el.setAttribute("d", f > 1e-3 ? arcPath(cx, cy, r, f) : "");
304
+ });
305
+ const cap = svg.querySelector("[data-dgmo-gauge-caption]");
306
+ if (cap) {
307
+ const t = remaining >= 0 ? "TO GO" : "AGO";
308
+ if (cap.textContent !== t) cap.textContent = t;
309
+ }
310
+ }
311
+ function targetToMs(target) {
312
+ const s = target.trim();
313
+ if (DATE_ONLY_RE.test(s)) {
314
+ const p = s.split("-").map(Number);
315
+ return new Date(p[0], p[1] - 1, p[2]).getTime();
316
+ }
317
+ const t = new Date(s).getTime();
318
+ return Number.isFinite(t) ? t : null;
319
+ }
320
+ function readRule(node) {
321
+ const kind = node.getAttribute("data-dgmo-recur-kind");
322
+ if (!kind) return null;
323
+ const num = (name) => {
324
+ const v = node.getAttribute(name);
325
+ return v === null ? void 0 : Number(v);
326
+ };
327
+ return {
328
+ kind,
329
+ month: num("data-dgmo-recur-month"),
330
+ day: num("data-dgmo-recur-day"),
331
+ nth: num("data-dgmo-recur-nth"),
332
+ weekday: num("data-dgmo-recur-weekday"),
333
+ hour: num("data-dgmo-recur-hour") ?? 0,
334
+ minute: num("data-dgmo-recur-minute") ?? 0,
335
+ allDay: node.getAttribute("data-dgmo-recur-allday") !== "0",
336
+ intervalN: num("data-dgmo-recur-interval-n"),
337
+ intervalUnit: node.getAttribute("data-dgmo-recur-interval-unit") ?? void 0,
338
+ anchorMs: num("data-dgmo-recur-anchor")
339
+ };
340
+ }
341
+ function sameLocalDay(a, b) {
342
+ const x = new Date(a);
343
+ const y = new Date(b);
344
+ return x.getFullYear() === y.getFullYear() && x.getMonth() === y.getMonth() && x.getDate() === y.getDate();
345
+ }
346
+ function readFields(node) {
347
+ const raw = node.getAttribute("data-dgmo-countdown-fields");
348
+ if (!raw) return ["d", "h", "m", "s"];
349
+ return raw.split(",").filter((f) => ["d", "h", "m", "s"].includes(f));
350
+ }
351
+ function updateNode(node, now) {
352
+ const rule = readRule(node);
353
+ const units = node.getAttribute("data-dgmo-countdown-units") || "days";
354
+ const round = node.getAttribute("data-dgmo-countdown-round") || "up";
355
+ const fields = readFields(node);
356
+ const onDay = node.getAttribute("data-dgmo-countdown-onday");
357
+ const title = node.getAttribute("data-dgmo-countdown-title");
358
+ let resolvedMs;
359
+ let expiredNow = false;
360
+ if (rule) {
361
+ resolvedMs = resolveNext(rule, now);
362
+ } else {
363
+ const target = node.getAttribute("data-dgmo-countdown");
364
+ resolvedMs = target ? targetToMs(target) : null;
365
+ if (resolvedMs !== null && resolvedMs - now <= 0) expiredNow = true;
366
+ }
367
+ if (resolvedMs === null) return;
368
+ const remaining = resolvedMs - now;
369
+ const sinceAttr = node.getAttribute("data-dgmo-countdown-since");
370
+ const custom = node.getAttribute("data-dgmo-countdown-expired");
371
+ const hasTime = node.getAttribute("data-dgmo-countdown-hastime") === "1";
372
+ const frozen = expiredNow && custom !== null;
373
+ const clockFinale = hasTime && !frozen && dayDeltaLocal(now, resolvedMs) <= 0;
374
+ let text;
375
+ if (frozen) {
376
+ text = custom;
377
+ } else if (clockFinale) {
378
+ const clock = formatCount(Math.abs(remaining), {
379
+ units: "clock",
380
+ round,
381
+ fields
382
+ });
383
+ text = remaining < 0 ? `${clock} ago` : clock;
384
+ } else if (expiredNow) {
385
+ if (units === "compound" || units === "human")
386
+ text = `${formatCompound(resolvedMs, now)} ago`;
387
+ else {
388
+ const elapsed = -remaining;
389
+ text = elapsed <= 0 ? "Now!" : `${formatCount(elapsed, { units, round, fields })} ago`;
390
+ }
391
+ } else if (rule && sameLocalDay(resolvedMs, now)) {
392
+ text = onDay ?? "Today!";
393
+ } else if (units === "human") {
394
+ text = hasTime ? formatHuman(now, resolvedMs).big : formatHuman(dayStartLocal(now), dayStartLocal(resolvedMs)).big;
395
+ } else if (units === "compound") {
396
+ text = formatCompound(now, resolvedMs);
397
+ } else {
398
+ text = formatCount(Math.max(0, remaining), { units, round, fields });
399
+ }
400
+ if (node.textContent !== text) node.textContent = text;
401
+ node.setAttribute(
402
+ "aria-label",
403
+ title ? `${title}: ${text}` : expiredNow ? text : `${text} remaining`
404
+ );
405
+ const svg = node.ownerSVGElement ?? node.closest("svg");
406
+ if (!svg) return;
407
+ const footer = svg.querySelector("[data-dgmo-countdown-footer]");
408
+ if (footer && !expiredNow) {
409
+ const hasTime2 = node.getAttribute("data-dgmo-countdown-hastime") === "1";
410
+ footer.textContent = formatFooter(resolvedMs, hasTime2);
411
+ }
412
+ const eyebrowEl = svg.querySelector("[data-dgmo-countdown-eyebrow]");
413
+ if (eyebrowEl && sinceAttr) {
414
+ const tpl = node.getAttribute("data-dgmo-countdown-since-label") || "Nth";
415
+ eyebrowEl.textContent = applyOrdinalTemplate(
416
+ tpl,
417
+ ordinalFor(resolvedMs, Number(sinceAttr))
418
+ );
419
+ }
420
+ const detail = svg.querySelector("[data-dgmo-countdown-detail]");
421
+ if (detail && !expiredNow && !clockFinale) {
422
+ if (units === "words") {
423
+ detail.textContent = formatWordsDetail(Math.max(0, remaining));
424
+ } else if (units === "human" && remaining > 0) {
425
+ detail.textContent = hasTime ? formatCount(remaining, { units: "clock", round, fields }) : formatHuman(dayStartLocal(now), dayStartLocal(resolvedMs)).sub;
426
+ }
427
+ }
428
+ if (clockFinale) updateGauges(svg, remaining);
429
+ const stamp = svg.querySelector("[data-dgmo-countdown-asof]");
430
+ if (stamp && stamp.parentNode) stamp.parentNode.removeChild(stamp);
431
+ }
432
+ function tickCountdowns(root = document) {
433
+ if (typeof document === "undefined") return;
434
+ const now = Date.now();
435
+ root.querySelectorAll("[data-dgmo-countdown]").forEach((node) => updateNode(node, now));
436
+ }
437
+ var intervalStarted = false;
438
+ function startCountdowns(root = document) {
439
+ if (typeof window === "undefined" || typeof document === "undefined") return;
440
+ tickCountdowns(root);
441
+ if (intervalStarted) return;
442
+ intervalStarted = true;
443
+ window.setInterval(() => tickCountdowns(document), 1e3);
444
+ }
445
+ export {
446
+ DAY_MS,
447
+ startCountdowns,
448
+ tickCountdowns
449
+ };
450
+ //# sourceMappingURL=countdown.js.map
@@ -0,0 +1,84 @@
1
+ import {
2
+ CHART_TYPE_DESCRIPTIONS,
3
+ chartTypeParsers,
4
+ getAllChartTypes,
5
+ getRenderCategory,
6
+ isExtendedChartType,
7
+ knownChartTypeIds,
8
+ looksLikeC4,
9
+ looksLikeGantt,
10
+ parseDgmo,
11
+ parseDgmoChartType
12
+ } from "./chunk-JFYIP7XU.js";
13
+ import "./chunk-Q4V4XQWV.js";
14
+ import "./chunk-BTUTZMPT.js";
15
+ import "./chunk-YNRDWYZP.js";
16
+ import "./chunk-3XPUDB7B.js";
17
+ import "./chunk-FL4IOQ37.js";
18
+ import "./chunk-PGEGPSCZ.js";
19
+ import "./chunk-DMJJ3CEQ.js";
20
+ import "./chunk-BDXC7MDK.js";
21
+ import "./chunk-XDXNDSKO.js";
22
+ import "./chunk-OHQEMBLI.js";
23
+ import "./chunk-TSA237IM.js";
24
+ import "./chunk-6IAA2QQ2.js";
25
+ import "./chunk-YZLMKW4T.js";
26
+ import "./chunk-3P2DKJCG.js";
27
+ import "./chunk-3LKM6FMW.js";
28
+ import "./chunk-OTP5KVLO.js";
29
+ import "./chunk-YB3FW24O.js";
30
+ import "./chunk-NNAAJVBU.js";
31
+ import "./chunk-XMVQ5F4M.js";
32
+ import "./chunk-UXOSVCLJ.js";
33
+ import "./chunk-FB2WHOEQ.js";
34
+ import "./chunk-U2HGPLZS.js";
35
+ import "./chunk-SU2VBK4L.js";
36
+ import "./chunk-5JSWBPZZ.js";
37
+ import "./chunk-XHVV4NVW.js";
38
+ import "./chunk-WWN566I7.js";
39
+ import "./chunk-O4VUFXIH.js";
40
+ import "./chunk-QQJUTGQH.js";
41
+ import "./chunk-S5GRONXL.js";
42
+ import "./chunk-2JJ2KNPI.js";
43
+ import "./chunk-O76UD6VJ.js";
44
+ import "./chunk-A3WQVFBS.js";
45
+ import "./chunk-6M5PJFQG.js";
46
+ import "./chunk-L3UHP6TD.js";
47
+ import "./chunk-USZW2HHU.js";
48
+ import "./chunk-7G2ER4CY.js";
49
+ import "./chunk-I7F2A35O.js";
50
+ import "./chunk-6BFZKBGD.js";
51
+ import "./chunk-I4IFRCUB.js";
52
+ import "./chunk-URSQLCTR.js";
53
+ import "./chunk-IYULMRBE.js";
54
+ import "./chunk-R3XI2VC7.js";
55
+ import "./chunk-P6T66CCZ.js";
56
+ import "./chunk-T4BRO3Z6.js";
57
+ import "./chunk-C3Z4DX3V.js";
58
+ import "./chunk-WCUHAQKB.js";
59
+ import "./chunk-GBE54KY6.js";
60
+ import "./chunk-I46X5VUA.js";
61
+ import "./chunk-2C56LEML.js";
62
+ import "./chunk-NXMESY4J.js";
63
+ import "./chunk-GL5UZ6NY.js";
64
+ import "./chunk-SCHXJTZL.js";
65
+ import "./chunk-FMDSF3OV.js";
66
+ import "./chunk-FFUN5D66.js";
67
+ import "./chunk-NOTA6TEI.js";
68
+ import "./chunk-OTAL43EH.js";
69
+ import "./chunk-TLVIHFRO.js";
70
+ import "./chunk-CXK4DZH3.js";
71
+ import "./chunk-5WRI5ZAA.js";
72
+ export {
73
+ CHART_TYPE_DESCRIPTIONS,
74
+ chartTypeParsers,
75
+ getAllChartTypes,
76
+ getRenderCategory,
77
+ isExtendedChartType,
78
+ knownChartTypeIds,
79
+ looksLikeC4,
80
+ looksLikeGantt,
81
+ parseDgmo,
82
+ parseDgmoChartType
83
+ };
84
+ //# sourceMappingURL=dgmo-router-QOMFCOOV.js.map
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  mapContentAspect,
3
3
  mapExportDimensions
4
- } from "./chunk-UWDO3O52.js";
5
- import "./chunk-YM2BSPYW.js";
4
+ } from "./chunk-JZNMXEIV.js";
5
+ import "./chunk-X2NMFTG3.js";
6
6
  import "./chunk-I5IGB56C.js";
7
7
  import "./chunk-HGN652MU.js";
8
8
  import "./chunk-7UEMZREK.js";
@@ -10,8 +10,8 @@ import "./chunk-A4GOY3PE.js";
10
10
  import "./chunk-HQCHYB5F.js";
11
11
  import "./chunk-5LWQH6QV.js";
12
12
  import "./chunk-P6T66CCZ.js";
13
- import "./chunk-HBVD67XG.js";
14
- import "./chunk-ZNQX6QWH.js";
13
+ import "./chunk-WCUHAQKB.js";
14
+ import "./chunk-OTAL43EH.js";
15
15
  import "./chunk-TLVIHFRO.js";
16
16
  import "./chunk-CXK4DZH3.js";
17
17
  import "./chunk-5WRI5ZAA.js";
@@ -19,4 +19,4 @@ export {
19
19
  mapContentAspect,
20
20
  mapExportDimensions
21
21
  };
22
- //# sourceMappingURL=dimensions-WSIJXT5Q.js.map
22
+ //# sourceMappingURL=dimensions-P5FSX2QN.js.map
package/dist/editor.js CHANGED
@@ -139,6 +139,17 @@ var DIRECTIVES_REGISTRY = [
139
139
  { token: "import", category: "directive" },
140
140
  { token: "hide", category: "directive" },
141
141
  { token: "direction", category: "directive" },
142
+ // ── Clock ────────────────────────────────────────────────
143
+ // The board's flat directives — highlight-only (the parser validates them
144
+ // inline, no extractable Set). `no-title` + `direction` are already listed
145
+ // above (shared). Values (analog is a bare flag; color-by's
146
+ // place|work|daylight|time|none) highlight as plain tokens.
147
+ { token: "analog", category: "directive" },
148
+ { token: "hours", category: "directive" },
149
+ { token: "days", category: "directive" },
150
+ { token: "no-sun", category: "directive" },
151
+ { token: "time-24", category: "directive" },
152
+ { token: "color-by", category: "directive" },
142
153
  // ── Boxes-and-lines ──────────────────────────────────────
143
154
  // `heat <Label> [low] [high]` is the value→colour ramp directive, but `heat`
144
155
  // is NOT registered as a directive keyword: it is dual-use with the `heat:`
@@ -231,6 +242,17 @@ var DIRECTIVES_REGISTRY = [
231
242
  { token: "scrubber-trials", category: "directive" },
232
243
  { token: "start-date", category: "directive" },
233
244
  { token: "end-date", category: "directive" },
245
+ // ── Goal ─────────────────────────────────────────────────
246
+ // Mode flags, value-key leaders, and opt-outs. `now`/`target` collide with
247
+ // label prose ("start now", "on target"), but the arrow-label demotion pass
248
+ // (see highlight-api LABEL_WORD_NODES) treats any keyword-bearing label as
249
+ // text and demotes them, so they highlight only as line leaders.
250
+ { token: "thermometer", category: "directive" },
251
+ { token: "gauge", category: "directive" },
252
+ { token: "now", category: "directive" },
253
+ { token: "target", category: "directive" },
254
+ { token: "no-note", category: "directive" },
255
+ { token: "no-auto-color", category: "directive" },
234
256
  // ── Control-flow + wireframe element keywords (CONTROL) ──
235
257
  { token: "if", category: "control" },
236
258
  { token: "else", category: "control" },
@@ -272,7 +294,19 @@ var DIRECTIVES_REGISTRY = [
272
294
  { token: "date", category: "modifier" },
273
295
  { token: "timestamp", category: "modifier" },
274
296
  { token: "float", category: "modifier" },
275
- { token: "decimal", category: "modifier" }
297
+ { token: "decimal", category: "modifier" },
298
+ // ── Bracket ──────────────────────────────────────────────
299
+ // `beats` / `vs` are the infix match keywords; the rest are line directives.
300
+ // (`seed` and `no-legend` already appear above — shared, not re-listed.)
301
+ { token: "beats", category: "control" },
302
+ { token: "vs", category: "control" },
303
+ { token: "rounds", category: "directive" },
304
+ { token: "accent", category: "directive" },
305
+ { token: "no-round", category: "modifier" },
306
+ { token: "no-rounds", category: "modifier" },
307
+ { token: "single-elim", category: "modifier" },
308
+ { token: "double-elim", category: "modifier" },
309
+ { token: "seeded", category: "modifier" }
276
310
  ];
277
311
  function tokensWhere(pred) {
278
312
  const out = /* @__PURE__ */ new Set();
@@ -325,6 +359,7 @@ var CHART_TYPES = /* @__PURE__ */ new Set([
325
359
  "c4",
326
360
  "state",
327
361
  "sitemap",
362
+ "bracket",
328
363
  "infra",
329
364
  "gantt",
330
365
  "pert",
@@ -339,6 +374,9 @@ var CHART_TYPES = /* @__PURE__ */ new Set([
339
374
  "ring",
340
375
  "treemap",
341
376
  "block",
377
+ "goal",
378
+ "countdown",
379
+ "clock",
342
380
  "sketch",
343
381
  "raci",
344
382
  "cycle",