@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,482 @@
1
+ import {
2
+ monthIndex,
3
+ resolveNext,
4
+ weekdayIndex
5
+ } from "./chunk-YNRDWYZP.js";
6
+ import {
7
+ extractColor,
8
+ measureIndent,
9
+ parseFirstLine
10
+ } from "./chunk-OTAL43EH.js";
11
+ import {
12
+ makeDgmoError,
13
+ makeFail
14
+ } from "./chunk-CXK4DZH3.js";
15
+
16
+ // src/countdown/parser.ts
17
+ var DATE_ONLY_RE = /^\d{4}-\d{2}-\d{2}$/;
18
+ var TIME_24H_RE = /^(\d{1,2}):(\d{2})$/;
19
+ var AMPM_RE = /^(\d{1,2})(?::(\d{2}))?\s*([ap]m)$/i;
20
+ function targetToMs(target) {
21
+ const s = target.trim();
22
+ if (DATE_ONLY_RE.test(s)) {
23
+ const [y, m, d] = s.split("-").map(Number);
24
+ return new Date(y, m - 1, d).getTime();
25
+ }
26
+ const t = new Date(s).getTime();
27
+ return Number.isFinite(t) ? t : null;
28
+ }
29
+ function ordinalToken(tok) {
30
+ const t = tok.toLowerCase();
31
+ if (t === "last") return -1;
32
+ const m = t.match(/^(\d+)(?:st|nd|rd|th)?$/);
33
+ if (m) {
34
+ const n = Number(m[1]);
35
+ return n >= 1 && n <= 5 ? n : null;
36
+ }
37
+ return null;
38
+ }
39
+ function parse24h(tok) {
40
+ const m = tok.match(TIME_24H_RE);
41
+ if (!m) return null;
42
+ const hour = Number(m[1]);
43
+ const minute = Number(m[2]);
44
+ if (hour > 23 || minute > 59) return null;
45
+ return { hour, minute };
46
+ }
47
+ function parseAt(rest, lineNum, softError) {
48
+ const t = parse24h(rest.trim());
49
+ if (t) return t;
50
+ const fixed = ampmTo24h(rest.trim());
51
+ softError(
52
+ lineNum,
53
+ fixed ? `"at ${rest}": use 24h time (${fixed}).` : `"at" needs a 24h time like 18:00 (got "${rest}").`
54
+ );
55
+ return null;
56
+ }
57
+ function ampmTo24h(tok) {
58
+ const m = tok.match(AMPM_RE);
59
+ if (!m) return null;
60
+ let hour = Number(m[1]) % 12;
61
+ if (/pm/i.test(m[3])) hour += 12;
62
+ return `${String(hour).padStart(2, "0")}:${m[2] ?? "00"}`;
63
+ }
64
+ function parseCountdown(content, palette) {
65
+ const result = {
66
+ type: "countdown",
67
+ title: null,
68
+ titleLineNumber: null,
69
+ target: null,
70
+ targetMs: null,
71
+ rule: null,
72
+ resolvedMs: null,
73
+ hasTime: false,
74
+ since: null,
75
+ sinceLabel: null,
76
+ units: "human",
77
+ round: "up",
78
+ fields: ["d", "h", "m", "s"],
79
+ lang: "en",
80
+ onDay: null,
81
+ expired: null,
82
+ note: null,
83
+ noVisual: false,
84
+ diagnostics: [],
85
+ error: null
86
+ };
87
+ const fail = makeFail(result);
88
+ const warn = (line, message) => {
89
+ result.diagnostics.push(makeDgmoError(line, message, "warning"));
90
+ };
91
+ const softError = (line, message) => {
92
+ result.diagnostics.push(makeDgmoError(line, message, "error"));
93
+ };
94
+ if (!content?.trim()) return fail(0, "No content provided");
95
+ const lines = content.split("\n");
96
+ let headerParsed = false;
97
+ let cadence = null;
98
+ let intervalN = 1;
99
+ let intervalUnit = "day";
100
+ let onSpec = null;
101
+ let atTime = null;
102
+ let anchorMs = null;
103
+ let targetMs = null;
104
+ let hasTargetLine = false;
105
+ let hasEvery = false;
106
+ const noteBody = [];
107
+ let inNote = false;
108
+ for (let i = 0; i < lines.length; i++) {
109
+ const lineNum = i + 1;
110
+ const raw = lines[i];
111
+ const trimmed = raw.trim();
112
+ if (!trimmed || trimmed.startsWith("//")) continue;
113
+ if (!headerParsed) {
114
+ const first = parseFirstLine(trimmed);
115
+ if (first?.chartType !== "countdown") {
116
+ return fail(lineNum, 'Expected "countdown [Title]" as the first line.');
117
+ }
118
+ if (first.title) {
119
+ const { label, color } = extractColor(
120
+ first.title,
121
+ palette,
122
+ result.diagnostics,
123
+ lineNum
124
+ );
125
+ result.title = label || null;
126
+ if (color !== void 0) result.color = color;
127
+ }
128
+ result.titleLineNumber = lineNum;
129
+ headerParsed = true;
130
+ continue;
131
+ }
132
+ if (measureIndent(raw) > 0) {
133
+ if (inNote) noteBody.push(trimmed);
134
+ else
135
+ warn(
136
+ lineNum,
137
+ `Indented content "${trimmed}" ignored \u2014 countdown is a single value.`
138
+ );
139
+ continue;
140
+ }
141
+ const [keyword, ...restTokens] = trimmed.split(/\s+/);
142
+ const key = keyword.toLowerCase();
143
+ const rest = restTokens.join(" ").trim();
144
+ if (key !== "note") inNote = false;
145
+ switch (key) {
146
+ // ── One-shot target ──
147
+ case "target": {
148
+ hasTargetLine = true;
149
+ if (/^now$/i.test(rest)) {
150
+ targetMs = Date.now();
151
+ result.target = new Date(targetMs).toISOString();
152
+ result.hasTime = true;
153
+ } else {
154
+ const ms = targetToMs(rest);
155
+ if (ms === null) {
156
+ softError(
157
+ lineNum,
158
+ `"target" needs an ISO date/datetime or "now" (got "${rest}").`
159
+ );
160
+ } else {
161
+ result.target = rest;
162
+ targetMs = ms;
163
+ if (rest.includes("T")) result.hasTime = true;
164
+ }
165
+ }
166
+ break;
167
+ }
168
+ // ── Recurrence — the canonical single-line form:
169
+ // every <cadence> [on <instant>] [at <time>] [from <date>]
170
+ // (each slot may also appear on its own line — handled below.)
171
+ case "every": {
172
+ hasEvery = true;
173
+ const seg = {
174
+ cadence: [],
175
+ on: [],
176
+ at: [],
177
+ from: []
178
+ };
179
+ let cur = "cadence";
180
+ for (const tok of rest.split(/\s+/).filter(Boolean)) {
181
+ const low = tok.toLowerCase();
182
+ if (low === "on" || low === "at" || low === "from") cur = low;
183
+ else seg[cur].push(tok);
184
+ }
185
+ const head = (seg.cadence[0] ?? "").toLowerCase();
186
+ const bareInterval = (head === "day" || head === "week" || head === "month") && seg.from.length > 0 && seg.on.length === 0;
187
+ if (bareInterval) {
188
+ cadence = "interval";
189
+ intervalN = 1;
190
+ intervalUnit = head;
191
+ } else if (head === "year" || head === "month" || head === "week") {
192
+ cadence = head;
193
+ } else if (head === "day") {
194
+ softError(
195
+ lineNum,
196
+ "`every day` needs `from <date>` (an interval anchor)."
197
+ );
198
+ break;
199
+ } else if (/^\d+$/.test(head) && seg.cadence[1]) {
200
+ const unit = seg.cadence[1].toLowerCase().replace(/s$/, "");
201
+ if (unit === "day" || unit === "week" || unit === "month") {
202
+ cadence = "interval";
203
+ intervalN = Number(head);
204
+ intervalUnit = unit;
205
+ } else {
206
+ softError(
207
+ lineNum,
208
+ `every ${head} <days|weeks|months> \u2014 unknown unit "${seg.cadence[1]}".`
209
+ );
210
+ }
211
+ } else {
212
+ emitFreeProseError(lineNum, rest, softError);
213
+ break;
214
+ }
215
+ if (seg.on.length)
216
+ onSpec = parseOn(seg.on.join(" "), lineNum, softError);
217
+ if (seg.at.length)
218
+ atTime = parseAt(seg.at.join(" "), lineNum, softError);
219
+ if (seg.from.length) {
220
+ const ms = targetToMs(seg.from.join(" "));
221
+ if (ms === null) {
222
+ softError(
223
+ lineNum,
224
+ `"from" needs an anchor date (got "${seg.from.join(" ")}").`
225
+ );
226
+ } else {
227
+ anchorMs = ms;
228
+ }
229
+ }
230
+ break;
231
+ }
232
+ // Standalone slot lines (lenient multi-line form).
233
+ case "on": {
234
+ onSpec = parseOn(rest, lineNum, softError);
235
+ break;
236
+ }
237
+ case "at": {
238
+ const t = parseAt(rest, lineNum, softError);
239
+ if (t) atTime = t;
240
+ break;
241
+ }
242
+ case "from": {
243
+ const ms = targetToMs(rest);
244
+ if (ms === null) {
245
+ softError(lineNum, `"from" needs an anchor date (got "${rest}").`);
246
+ } else {
247
+ anchorMs = ms;
248
+ }
249
+ break;
250
+ }
251
+ // ── Ordinal / since — numbers a yearly occurrence (resolvedYear − since) ──
252
+ case "since": {
253
+ const y = Number(rest);
254
+ if (Number.isInteger(y) && y > 0) result.since = y;
255
+ else softError(lineNum, `"since" needs a year (got "${rest}").`);
256
+ break;
257
+ }
258
+ // The eyebrow template: `Nth` → ordinal word, `N` → the number.
259
+ case "since-label":
260
+ result.sinceLabel = rest || null;
261
+ break;
262
+ // ── Display ──
263
+ case "units": {
264
+ const v = rest.toLowerCase();
265
+ if (v === "human" || v === "days" || v === "full" || v === "clock" || v === "weeks" || v === "words" || v === "compound") {
266
+ result.units = v;
267
+ } else {
268
+ warn(
269
+ lineNum,
270
+ `Unknown units "${rest}" \u2014 use human|days|full|clock|weeks|words.`
271
+ );
272
+ }
273
+ break;
274
+ }
275
+ case "round": {
276
+ const v = rest.toLowerCase();
277
+ if (v === "up" || v === "down" || v === "nearest") {
278
+ result.round = v;
279
+ } else {
280
+ warn(lineNum, `Unknown round "${rest}" \u2014 use up|down|nearest.`);
281
+ }
282
+ break;
283
+ }
284
+ case "fields": {
285
+ const set = rest.split(/[,\s]+/).map((f) => f.toLowerCase()).filter((f) => ["d", "h", "m", "s"].includes(f));
286
+ if (set.length) result.fields = set;
287
+ else
288
+ warn(lineNum, `"fields" needs a subset of d,h,m,s (got "${rest}").`);
289
+ break;
290
+ }
291
+ case "lang":
292
+ result.lang = rest.toLowerCase() || "en";
293
+ break;
294
+ case "on-day":
295
+ result.onDay = rest || null;
296
+ break;
297
+ case "expired":
298
+ result.expired = rest;
299
+ break;
300
+ // ── `no-visual` — suppress the default-on calendar band (§36.6). ──
301
+ case "no-visual":
302
+ result.noVisual = true;
303
+ break;
304
+ // ── `note` — markdown caption; inline value or indented body block. ──
305
+ case "note":
306
+ inNote = true;
307
+ if (rest) noteBody.push(rest);
308
+ break;
309
+ default:
310
+ warn(lineNum, `Unrecognized line "${trimmed}".`);
311
+ }
312
+ }
313
+ if (noteBody.length) result.note = noteBody.join("\n");
314
+ if (hasEvery && hasTargetLine) {
315
+ softError(
316
+ result.titleLineNumber ?? 1,
317
+ "A countdown has either `target` (one-shot) or `every` (recurring), not both."
318
+ );
319
+ }
320
+ if (result.expired !== null && hasEvery) {
321
+ warn(
322
+ result.titleLineNumber ?? 1,
323
+ "`expired` applies only to one-shot `target` blocks; recurring blocks roll forward."
324
+ );
325
+ }
326
+ if (hasEvery && cadence) {
327
+ const rule = assembleRule(
328
+ cadence,
329
+ { intervalN, intervalUnit, anchorMs },
330
+ onSpec,
331
+ atTime,
332
+ result.titleLineNumber ?? 1,
333
+ softError
334
+ );
335
+ if (rule) {
336
+ result.rule = rule;
337
+ result.resolvedMs = resolveNext(rule, Date.now());
338
+ if (atTime) result.hasTime = true;
339
+ }
340
+ } else if (targetMs !== null) {
341
+ result.targetMs = targetMs;
342
+ result.resolvedMs = targetMs;
343
+ }
344
+ if (result.resolvedMs === null && result.error === null) {
345
+ if (!hasEvery && !hasTargetLine) {
346
+ softError(
347
+ result.titleLineNumber ?? 1,
348
+ "Missing `target` or `every` \u2014 a countdown needs one."
349
+ );
350
+ }
351
+ }
352
+ return result;
353
+ }
354
+ function parseOn(rest, lineNum, softError) {
355
+ const toks = rest.split(/\s+/).filter(Boolean);
356
+ if (toks.length === 0) {
357
+ softError(
358
+ lineNum,
359
+ '"on" needs a target (e.g. "Aug 21", "3rd Tuesday", "Friday").'
360
+ );
361
+ return null;
362
+ }
363
+ const ord = ordinalToken(toks[0]);
364
+ if (ord !== null && toks[1]) {
365
+ const wd2 = weekdayIndex(toks[1]);
366
+ if (wd2 === null) {
367
+ softError(lineNum, `"${toks[1]}" is not a weekday.`);
368
+ return null;
369
+ }
370
+ return { nth: ord, weekday: wd2 };
371
+ }
372
+ const mo = monthIndex(toks[0]);
373
+ if (mo !== null && toks[1]) {
374
+ const day = Number(toks[1].replace(/(st|nd|rd|th)$/i, ""));
375
+ if (!Number.isInteger(day) || day < 1 || day > 31) {
376
+ softError(lineNum, `"${toks[1]}" is not a valid day of month.`);
377
+ return null;
378
+ }
379
+ return { month: mo, day };
380
+ }
381
+ const wd = weekdayIndex(toks[0]);
382
+ if (wd !== null) return { weekday: wd };
383
+ softError(
384
+ lineNum,
385
+ `"on ${rest}" \u2014 use "Aug 21", "3rd Tuesday", "last Friday", or a weekday.`
386
+ );
387
+ return null;
388
+ }
389
+ function assembleRule(cadence, interval, onSpec, atTime, lineNum, softError) {
390
+ const hour = atTime?.hour ?? 0;
391
+ const minute = atTime?.minute ?? 0;
392
+ const allDay = atTime === null;
393
+ if (cadence === "interval") {
394
+ return {
395
+ kind: "interval",
396
+ intervalN: interval.intervalN,
397
+ intervalUnit: interval.intervalUnit,
398
+ anchorMs: interval.anchorMs ?? void 0,
399
+ hour,
400
+ minute,
401
+ allDay
402
+ };
403
+ }
404
+ if (cadence === "year") {
405
+ if (onSpec?.month === void 0 || onSpec.day === void 0) {
406
+ softError(
407
+ lineNum,
408
+ "`every year` needs `on <Month> <Day>` (e.g. on Aug 21)."
409
+ );
410
+ return null;
411
+ }
412
+ return {
413
+ kind: "month-day",
414
+ month: onSpec.month,
415
+ day: onSpec.day,
416
+ hour,
417
+ minute,
418
+ allDay
419
+ };
420
+ }
421
+ if (cadence === "month") {
422
+ if (onSpec?.nth === void 0 || onSpec.weekday === void 0) {
423
+ softError(
424
+ lineNum,
425
+ "`every month` needs `on <nth> <weekday>`, `on last <weekday>`, or `from <date>`."
426
+ );
427
+ return null;
428
+ }
429
+ return onSpec.nth < 0 ? { kind: "last-weekday", weekday: onSpec.weekday, hour, minute, allDay } : {
430
+ kind: "nth-weekday",
431
+ nth: onSpec.nth,
432
+ weekday: onSpec.weekday,
433
+ hour,
434
+ minute,
435
+ allDay
436
+ };
437
+ }
438
+ if (onSpec?.weekday === void 0 || onSpec.nth !== void 0) {
439
+ softError(lineNum, "`every week` needs `on <weekday>` (e.g. on Friday).");
440
+ return null;
441
+ }
442
+ return { kind: "weekly", weekday: onSpec.weekday, hour, minute, allDay };
443
+ }
444
+ function emitFreeProseError(lineNum, rest, softError) {
445
+ const toks = rest.split(/\s+/).filter(Boolean);
446
+ const problems = [];
447
+ let onPart = "";
448
+ let atPart = "";
449
+ for (let i = 0; i < toks.length; i++) {
450
+ const tok = toks[i];
451
+ const fixed = ampmTo24h(tok);
452
+ if (fixed) {
453
+ problems.push(`"${tok}": use 24h time (${fixed})`);
454
+ atPart = ` at ${fixed}`;
455
+ continue;
456
+ }
457
+ const wd = weekdayIndex(tok);
458
+ if (wd !== null) onPart = `week on ${tok}`;
459
+ const mo = monthIndex(tok);
460
+ if (mo !== null && toks[i + 1] && /^\d/.test(toks[i + 1])) {
461
+ onPart = `year on ${tok} ${toks[i + 1]}`;
462
+ }
463
+ }
464
+ if (!onPart && !atPart) {
465
+ problems.push(
466
+ "bare cadence \u2014 use year | month | week | N days|weeks|months"
467
+ );
468
+ } else if (onPart.startsWith("week")) {
469
+ problems.push("bare weekday needs a cadence");
470
+ }
471
+ const suggestion = onPart ? `every ${onPart}${atPart}` : `every week \u2026${atPart}`;
472
+ softError(
473
+ lineNum,
474
+ `Free-form recurrence rejected. ${problems.join("; ")}. Try: ${suggestion}`
475
+ );
476
+ }
477
+
478
+ export {
479
+ targetToMs,
480
+ parseCountdown
481
+ };
482
+ //# sourceMappingURL=chunk-BTUTZMPT.js.map
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  resolveTagColor,
6
6
  tagAttrKey
7
- } from "./chunk-HBVD67XG.js";
7
+ } from "./chunk-WCUHAQKB.js";
8
8
  import {
9
9
  resolveColor
10
10
  } from "./chunk-TLVIHFRO.js";
@@ -164,4 +164,4 @@ export {
164
164
  formatPct,
165
165
  buildLegend
166
166
  };
167
- //# sourceMappingURL=chunk-UVCXZEGU.js.map
167
+ //# sourceMappingURL=chunk-CVV36CWI.js.map
@@ -1,19 +1,20 @@
1
1
  import {
2
+ BODY_TERMS,
2
3
  getFigure,
3
4
  resolvePartKey
4
- } from "./chunk-3NFJWFNP.js";
5
+ } from "./chunk-BDXC7MDK.js";
5
6
  import {
6
7
  AUTO_TAG_COLOR_SENTINEL,
7
8
  finalizeAutoTagColors,
8
9
  matchTagBlockHeading,
9
10
  stripDefaultModifier,
10
11
  tagAttrKey
11
- } from "./chunk-HBVD67XG.js";
12
+ } from "./chunk-WCUHAQKB.js";
12
13
  import {
13
14
  extractColor,
14
15
  measureIndent,
15
16
  parseFirstLine
16
- } from "./chunk-ZNQX6QWH.js";
17
+ } from "./chunk-OTAL43EH.js";
17
18
  import {
18
19
  emit
19
20
  } from "./chunk-CXK4DZH3.js";
@@ -189,17 +190,28 @@ function parseBody(content, palette) {
189
190
  if (m) {
190
191
  contentStarted = true;
191
192
  currentTagGroup = null;
193
+ let name = m[1];
194
+ let rest = m[2] ?? "";
195
+ let side;
196
+ if (/^(left|right)$/i.test(name)) {
197
+ const m2 = rest.match(PART_RE);
198
+ if (m2) {
199
+ side = name.toLowerCase();
200
+ name = m2[1];
201
+ rest = m2[2] ?? "";
202
+ }
203
+ }
192
204
  const metadata = {};
193
- const rest = m[2] ?? "";
194
205
  for (const pair of rest.matchAll(META_PAIR_RE)) {
195
206
  const key = aliasMap.get(pair[1].toLowerCase()) ?? tagAttrKey(pair[1]);
196
207
  metadata[key] = pair[2].trim();
197
208
  }
198
209
  currentPart = {
199
- name: m[1],
210
+ name,
200
211
  metadata,
201
212
  notes: [],
202
- lineNumber
213
+ lineNumber,
214
+ ...side && { side }
203
215
  };
204
216
  parts.push(currentPart);
205
217
  continue;
@@ -225,9 +237,18 @@ function parseBody(content, palette) {
225
237
  diagnostics
226
238
  };
227
239
  }
240
+ function extractSymbols(docText) {
241
+ const parsed = parseBody(docText);
242
+ const figures = (parsed.options.views.length ? parsed.options.views : ["front"]).map((v) => getFigure(parsed.options.sex, v));
243
+ const entities = BODY_TERMS.filter(
244
+ (t) => t === "left" || t === "right" || figures.some((f) => resolvePartKey(f, t))
245
+ );
246
+ return { kind: "body", entities };
247
+ }
228
248
 
229
249
  export {
230
250
  BODY_DIAGNOSTICS,
231
- parseBody
251
+ parseBody,
252
+ extractSymbols
232
253
  };
233
- //# sourceMappingURL=chunk-TC3YWBFO.js.map
254
+ //# sourceMappingURL=chunk-DMJJ3CEQ.js.map
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-YYVTBZZC.js";
10
10
  import {
11
11
  renderIntegratedLegend
12
- } from "./chunk-4UDKKGHH.js";
12
+ } from "./chunk-QUJGFNFN.js";
13
13
  import {
14
14
  getMaxLegendReservedHeight
15
15
  } from "./chunk-7UEMZREK.js";
@@ -29,7 +29,7 @@ import {
29
29
  } from "./chunk-5LWQH6QV.js";
30
30
  import {
31
31
  parseKanban
32
- } from "./chunk-O5MQPK7E.js";
32
+ } from "./chunk-I4IFRCUB.js";
33
33
  import {
34
34
  contrastText,
35
35
  mix,
@@ -37,7 +37,7 @@ import {
37
37
  } from "./chunk-P6T66CCZ.js";
38
38
  import {
39
39
  tagAttrKey
40
- } from "./chunk-HBVD67XG.js";
40
+ } from "./chunk-WCUHAQKB.js";
41
41
 
42
42
  // src/kanban/renderer.ts
43
43
  import * as d3Selection from "d3-selection";
@@ -882,4 +882,4 @@ export {
882
882
  renderKanban,
883
883
  renderKanbanForExport
884
884
  };
885
- //# sourceMappingURL=chunk-NVT6C2VE.js.map
885
+ //# sourceMappingURL=chunk-EJTKV7CY.js.map
@@ -17,7 +17,7 @@ import {
17
17
  import {
18
18
  resolveTagColor,
19
19
  tagAttrKey
20
- } from "./chunk-HBVD67XG.js";
20
+ } from "./chunk-WCUHAQKB.js";
21
21
  import {
22
22
  CATEGORICAL_COLOR_ORDER,
23
23
  resolveColor
@@ -113,7 +113,7 @@ function renderSwimlaneForExport(container, parsed, layout, palette, isDark, opt
113
113
  });
114
114
  const root = svg.append("g").attr("transform", `translate(0, ${titleOffset})`);
115
115
  if (parsed.title) {
116
- svg.append("text").attr("x", width / 2).attr("y", TITLE_Y).attr("text-anchor", "middle").attr("font-size", TITLE_FONT_SIZE).attr("font-weight", TITLE_FONT_WEIGHT).attr("fill", palette.text).text(parsed.title);
116
+ svg.append("text").attr("class", "chart-title").attr("x", width / 2).attr("y", TITLE_Y).attr("text-anchor", "middle").attr("font-size", TITLE_FONT_SIZE).attr("font-weight", TITLE_FONT_WEIGHT).attr("fill", palette.text).attr("data-line-number", String(parsed.titleLineNumber ?? 1)).text(parsed.title);
117
117
  }
118
118
  const laneColorById = /* @__PURE__ */ new Map();
119
119
  parsed.lanes.forEach((lane, i) => {
@@ -223,7 +223,7 @@ function renderSwimlaneForExport(container, parsed, layout, palette, isDark, opt
223
223
  marker = `sw-arrow-${ev.success.replace("#", "")}`;
224
224
  }
225
225
  const d = roundedPolyline(e.points, EDGE_CORNER_R);
226
- edgeG.append("path").attr("d", d).attr("fill", "none").attr("stroke", stroke).attr("stroke-width", EDGE_STROKE).attr("stroke-dasharray", e.back ? "5 4" : null).attr("marker-end", `url(#${marker})`).attr("data-line-number", String(e.lineNumber));
226
+ edgeG.append("path").attr("class", "sw-edge sw-edge-group").attr("d", d).attr("fill", "none").attr("stroke", stroke).attr("stroke-width", EDGE_STROKE).attr("stroke-dasharray", e.back ? "5 4" : null).attr("marker-end", `url(#${marker})`).attr("data-line-number", String(e.lineNumber));
227
227
  if (e.label) {
228
228
  const segLen = [];
229
229
  let total = 0;
@@ -300,7 +300,7 @@ function renderSwimlaneForExport(container, parsed, layout, palette, isDark, opt
300
300
  const nodesG = root.append("g").attr("class", "dgmo-swimlane-nodes");
301
301
  for (const n of layout.nodes) {
302
302
  const { fill, stroke } = nodeFill(n);
303
- const g = nodesG.append("g").attr("data-line-number", String(n.lineNumber)).attr("data-lane", n.lane);
303
+ const g = nodesG.append("g").attr("class", "sw-node").attr("data-node-id", n.id).attr("data-line-number", String(n.lineNumber)).attr("data-lane", n.lane);
304
304
  const cx = n.x;
305
305
  const cy = n.y;
306
306
  if (n.shape === "exclusive" || n.shape === "parallel") {
@@ -391,4 +391,4 @@ function drawCenteredLabel(g, label, cx, cy, fill, fontSize, tight = false, halo
391
391
  export {
392
392
  renderSwimlaneForExport
393
393
  };
394
- //# sourceMappingURL=chunk-4NQ3IKUM.js.map
394
+ //# sourceMappingURL=chunk-ER5SCQWQ.js.map
@@ -5,10 +5,10 @@ import {
5
5
  formatPct,
6
6
  resolveCellColor,
7
7
  resolveColorMode
8
- } from "./chunk-UVCXZEGU.js";
8
+ } from "./chunk-CVV36CWI.js";
9
9
  import {
10
10
  renderIntegratedLegend
11
- } from "./chunk-4UDKKGHH.js";
11
+ } from "./chunk-QUJGFNFN.js";
12
12
  import {
13
13
  getMaxLegendReservedHeight
14
14
  } from "./chunk-7UEMZREK.js";
@@ -33,7 +33,7 @@ import {
33
33
  } from "./chunk-P6T66CCZ.js";
34
34
  import {
35
35
  tagAttrKey
36
- } from "./chunk-HBVD67XG.js";
36
+ } from "./chunk-WCUHAQKB.js";
37
37
 
38
38
  // src/treemap/renderer.ts
39
39
  import * as d3Selection from "d3-selection";
@@ -381,4 +381,4 @@ export {
381
381
  renderTreemapForExport,
382
382
  renderTreemap
383
383
  };
384
- //# sourceMappingURL=chunk-7VLUGEMJ.js.map
384
+ //# sourceMappingURL=chunk-F5UDBSJY.js.map