schematex 0.5.0 → 0.6.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 (180) hide show
  1. package/README.md +89 -7
  2. package/dist/ai/ai-sdk.cjs +32 -26
  3. package/dist/ai/ai-sdk.cjs.map +1 -1
  4. package/dist/ai/ai-sdk.d.cts +4 -3
  5. package/dist/ai/ai-sdk.d.ts +4 -3
  6. package/dist/ai/ai-sdk.js +28 -22
  7. package/dist/ai/ai-sdk.js.map +1 -1
  8. package/dist/ai/index.cjs +37 -25
  9. package/dist/ai/index.d.cts +20 -153
  10. package/dist/ai/index.d.ts +20 -153
  11. package/dist/ai/index.js +17 -17
  12. package/dist/{api-C5UcmT7n.d.cts → api-XWHHAhQI.d.ts} +12 -2
  13. package/dist/{api-C5UcmT7n.d.ts → api-qVDutqXH.d.cts} +12 -2
  14. package/dist/browser.cjs +37 -19
  15. package/dist/browser.cjs.map +1 -1
  16. package/dist/browser.d.cts +12 -3
  17. package/dist/browser.d.ts +12 -3
  18. package/dist/browser.js +27 -19
  19. package/dist/browser.js.map +1 -1
  20. package/dist/{chunk-3YUUC6RN.cjs → chunk-25ZON47K.cjs} +4105 -267
  21. package/dist/chunk-25ZON47K.cjs.map +1 -0
  22. package/dist/{chunk-N7W5KZK7.cjs → chunk-2L4YXZAZ.cjs} +4 -4
  23. package/dist/{chunk-N7W5KZK7.cjs.map → chunk-2L4YXZAZ.cjs.map} +1 -1
  24. package/dist/{chunk-ZYRCPSBU.js → chunk-3IE7KZY4.js} +4 -4
  25. package/dist/{chunk-ZYRCPSBU.js.map → chunk-3IE7KZY4.js.map} +1 -1
  26. package/dist/{chunk-ZX74KJPM.js → chunk-3VB5AT4R.js} +3 -3
  27. package/dist/{chunk-ZX74KJPM.js.map → chunk-3VB5AT4R.js.map} +1 -1
  28. package/dist/{chunk-S2KJRHDZ.cjs → chunk-4UFR2LB6.cjs} +13 -13
  29. package/dist/{chunk-S2KJRHDZ.cjs.map → chunk-4UFR2LB6.cjs.map} +1 -1
  30. package/dist/{chunk-QTNPMIO2.cjs → chunk-6JI6FWLZ.cjs} +341 -35
  31. package/dist/chunk-6JI6FWLZ.cjs.map +1 -0
  32. package/dist/{chunk-GTDQAN2Z.js → chunk-7AFW2J6J.js} +4075 -247
  33. package/dist/chunk-7AFW2J6J.js.map +1 -0
  34. package/dist/{chunk-UFTYX73U.js → chunk-7F76AWOI.js} +339 -35
  35. package/dist/chunk-7F76AWOI.js.map +1 -0
  36. package/dist/{chunk-BW4KGTV7.cjs → chunk-C5C5EF3W.cjs} +4 -4
  37. package/dist/{chunk-BW4KGTV7.cjs.map → chunk-C5C5EF3W.cjs.map} +1 -1
  38. package/dist/{chunk-VJGMEGMR.js → chunk-DOK7LKLO.js} +125 -13
  39. package/dist/chunk-DOK7LKLO.js.map +1 -0
  40. package/dist/{chunk-3M6WB62Y.cjs → chunk-ECD5XHBM.cjs} +139 -7
  41. package/dist/chunk-ECD5XHBM.cjs.map +1 -0
  42. package/dist/{chunk-L3CTXXVZ.js → chunk-FBS3PACU.js} +3 -3
  43. package/dist/{chunk-L3CTXXVZ.js.map → chunk-FBS3PACU.js.map} +1 -1
  44. package/dist/{chunk-QUKVGHN4.cjs → chunk-FKJBXGWP.cjs} +4 -4
  45. package/dist/{chunk-QUKVGHN4.cjs.map → chunk-FKJBXGWP.cjs.map} +1 -1
  46. package/dist/{chunk-SZK376QB.js → chunk-H4MT5TJP.js} +3 -3
  47. package/dist/{chunk-SZK376QB.js.map → chunk-H4MT5TJP.js.map} +1 -1
  48. package/dist/{chunk-3M6T7KB4.js → chunk-HAZALB7U.js} +3 -3
  49. package/dist/{chunk-3M6T7KB4.js.map → chunk-HAZALB7U.js.map} +1 -1
  50. package/dist/{chunk-VFZOPRQP.cjs → chunk-HWVBHU3O.cjs} +5 -5
  51. package/dist/{chunk-VFZOPRQP.cjs.map → chunk-HWVBHU3O.cjs.map} +1 -1
  52. package/dist/{chunk-D7EHZFK4.cjs → chunk-L7POWM5B.cjs} +138 -2
  53. package/dist/chunk-L7POWM5B.cjs.map +1 -0
  54. package/dist/{chunk-6OSUNBZY.js → chunk-LGABFD3L.js} +135 -7
  55. package/dist/chunk-LGABFD3L.js.map +1 -0
  56. package/dist/{chunk-2VNMKOUO.js → chunk-LRI4RH2N.js} +135 -3
  57. package/dist/chunk-LRI4RH2N.js.map +1 -0
  58. package/dist/{chunk-VDSYMSUY.js → chunk-MVIEIKOI.js} +3 -3
  59. package/dist/{chunk-VDSYMSUY.js.map → chunk-MVIEIKOI.js.map} +1 -1
  60. package/dist/{chunk-ZL5RB4UV.js → chunk-N5B242WY.js} +3 -3
  61. package/dist/{chunk-ZL5RB4UV.js.map → chunk-N5B242WY.js.map} +1 -1
  62. package/dist/{chunk-IM4RCUHA.js → chunk-P63S7P6N.js} +1309 -69
  63. package/dist/chunk-P63S7P6N.js.map +1 -0
  64. package/dist/{chunk-YLEVMOK2.cjs → chunk-R66QG3XT.cjs} +5 -4
  65. package/dist/{chunk-YLEVMOK2.cjs.map → chunk-R66QG3XT.cjs.map} +1 -1
  66. package/dist/{chunk-I55HO32M.js → chunk-RJMCWT7Z.js} +3 -3
  67. package/dist/{chunk-I55HO32M.js.map → chunk-RJMCWT7Z.js.map} +1 -1
  68. package/dist/{chunk-TZTCIAYW.cjs → chunk-S3RMAXH5.cjs} +137 -25
  69. package/dist/chunk-S3RMAXH5.cjs.map +1 -0
  70. package/dist/{chunk-IBRW3UOA.js → chunk-TWLKXV2O.js} +3 -3
  71. package/dist/{chunk-IBRW3UOA.js.map → chunk-TWLKXV2O.js.map} +1 -1
  72. package/dist/{chunk-HUPDIRBX.js → chunk-UWA5MWCI.js} +949 -918
  73. package/dist/chunk-UWA5MWCI.js.map +1 -0
  74. package/dist/{chunk-RYVV5UVI.cjs → chunk-V4GILQR6.cjs} +4 -4
  75. package/dist/{chunk-RYVV5UVI.cjs.map → chunk-V4GILQR6.cjs.map} +1 -1
  76. package/dist/{chunk-XRCY75UV.cjs → chunk-V4RO5KYY.cjs} +951 -918
  77. package/dist/chunk-V4RO5KYY.cjs.map +1 -0
  78. package/dist/{chunk-VZ5LDNHK.cjs → chunk-VTSH4YPT.cjs} +12 -12
  79. package/dist/{chunk-VZ5LDNHK.cjs.map → chunk-VTSH4YPT.cjs.map} +1 -1
  80. package/dist/{chunk-UUBNQV2T.cjs → chunk-WQDIZH2Z.cjs} +12 -12
  81. package/dist/{chunk-UUBNQV2T.cjs.map → chunk-WQDIZH2Z.cjs.map} +1 -1
  82. package/dist/{chunk-EGSUMHCS.cjs → chunk-YB4XJY5L.cjs} +12 -12
  83. package/dist/{chunk-EGSUMHCS.cjs.map → chunk-YB4XJY5L.cjs.map} +1 -1
  84. package/dist/{chunk-JHDR56XO.js → chunk-YS6CGUNH.js} +3 -3
  85. package/dist/{chunk-JHDR56XO.js.map → chunk-YS6CGUNH.js.map} +1 -1
  86. package/dist/{chunk-NWPCY65Z.cjs → chunk-YVDUEUFV.cjs} +1311 -68
  87. package/dist/chunk-YVDUEUFV.cjs.map +1 -0
  88. package/dist/{types-BOAsqHoU.d.ts → diagnostics-DRxhodP6.d.cts} +74 -2
  89. package/dist/{types-BOAsqHoU.d.cts → diagnostics-DRxhodP6.d.ts} +74 -2
  90. package/dist/diagrams/blockdiagram/index.d.cts +1 -1
  91. package/dist/diagrams/blockdiagram/index.d.ts +1 -1
  92. package/dist/diagrams/circuit/index.cjs +8 -8
  93. package/dist/diagrams/circuit/index.d.cts +1 -1
  94. package/dist/diagrams/circuit/index.d.ts +1 -1
  95. package/dist/diagrams/circuit/index.js +2 -2
  96. package/dist/diagrams/ecomap/index.cjs +7 -7
  97. package/dist/diagrams/ecomap/index.d.cts +1 -1
  98. package/dist/diagrams/ecomap/index.d.ts +1 -1
  99. package/dist/diagrams/ecomap/index.js +2 -2
  100. package/dist/diagrams/entity/index.cjs +6 -6
  101. package/dist/diagrams/entity/index.d.cts +1 -1
  102. package/dist/diagrams/entity/index.d.ts +1 -1
  103. package/dist/diagrams/entity/index.js +2 -2
  104. package/dist/diagrams/fishbone/index.cjs +8 -8
  105. package/dist/diagrams/fishbone/index.d.cts +1 -1
  106. package/dist/diagrams/fishbone/index.d.ts +1 -1
  107. package/dist/diagrams/fishbone/index.js +2 -2
  108. package/dist/diagrams/flowchart/index.cjs +8 -8
  109. package/dist/diagrams/flowchart/index.d.cts +2 -2
  110. package/dist/diagrams/flowchart/index.d.ts +2 -2
  111. package/dist/diagrams/flowchart/index.js +2 -2
  112. package/dist/diagrams/genogram/index.cjs +9 -9
  113. package/dist/diagrams/genogram/index.d.cts +1 -1
  114. package/dist/diagrams/genogram/index.d.ts +1 -1
  115. package/dist/diagrams/genogram/index.js +2 -2
  116. package/dist/diagrams/ladder/index.cjs +6 -6
  117. package/dist/diagrams/ladder/index.d.cts +1 -1
  118. package/dist/diagrams/ladder/index.d.ts +1 -1
  119. package/dist/diagrams/ladder/index.js +2 -2
  120. package/dist/diagrams/logic/index.cjs +6 -6
  121. package/dist/diagrams/logic/index.d.cts +1 -1
  122. package/dist/diagrams/logic/index.d.ts +1 -1
  123. package/dist/diagrams/logic/index.js +2 -2
  124. package/dist/diagrams/orgchart/index.cjs +7 -7
  125. package/dist/diagrams/orgchart/index.d.cts +1 -1
  126. package/dist/diagrams/orgchart/index.d.ts +1 -1
  127. package/dist/diagrams/orgchart/index.js +2 -2
  128. package/dist/diagrams/pedigree/index.cjs +7 -7
  129. package/dist/diagrams/pedigree/index.d.cts +1 -1
  130. package/dist/diagrams/pedigree/index.d.ts +1 -1
  131. package/dist/diagrams/pedigree/index.js +2 -2
  132. package/dist/diagrams/phylo/index.cjs +7 -7
  133. package/dist/diagrams/phylo/index.d.cts +1 -1
  134. package/dist/diagrams/phylo/index.d.ts +1 -1
  135. package/dist/diagrams/phylo/index.js +2 -2
  136. package/dist/diagrams/sld/index.cjs +14 -6
  137. package/dist/diagrams/sld/index.d.cts +14 -2
  138. package/dist/diagrams/sld/index.d.ts +14 -2
  139. package/dist/diagrams/sld/index.js +2 -2
  140. package/dist/diagrams/sociogram/index.cjs +6 -6
  141. package/dist/diagrams/sociogram/index.d.cts +1 -1
  142. package/dist/diagrams/sociogram/index.d.ts +1 -1
  143. package/dist/diagrams/sociogram/index.js +2 -2
  144. package/dist/diagrams/timing/index.d.cts +1 -1
  145. package/dist/diagrams/timing/index.d.ts +1 -1
  146. package/dist/diagrams/venn/index.cjs +9 -9
  147. package/dist/diagrams/venn/index.d.cts +1 -1
  148. package/dist/diagrams/venn/index.d.ts +1 -1
  149. package/dist/diagrams/venn/index.js +2 -2
  150. package/dist/{index-CJai_TEZ.d.ts → index-BRIkOPnd.d.cts} +60 -2
  151. package/dist/{index-C9A0h-CB.d.cts → index-C7SN-FB3.d.ts} +60 -2
  152. package/dist/index.cjs +351 -51
  153. package/dist/index.cjs.map +1 -1
  154. package/dist/index.d.cts +38 -4
  155. package/dist/index.d.ts +38 -4
  156. package/dist/index.js +283 -16
  157. package/dist/index.js.map +1 -1
  158. package/dist/react.cjs +21 -23
  159. package/dist/react.cjs.map +1 -1
  160. package/dist/react.d.cts +3 -2
  161. package/dist/react.d.ts +3 -2
  162. package/dist/react.js +21 -23
  163. package/dist/react.js.map +1 -1
  164. package/dist/tools-BVeUNdsU.d.ts +161 -0
  165. package/dist/tools-DdhP1kWY.d.cts +161 -0
  166. package/package.json +2 -2
  167. package/dist/chunk-2VNMKOUO.js.map +0 -1
  168. package/dist/chunk-3M6WB62Y.cjs.map +0 -1
  169. package/dist/chunk-3YUUC6RN.cjs.map +0 -1
  170. package/dist/chunk-6OSUNBZY.js.map +0 -1
  171. package/dist/chunk-D7EHZFK4.cjs.map +0 -1
  172. package/dist/chunk-GTDQAN2Z.js.map +0 -1
  173. package/dist/chunk-HUPDIRBX.js.map +0 -1
  174. package/dist/chunk-IM4RCUHA.js.map +0 -1
  175. package/dist/chunk-NWPCY65Z.cjs.map +0 -1
  176. package/dist/chunk-QTNPMIO2.cjs.map +0 -1
  177. package/dist/chunk-TZTCIAYW.cjs.map +0 -1
  178. package/dist/chunk-UFTYX73U.js.map +0 -1
  179. package/dist/chunk-VJGMEGMR.js.map +0 -1
  180. package/dist/chunk-XRCY75UV.cjs.map +0 -1
@@ -1,23 +1,23 @@
1
- import { orgchart } from './chunk-I55HO32M.js';
2
- import { circuit } from './chunk-HUPDIRBX.js';
1
+ import { orgchart } from './chunk-RJMCWT7Z.js';
2
+ import { circuit } from './chunk-UWA5MWCI.js';
3
3
  import { blockdiagram } from './chunk-EPKIJEH7.js';
4
- import { ladder } from './chunk-IBRW3UOA.js';
5
- import { sld } from './chunk-6OSUNBZY.js';
6
- import { entity } from './chunk-SZK376QB.js';
7
- import { fishbone } from './chunk-3M6T7KB4.js';
8
- import { venn } from './chunk-L3CTXXVZ.js';
9
- import { flowchart, layoutFlowchart } from './chunk-UFTYX73U.js';
10
- import { genogram } from './chunk-VJGMEGMR.js';
11
- import { ecomap } from './chunk-JHDR56XO.js';
12
- import { pedigree } from './chunk-ZL5RB4UV.js';
4
+ import { ladder } from './chunk-TWLKXV2O.js';
5
+ import { sld } from './chunk-LGABFD3L.js';
6
+ import { entity } from './chunk-H4MT5TJP.js';
7
+ import { fishbone } from './chunk-HAZALB7U.js';
8
+ import { venn } from './chunk-FBS3PACU.js';
9
+ import { flowchart, layoutFlowchart } from './chunk-7F76AWOI.js';
10
+ import { genogram } from './chunk-DOK7LKLO.js';
11
+ import { ecomap } from './chunk-YS6CGUNH.js';
12
+ import { pedigree } from './chunk-N5B242WY.js';
13
13
  import { parseFrontmatter } from './chunk-2KTQ75LN.js';
14
- import { phylo } from './chunk-VDSYMSUY.js';
15
- import { sociogram } from './chunk-ZX74KJPM.js';
14
+ import { phylo } from './chunk-MVIEIKOI.js';
15
+ import { sociogram } from './chunk-3VB5AT4R.js';
16
16
  import { timing } from './chunk-6URNSB6G.js';
17
- import { logic } from './chunk-ZYRCPSBU.js';
18
- import { resolveBaseTheme, resolveTimelineTheme, cssCustomProperties, resolveMindmapTheme } from './chunk-2VNMKOUO.js';
19
- import { matchQuotedTitle } from './chunk-5IKOLUWK.js';
20
- import { el, escapeXml, title, desc, text, path, rect, group, svgRoot, defs, circle, polygon, line, multilineText } from './chunk-SYYBKDL7.js';
17
+ import { logic } from './chunk-3IE7KZY4.js';
18
+ import { resolveBaseTheme, resolveTimelineTheme, cssCustomProperties, resolvePetriTheme, resolveNetworkTheme, resolveMindmapTheme } from './chunk-LRI4RH2N.js';
19
+ import { matchQuotedTitle, stripQuotes } from './chunk-5IKOLUWK.js';
20
+ import { el, escapeXml, group, rect, text, line, path, circle, polygon, title, desc, svgRoot, defs, multilineText } from './chunk-SYYBKDL7.js';
21
21
 
22
22
  // src/diagrams/decisiontree/parser.ts
23
23
  var DTreeParseError = class extends Error {
@@ -3935,7 +3935,7 @@ function renderLayout(layout) {
3935
3935
  var state = {
3936
3936
  type: "state",
3937
3937
  detect(text2) {
3938
- return /^\s*state\b/i.test(text2);
3938
+ return /^\s*(?:state\b|stateDiagram(?:-v2)?\b)/i.test(text2);
3939
3939
  },
3940
3940
  parse: parseStateDiagram,
3941
3941
  render(text2, config) {
@@ -5459,6 +5459,113 @@ function parseTag(tag) {
5459
5459
  return { letter: tag.slice(0, idx), number: tag.slice(idx + 1) };
5460
5460
  }
5461
5461
 
5462
+ // src/diagrams/pid/lint.ts
5463
+ function lintPid(text2) {
5464
+ let ast;
5465
+ try {
5466
+ ast = parsePid(text2);
5467
+ } catch {
5468
+ return [];
5469
+ }
5470
+ return lintPidAst(ast);
5471
+ }
5472
+ var SIGNAL_TYPES = /* @__PURE__ */ new Set([
5473
+ "pneumatic",
5474
+ "electric",
5475
+ "software",
5476
+ "capillary",
5477
+ "hydraulic",
5478
+ "mechanical"
5479
+ ]);
5480
+ function idLetters(tag) {
5481
+ const head = tag.split("-")[0] ?? tag;
5482
+ return head.replace(/[^A-Za-z]/g, "").toUpperCase();
5483
+ }
5484
+ function isController(tag) {
5485
+ return idLetters(tag).slice(1).includes("C");
5486
+ }
5487
+ function isTransmitter(tag) {
5488
+ return idLetters(tag).slice(1).includes("T");
5489
+ }
5490
+ function lintPidAst(ast) {
5491
+ const out = [];
5492
+ const instByTag = /* @__PURE__ */ new Map();
5493
+ for (const inst of ast.instruments) instByTag.set(inst.tag, inst);
5494
+ const controlValveIds = new Set(
5495
+ ast.equipment.filter((e) => e.equipType === "valve_control").map((e) => e.id)
5496
+ );
5497
+ const signalAdj = /* @__PURE__ */ new Map();
5498
+ const addEdge = (a, b) => {
5499
+ if (!signalAdj.has(a)) signalAdj.set(a, /* @__PURE__ */ new Set());
5500
+ signalAdj.get(a).add(b);
5501
+ };
5502
+ for (const ln of ast.lines) {
5503
+ if (!SIGNAL_TYPES.has(ln.lineType)) continue;
5504
+ const a = ln.from.id;
5505
+ const b = ln.to.id;
5506
+ if (instByTag.has(a) && instByTag.has(b)) {
5507
+ addEdge(a, b);
5508
+ addEdge(b, a);
5509
+ }
5510
+ }
5511
+ const canReachController = (start) => {
5512
+ const seen = /* @__PURE__ */ new Set([start]);
5513
+ const queue = [start];
5514
+ while (queue.length) {
5515
+ const cur = queue.shift();
5516
+ if (cur !== start && isController(cur)) return true;
5517
+ for (const nxt of signalAdj.get(cur) ?? []) {
5518
+ if (!seen.has(nxt)) {
5519
+ seen.add(nxt);
5520
+ queue.push(nxt);
5521
+ }
5522
+ }
5523
+ }
5524
+ return false;
5525
+ };
5526
+ for (const inst of ast.instruments) {
5527
+ if (!inst.measures || isController(inst.tag)) continue;
5528
+ if (!canReachController(inst.tag)) {
5529
+ out.push({
5530
+ severity: "warning",
5531
+ code: "PID_LOOP_INCOMPLETE",
5532
+ message: `instrument loop ${inst.tag} has no signal path to a controller`,
5533
+ hint: `${inst.tag} measures '${inst.measures}' but no signal line (electric/pneumatic/\u2026) connects it to a controller instrument (e.g. a tag with a 'C' function letter such as FIC). Add a signal line from ${inst.tag} to the controller.`,
5534
+ fatal: false
5535
+ });
5536
+ }
5537
+ }
5538
+ for (const ln of ast.lines) {
5539
+ if (!SIGNAL_TYPES.has(ln.lineType)) continue;
5540
+ const a = ln.from.id;
5541
+ const b = ln.to.id;
5542
+ const aInst = instByTag.get(a);
5543
+ const bInst = instByTag.get(b);
5544
+ const txToCtrl = aInst && isTransmitter(a) && !isController(a) && bInst && isController(b) || bInst && isTransmitter(b) && !isController(b) && aInst && isController(a);
5545
+ if (txToCtrl && ln.lineType !== "electric") {
5546
+ out.push({
5547
+ severity: "warning",
5548
+ code: "PID_SIGNAL_TYPE_MISMATCH",
5549
+ message: `signal line ${a}\u2192${b} is '${ln.lineType}' but a transmitter\u2192controller link should be 'electric' (ISA-5.1 \xA75.2)`,
5550
+ hint: `Set [type: electric] on this line, or rename the devices if the connection is not a transmitter\u2192controller measurement signal.`,
5551
+ fatal: false
5552
+ });
5553
+ continue;
5554
+ }
5555
+ const ctrlToValve = aInst && isController(a) && controlValveIds.has(b) || bInst && isController(b) && controlValveIds.has(a);
5556
+ if (ctrlToValve && ln.lineType !== "pneumatic") {
5557
+ out.push({
5558
+ severity: "warning",
5559
+ code: "PID_SIGNAL_TYPE_MISMATCH",
5560
+ message: `signal line ${a}\u2192${b} is '${ln.lineType}' but a controller\u2192control-valve link should be 'pneumatic' (ISA-5.1 \xA75.2)`,
5561
+ hint: `Set [type: pneumatic] on this line, or use [type: electric] only if the valve has an electric actuator.`,
5562
+ fatal: false
5563
+ });
5564
+ }
5565
+ }
5566
+ return out;
5567
+ }
5568
+
5462
5569
  // src/diagrams/pid/index.ts
5463
5570
  var pid = {
5464
5571
  type: "pid",
@@ -5466,6 +5573,7 @@ var pid = {
5466
5573
  return /^\s*pid\b/i.test(text2);
5467
5574
  },
5468
5575
  parse: parsePid,
5576
+ lint: lintPid,
5469
5577
  render(text2, config) {
5470
5578
  const ast = parsePid(text2);
5471
5579
  return renderPidAST(ast);
@@ -6510,7 +6618,7 @@ function layoutPrisma(ast) {
6510
6618
  lines: incResized.wrappedLines
6511
6619
  });
6512
6620
  cursorY = incY + incResized.height + PRISMA_CONST.OUTER_PAD_BOTTOM;
6513
- const arrow = (x1, y1, x2, y2) => `M ${x1} ${y1} L ${x2} ${y2}`;
6621
+ const arrow2 = (x1, y1, x2, y2) => `M ${x1} ${y1} L ${x2} ${y2}`;
6514
6622
  const mainColRight = mainColLeft + mainW;
6515
6623
  const rightColArrowEnd = rightColLeft - PRISMA_CONST.ARROWHEAD_LEN;
6516
6624
  if (idRemovedSpec) {
@@ -6519,7 +6627,7 @@ function layoutPrisma(ast) {
6519
6627
  kind: "exclusion",
6520
6628
  from: "id-databases",
6521
6629
  to: "id-removed",
6522
- d: arrow(mainColRight, ay, rightColArrowEnd, ay)
6630
+ d: arrow2(mainColRight, ay, rightColArrowEnd, ay)
6523
6631
  });
6524
6632
  }
6525
6633
  if (dual && idRightSpec) {
@@ -6528,7 +6636,7 @@ function layoutPrisma(ast) {
6528
6636
  kind: "merge-trunk",
6529
6637
  from: "id-databases",
6530
6638
  to: "screening",
6531
- d: arrow(mainColCenterX, idRowY + idDbBox.height, mainColCenterX, screeningY - PRISMA_CONST.ARROWHEAD_LEN)
6639
+ d: arrow2(mainColCenterX, idRowY + idDbBox.height, mainColCenterX, screeningY - PRISMA_CONST.ARROWHEAD_LEN)
6532
6640
  });
6533
6641
  edges.push({
6534
6642
  kind: "merge-leg",
@@ -6541,7 +6649,7 @@ function layoutPrisma(ast) {
6541
6649
  kind: "main",
6542
6650
  from: "id-databases",
6543
6651
  to: "screening",
6544
- d: arrow(mainColCenterX, idRowY + idDbBox.height, mainColCenterX, screeningY - PRISMA_CONST.ARROWHEAD_LEN)
6652
+ d: arrow2(mainColCenterX, idRowY + idDbBox.height, mainColCenterX, screeningY - PRISMA_CONST.ARROWHEAD_LEN)
6545
6653
  });
6546
6654
  }
6547
6655
  if (previousBoxBottom >= 0) {
@@ -6549,34 +6657,34 @@ function layoutPrisma(ast) {
6549
6657
  kind: "previous",
6550
6658
  from: "previous",
6551
6659
  to: "id-databases",
6552
- d: arrow(previousBoxCenterX, previousBoxBottom, mainColCenterX, headerRowY - PRISMA_CONST.ARROWHEAD_LEN)
6660
+ d: arrow2(previousBoxCenterX, previousBoxBottom, mainColCenterX, headerRowY - PRISMA_CONST.ARROWHEAD_LEN)
6553
6661
  });
6554
6662
  }
6555
6663
  edges.push({
6556
6664
  kind: "main",
6557
6665
  from: "screening",
6558
6666
  to: "eligibility",
6559
- d: arrow(mainColCenterX, screeningY + screeningResized.height, mainColCenterX, eligY - PRISMA_CONST.ARROWHEAD_LEN)
6667
+ d: arrow2(mainColCenterX, screeningY + screeningResized.height, mainColCenterX, eligY - PRISMA_CONST.ARROWHEAD_LEN)
6560
6668
  });
6561
6669
  edges.push({
6562
6670
  kind: "main",
6563
6671
  from: "eligibility",
6564
6672
  to: "included",
6565
- d: arrow(mainColCenterX, eligY + eligResized.height, mainColCenterX, incY - PRISMA_CONST.ARROWHEAD_LEN)
6673
+ d: arrow2(mainColCenterX, eligY + eligResized.height, mainColCenterX, incY - PRISMA_CONST.ARROWHEAD_LEN)
6566
6674
  });
6567
6675
  const screeningArrowY = screeningY + screeningResized.height / 2;
6568
6676
  edges.push({
6569
6677
  kind: "exclusion",
6570
6678
  from: "screening",
6571
6679
  to: "screening-excluded",
6572
- d: arrow(mainColRight, screeningArrowY, rightColArrowEnd, screeningArrowY)
6680
+ d: arrow2(mainColRight, screeningArrowY, rightColArrowEnd, screeningArrowY)
6573
6681
  });
6574
6682
  const eligArrowY = eligY + eligResized.height / 2;
6575
6683
  edges.push({
6576
6684
  kind: "exclusion",
6577
6685
  from: "eligibility",
6578
6686
  to: "eligibility-excluded",
6579
- d: arrow(mainColRight, eligArrowY, rightColArrowEnd, eligArrowY)
6687
+ d: arrow2(mainColRight, eligArrowY, rightColArrowEnd, eligArrowY)
6580
6688
  });
6581
6689
  let pageHeight = cursorY;
6582
6690
  if (warnings.length > 0) {
@@ -7184,7 +7292,7 @@ function parseHeader(state2) {
7184
7292
  if (!m) break;
7185
7293
  const key = m[1].toLowerCase();
7186
7294
  const valueRaw = m[2].trim();
7187
- const value = stripQuotes(valueRaw);
7295
+ const value = stripQuotes2(valueRaw);
7188
7296
  if (key === "title") state2.ast.title = value;
7189
7297
  else if (key === "system") state2.ast.system = value;
7190
7298
  else if (key === "direction") {
@@ -7202,7 +7310,7 @@ function parseHeader(state2) {
7202
7310
  state2.i++;
7203
7311
  }
7204
7312
  }
7205
- function stripQuotes(s) {
7313
+ function stripQuotes2(s) {
7206
7314
  const t = s.trim();
7207
7315
  if (t.startsWith('"') && t.endsWith('"') && t.length >= 2) return t.slice(1, -1);
7208
7316
  return t;
@@ -7239,7 +7347,7 @@ function parseActorDecl(ln, state2) {
7239
7347
  id = asMatch[1];
7240
7348
  rest = rest.slice(0, asMatch.index).trim();
7241
7349
  }
7242
- const name = stripQuotes(rest);
7350
+ const name = stripQuotes2(rest);
7243
7351
  if (!name) throw new UsecaseParseError(`actor declaration missing name`, ln.line);
7244
7352
  const actorId = id ?? defaultIdFor(name);
7245
7353
  declareId(state2, actorId, "actor", ln.line);
@@ -7275,7 +7383,7 @@ function parseUsecaseDecl(ln, state2) {
7275
7383
  id = asMatch[1];
7276
7384
  rest = rest.slice(0, asMatch.index).trim();
7277
7385
  }
7278
- const name = stripQuotes(rest);
7386
+ const name = stripQuotes2(rest);
7279
7387
  if (!name) throw new UsecaseParseError(`usecase declaration missing name`, ln.line);
7280
7388
  const ucId = id ?? defaultIdFor(name);
7281
7389
  declareId(state2, ucId, "usecase", ln.line);
@@ -8030,13 +8138,13 @@ function layoutUsecase(ast) {
8030
8138
  pb = perimeter(r.target, a.cx, a.cy);
8031
8139
  }
8032
8140
  const dashed = r.kind === "include" || r.kind === "extend";
8033
- let arrowKind = "none";
8034
- if (r.kind === "directed" || r.kind === "include" || r.kind === "extend") arrowKind = "open";
8035
- else if (r.kind === "generalization") arrowKind = "hollow";
8141
+ let arrowKind2 = "none";
8142
+ if (r.kind === "directed" || r.kind === "include" || r.kind === "extend") arrowKind2 = "open";
8143
+ else if (r.kind === "generalization") arrowKind2 = "hollow";
8036
8144
  const edge = {
8037
8145
  relation: r,
8038
8146
  d: `M ${round(pa.x)} ${round(pa.y)} L ${round(pb.x)} ${round(pb.y)}`,
8039
- arrowKind,
8147
+ arrowKind: arrowKind2,
8040
8148
  dashed
8041
8149
  };
8042
8150
  if (r.kind === "include" || r.kind === "extend") {
@@ -8519,7 +8627,7 @@ function preprocess7(src) {
8519
8627
  }
8520
8628
  return out;
8521
8629
  }
8522
- function stripQuotes2(s) {
8630
+ function stripQuotes3(s) {
8523
8631
  const t = s.trim();
8524
8632
  if (t.length >= 2 && (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'"))) {
8525
8633
  return t.slice(1, -1);
@@ -8537,7 +8645,7 @@ function parseHeaderLine(ln, ast) {
8537
8645
  if (!m) return false;
8538
8646
  const key = m[1].toLowerCase();
8539
8647
  const valueRaw = m[2].trim();
8540
- const value = stripQuotes2(valueRaw);
8648
+ const value = stripQuotes3(valueRaw);
8541
8649
  switch (key) {
8542
8650
  case "title":
8543
8651
  ast.title = value;
@@ -8666,23 +8774,23 @@ function parseTaskLine(ln, ast) {
8666
8774
  milestone = true;
8667
8775
  return "";
8668
8776
  }).trim();
8669
- const markers3 = [];
8777
+ const markers5 = [];
8670
8778
  KEY_RE.lastIndex = 0;
8671
8779
  let mm;
8672
8780
  while ((mm = KEY_RE.exec(rest)) !== null) {
8673
- markers3.push({ key: mm[1].toLowerCase(), start: mm.index, valStart: mm.index + mm[0].length });
8781
+ markers5.push({ key: mm[1].toLowerCase(), start: mm.index, valStart: mm.index + mm[0].length });
8674
8782
  }
8675
8783
  const values = {};
8676
- for (let i = 0; i < markers3.length; i++) {
8677
- const cur = markers3[i];
8678
- const nextStart = i + 1 < markers3.length ? markers3[i + 1].start : rest.length;
8784
+ for (let i = 0; i < markers5.length; i++) {
8785
+ const cur = markers5[i];
8786
+ const nextStart = i + 1 < markers5.length ? markers5[i + 1].start : rest.length;
8679
8787
  const val = rest.slice(cur.valStart, nextStart).trim();
8680
8788
  if (cur.key in values) {
8681
8789
  throw new PertParseError(`duplicate '${cur.key}:' on task '${id}'`, ln.line);
8682
8790
  }
8683
8791
  values[cur.key] = val;
8684
8792
  }
8685
- const preamble = (markers3.length ? rest.slice(0, markers3[0].start) : rest).trim();
8793
+ const preamble = (markers5.length ? rest.slice(0, markers5[0].start) : rest).trim();
8686
8794
  if (preamble) {
8687
8795
  throw new PertParseError(`unexpected text '${preamble}' in task '${id}'`, ln.line);
8688
8796
  }
@@ -8710,7 +8818,7 @@ function parseTaskLine(ln, ast) {
8710
8818
  }
8711
8819
  const tags = values.tags ? values.tags.split(",").map((t) => t.trim()).filter(Boolean) : [];
8712
8820
  const className = values.class ? values.class.trim() : void 0;
8713
- const lane = values.lane ? stripQuotes2(values.lane) : void 0;
8821
+ const lane = values.lane ? stripQuotes3(values.lane) : void 0;
8714
8822
  const task = {
8715
8823
  id,
8716
8824
  label,
@@ -10085,186 +10193,3832 @@ function renderLanes(lanes, width) {
10085
10193
  text({ class: "sx-pert-lane-label", x: 16, y: ln.y + ln.height / 2 + 4, "text-anchor": "start" }, ln.name)
10086
10194
  );
10087
10195
  }
10088
- return group({ class: "sx-pert-lane" }, parts);
10196
+ return group({ class: "sx-pert-lane" }, parts);
10197
+ }
10198
+ function renderSentinel(s) {
10199
+ return group({ class: "sx-pert-sentinel", "data-id": s.id }, [
10200
+ circle({ cx: s.cx, cy: s.cy, r: s.r }),
10201
+ text({ x: s.cx, y: s.cy + 3.5, "text-anchor": "middle" }, s.label)
10202
+ ]);
10203
+ }
10204
+ function renderAxis2(axis, gridTop) {
10205
+ const parts = [];
10206
+ for (const tk of axis.ticks) {
10207
+ if (tk.major) {
10208
+ parts.push(line({ class: "sx-pert-grid", x1: tk.pos, y1: gridTop, x2: tk.pos, y2: axis.baseline }));
10209
+ }
10210
+ }
10211
+ parts.push(line({ class: "baseline", x1: axis.start, y1: axis.baseline, x2: axis.end, y2: axis.baseline }));
10212
+ for (const tk of axis.ticks) {
10213
+ const len = tk.major ? 7 : 4;
10214
+ parts.push(line({ x1: tk.pos, y1: axis.baseline, x2: tk.pos, y2: axis.baseline + len }));
10215
+ if (tk.major) {
10216
+ parts.push(
10217
+ text({ x: tk.pos, y: axis.baseline + 18, "text-anchor": "middle" }, fmtVal(tk.value))
10218
+ );
10219
+ }
10220
+ }
10221
+ return group({ class: "sx-pert-axis" }, parts);
10222
+ }
10223
+ function renderAoa(aoa) {
10224
+ const arcEls = [];
10225
+ const labelEls = [];
10226
+ for (const a of aoa.arcs) {
10227
+ const cls = `sx-pert-aoa-arc${a.dummy ? " dummy" : ""}${a.critical ? " critical" : ""}`;
10228
+ const marker = a.critical ? "url(#sx-pert-arrow-crit)" : "url(#sx-pert-arrow)";
10229
+ arcEls.push(
10230
+ path({
10231
+ class: cls,
10232
+ d: a.d,
10233
+ "marker-end": marker,
10234
+ "data-from": a.from,
10235
+ "data-to": a.to,
10236
+ "data-task": a.taskId ?? "",
10237
+ "data-dummy": String(a.dummy),
10238
+ "data-critical": String(a.critical)
10239
+ })
10240
+ );
10241
+ if (!a.dummy && a.label) {
10242
+ const critCls = a.critical ? " critical" : "";
10243
+ const durStr = fmtVal(a.duration ?? 0);
10244
+ const w = Math.max(a.label.length * 6.2, durStr.length * 6) + 8;
10245
+ labelEls.push(
10246
+ rect({ class: "sx-pert-edge-halo", x: a.labelX - w / 2, y: a.labelY - 16, width: w, height: 31, rx: 3, ry: 3 })
10247
+ );
10248
+ labelEls.push(
10249
+ text({ class: `sx-pert-aoa-name${critCls}`, x: a.labelX, y: a.labelY - 4, "text-anchor": "middle" }, a.label)
10250
+ );
10251
+ labelEls.push(
10252
+ text({ class: `sx-pert-aoa-dur${critCls}`, x: a.labelX, y: a.labelY + 12, "text-anchor": "middle" }, durStr)
10253
+ );
10254
+ }
10255
+ }
10256
+ const eventEls = [];
10257
+ for (const e of aoa.events) {
10258
+ eventEls.push(
10259
+ group({ class: `sx-pert-aoa-event${e.critical ? " critical" : ""}`, "data-event": e.id }, [
10260
+ circle({ cx: e.x, cy: e.y, r: e.r }),
10261
+ text({ x: e.x, y: e.y + 4.5, "text-anchor": "middle" }, String(e.id))
10262
+ ])
10263
+ );
10264
+ }
10265
+ return group({ class: "sx-pert-aoa" }, [
10266
+ group({ class: "sx-pert-aoa-arcs" }, arcEls),
10267
+ group({ class: "sx-pert-aoa-labels" }, labelEls),
10268
+ group({ class: "sx-pert-aoa-events" }, eventEls)
10269
+ ]);
10270
+ }
10271
+ function summaryText(summary) {
10272
+ const u = unitWord(summary.unit);
10273
+ const dur = `${fmtVal(summary.projectDuration)}${u ? " " + u : ""}`;
10274
+ const sigma = summary.projectStdDev !== void 0 ? ` \xB7 \u03C3 \u2248 ${fmtVal(summary.projectStdDev)}` : "";
10275
+ const plain = `Project duration ${dur} \xB7 ${summary.taskCount} tasks \xB7 ${summary.depCount} dependencies \xB7 ${summary.criticalCount} critical${sigma}`;
10276
+ const critPath = summary.criticalPath.map((id) => id).join(" \u2192 ");
10277
+ return { plain, critPath };
10278
+ }
10279
+ function renderPertLayout(layout, config) {
10280
+ const t = resolveBaseTheme(config?.theme ?? "default");
10281
+ const children = [];
10282
+ const cp = layout.summary.criticalPath;
10283
+ children.push(title(`PERT network${layout.title ? " \u2014 " + layout.title : ""}`));
10284
+ children.push(
10285
+ desc(
10286
+ `${layout.summary.taskCount} activities, project duration ${fmtVal(layout.summary.projectDuration)} ${unitWord(layout.unit)}` + (cp.length ? `, critical path ${cp.join(" \u2192 ")}` : "") + "."
10287
+ )
10288
+ );
10289
+ children.push(el("style", {}, buildCss4(t)));
10290
+ children.push(markers2());
10291
+ if (layout.title) {
10292
+ children.push(
10293
+ text({ x: layout.width / 2, y: 26, class: "sx-pert-title", "text-anchor": "middle" }, layout.title)
10294
+ );
10295
+ }
10296
+ if (layout.aoa) {
10297
+ children.push(renderAoa(layout.aoa));
10298
+ } else {
10299
+ if (layout.lanes && layout.lanes.length) {
10300
+ children.push(renderLanes(layout.lanes, layout.width));
10301
+ }
10302
+ if (layout.axis) {
10303
+ let minBoxY = Infinity;
10304
+ for (const b of layout.boxes) minBoxY = Math.min(minBoxY, b.y);
10305
+ children.push(renderAxis2(layout.axis, isFinite(minBoxY) ? minBoxY : layout.axis.baseline - 20));
10306
+ }
10307
+ const edgeEls = [];
10308
+ for (const e of layout.edges) edgeEls.push(renderEdge3(e));
10309
+ children.push(group({ class: "sx-pert-edges" }, edgeEls));
10310
+ if (layout.sentinels.length) {
10311
+ children.push(group({ class: "sx-pert-sentinels" }, layout.sentinels.map(renderSentinel)));
10312
+ }
10313
+ const boxEls = [];
10314
+ for (const b of layout.boxes) boxEls.push(renderBox2(b, layout.mode));
10315
+ children.push(group({ class: "sx-pert-tasks" }, boxEls));
10316
+ const labelEls = [];
10317
+ for (const e of layout.edges) {
10318
+ const lbl = renderEdgeLabel2(e);
10319
+ if (lbl) labelEls.push(lbl);
10320
+ }
10321
+ children.push(group({ class: "sx-pert-labels" }, labelEls));
10322
+ }
10323
+ const { plain, critPath } = summaryText(layout.summary);
10324
+ const footerY = layout.height - 12;
10325
+ const footerParts = [
10326
+ text({ class: "sx-pert-summary", x: PERT_PAD, y: footerY, "text-anchor": "start" }, plain)
10327
+ ];
10328
+ if (critPath) {
10329
+ footerParts.push(
10330
+ text(
10331
+ { class: "sx-pert-summary crit", x: layout.width - PERT_PAD, y: footerY, "text-anchor": "end" },
10332
+ `Critical path: ${critPath}`
10333
+ )
10334
+ );
10335
+ }
10336
+ children.push(group({ class: "sx-pert-footer" }, footerParts));
10337
+ return svgRoot(
10338
+ {
10339
+ class: "sx-pert",
10340
+ role: "img",
10341
+ "aria-label": escapeXml(layout.title ?? "PERT network diagram"),
10342
+ width: layout.width,
10343
+ height: layout.height,
10344
+ viewBox: `0 0 ${layout.width} ${layout.height}`,
10345
+ "data-diagram-type": "pert"
10346
+ },
10347
+ children
10348
+ );
10349
+ }
10350
+ var PERT_PAD = 24;
10351
+ function renderPert(textOrAst, config) {
10352
+ const ast = typeof textOrAst === "string" ? parsePert(textOrAst) : textOrAst;
10353
+ const schedule = schedulePert(ast);
10354
+ const layout = layoutPert(ast, schedule);
10355
+ return renderPertLayout(layout, config);
10356
+ }
10357
+
10358
+ // src/diagrams/pert/index.ts
10359
+ var pert = {
10360
+ type: "pert",
10361
+ detect(text2) {
10362
+ for (const raw of text2.split(/\r?\n/)) {
10363
+ const t = raw.trim();
10364
+ if (!t) continue;
10365
+ if (t.startsWith("#") || t.startsWith("//")) continue;
10366
+ return /^pert\b/i.test(t);
10367
+ }
10368
+ return false;
10369
+ },
10370
+ parse: parsePert,
10371
+ render(text2, config) {
10372
+ return renderPert(text2, config);
10373
+ }
10374
+ };
10375
+
10376
+ // src/diagrams/sequence/parser.ts
10377
+ var SequenceParseError = class extends Error {
10378
+ line;
10379
+ constructor(message, line2) {
10380
+ super(line2 ? `${message} (line ${line2})` : message);
10381
+ this.name = "SequenceParseError";
10382
+ this.line = line2;
10383
+ }
10384
+ };
10385
+ var KINDS = /* @__PURE__ */ new Set([
10386
+ "participant",
10387
+ "actor",
10388
+ "boundary",
10389
+ "control",
10390
+ "entity",
10391
+ "database",
10392
+ "collections",
10393
+ "queue"
10394
+ ]);
10395
+ var SIMPLE_FRAG = /* @__PURE__ */ new Set([
10396
+ "opt",
10397
+ "loop",
10398
+ "break",
10399
+ "critical",
10400
+ "neg",
10401
+ "ignore",
10402
+ "consider",
10403
+ "assert"
10404
+ ]);
10405
+ var MULTI_FRAG = /* @__PURE__ */ new Set(["par", "seq", "strict"]);
10406
+ var MSGSET_FRAG = /* @__PURE__ */ new Set(["ignore", "consider"]);
10407
+ var ALL_FRAG = /* @__PURE__ */ new Set([
10408
+ "alt",
10409
+ ...SIMPLE_FRAG,
10410
+ ...MULTI_FRAG
10411
+ ]);
10412
+ var ARROW_RE = /(-->|->>|o->|-x|->)/;
10413
+ function arrowKind(token) {
10414
+ switch (token) {
10415
+ case "-->":
10416
+ return "reply";
10417
+ case "->>":
10418
+ return "async";
10419
+ case "o->":
10420
+ return "found";
10421
+ case "-x":
10422
+ return "lost";
10423
+ default:
10424
+ return "sync";
10425
+ }
10426
+ }
10427
+ function firstWord(s) {
10428
+ const m = /^(\S+)/.exec(s);
10429
+ return m ? m[1] : "";
10430
+ }
10431
+ function extractGuard(rest) {
10432
+ const t = rest.trim();
10433
+ if (!t) return void 0;
10434
+ if (t.startsWith("[") && t.endsWith("]")) return t.slice(1, -1).trim();
10435
+ if (t.startsWith("(") && t.endsWith(")")) return t.slice(1, -1).trim();
10436
+ return t;
10437
+ }
10438
+ var SequenceParser = class {
10439
+ lines;
10440
+ i = 0;
10441
+ order = [];
10442
+ byId = /* @__PURE__ */ new Map();
10443
+ warnings = [];
10444
+ constructor(source) {
10445
+ this.lines = source.split(/\r?\n/).map((raw, idx) => ({
10446
+ text: raw.trim(),
10447
+ n: idx + 1
10448
+ }));
10449
+ }
10450
+ parse() {
10451
+ const header = this.consumeHeader();
10452
+ const ast = {
10453
+ type: "sequence",
10454
+ participants: [],
10455
+ statements: [],
10456
+ warnings: this.warnings
10457
+ };
10458
+ if (header.title) ast.title = header.title;
10459
+ const statements = this.parseBlock();
10460
+ const leftover = this.peek();
10461
+ if (leftover) {
10462
+ const fw = firstWord(leftover.text).toLowerCase();
10463
+ throw new SequenceParseError(
10464
+ `'${fw}' without a matching combined fragment`,
10465
+ leftover.n
10466
+ );
10467
+ }
10468
+ if (this.autonumberSpec) ast.autonumber = this.autonumberSpec;
10469
+ ast.statements = statements;
10470
+ ast.participants = this.order.map((id) => this.byId.get(id));
10471
+ return ast;
10472
+ }
10473
+ autonumberSpec;
10474
+ // ── line cursor ──────────────────────────────────────────────
10475
+ peek() {
10476
+ while (this.i < this.lines.length) {
10477
+ const ln = this.lines[this.i];
10478
+ if (ln.text === "" || ln.text.startsWith("#") || ln.text.startsWith("//")) {
10479
+ this.i++;
10480
+ continue;
10481
+ }
10482
+ return ln;
10483
+ }
10484
+ return null;
10485
+ }
10486
+ next() {
10487
+ const ln = this.peek();
10488
+ if (ln) this.i++;
10489
+ return ln;
10490
+ }
10491
+ // ── header ───────────────────────────────────────────────────
10492
+ consumeHeader() {
10493
+ const ln = this.next();
10494
+ if (!ln || !/^sequence\b/i.test(ln.text)) {
10495
+ throw new SequenceParseError(
10496
+ "A sequence diagram must start with the keyword 'sequence'",
10497
+ ln?.n
10498
+ );
10499
+ }
10500
+ const title2 = matchQuotedTitle(ln.text);
10501
+ return title2 ? { title: title2 } : {};
10502
+ }
10503
+ // ── participants ─────────────────────────────────────────────
10504
+ ensure(id, kind = "participant", line2) {
10505
+ let p = this.byId.get(id);
10506
+ if (!p) {
10507
+ p = { id, name: id, kind, line: line2 };
10508
+ this.byId.set(id, p);
10509
+ this.order.push(id);
10510
+ }
10511
+ return p;
10512
+ }
10513
+ declare(line2) {
10514
+ const fw = firstWord(line2.text);
10515
+ const kind = fw.toLowerCase();
10516
+ let rest = line2.text.slice(fw.length).trim();
10517
+ let stereotype;
10518
+ const stereoMatch = /«([^»]*)»|<<([^>]*)>>/.exec(rest);
10519
+ if (stereoMatch) {
10520
+ stereotype = (stereoMatch[1] ?? stereoMatch[2] ?? "").trim();
10521
+ rest = (rest.slice(0, stereoMatch.index) + rest.slice(stereoMatch.index + stereoMatch[0].length)).trim();
10522
+ }
10523
+ let id;
10524
+ let name;
10525
+ const asMatch = /\sas\s/i.exec(rest);
10526
+ if (asMatch) {
10527
+ id = stripQuotes(rest.slice(0, asMatch.index).trim());
10528
+ name = stripQuotes(rest.slice(asMatch.index + asMatch[0].length).trim());
10529
+ } else {
10530
+ id = stripQuotes(rest);
10531
+ name = id;
10532
+ }
10533
+ if (!id) {
10534
+ throw new SequenceParseError(`'${fw}' declaration needs a name`, line2.n);
10535
+ }
10536
+ const existing = this.byId.get(id);
10537
+ if (existing) {
10538
+ existing.kind = kind;
10539
+ existing.name = name;
10540
+ if (stereotype) existing.stereotype = stereotype;
10541
+ } else {
10542
+ const p = { id, name, kind, line: line2.n };
10543
+ if (stereotype) p.stereotype = stereotype;
10544
+ this.byId.set(id, p);
10545
+ this.order.push(id);
10546
+ }
10547
+ }
10548
+ // ── block / statement dispatch ───────────────────────────────
10549
+ /** Parse statements until a terminator (`end`/`else`/`and`) or EOF. Terminators are left unconsumed. */
10550
+ parseBlock() {
10551
+ const out = [];
10552
+ for (; ; ) {
10553
+ const ln = this.peek();
10554
+ if (!ln) break;
10555
+ const fw = firstWord(ln.text).toLowerCase();
10556
+ if (fw === "end" || fw === "else" || fw === "and") break;
10557
+ if (KINDS.has(fw)) {
10558
+ this.i++;
10559
+ this.declare(ln);
10560
+ continue;
10561
+ }
10562
+ if (ALL_FRAG.has(fw)) {
10563
+ out.push(this.parseFragment(ln));
10564
+ continue;
10565
+ }
10566
+ this.i++;
10567
+ const stmt = this.parseSimpleStatement(ln);
10568
+ if (stmt) out.push(stmt);
10569
+ }
10570
+ return out;
10571
+ }
10572
+ parseFragment(open) {
10573
+ const fw = firstWord(open.text).toLowerCase();
10574
+ this.i++;
10575
+ let rest = open.text.slice(fw.length);
10576
+ let messageSet;
10577
+ if (MSGSET_FRAG.has(fw)) {
10578
+ const bm = /\{([^}]*)\}/.exec(rest);
10579
+ if (bm) {
10580
+ messageSet = bm[1].split(",").map((s) => s.trim()).filter(Boolean);
10581
+ rest = rest.slice(0, bm.index) + rest.slice(bm.index + bm[0].length);
10582
+ }
10583
+ }
10584
+ const firstGuard = extractGuard(rest);
10585
+ const operands = [
10586
+ { guard: firstGuard, statements: this.parseBlock() }
10587
+ ];
10588
+ for (; ; ) {
10589
+ const ln = this.peek();
10590
+ if (!ln) {
10591
+ throw new SequenceParseError(`Unterminated '${fw}' fragment`, open.n);
10592
+ }
10593
+ const term = firstWord(ln.text).toLowerCase();
10594
+ if (term === "end") {
10595
+ this.i++;
10596
+ break;
10597
+ }
10598
+ if (term === "else") {
10599
+ if (fw !== "alt") {
10600
+ throw new SequenceParseError(
10601
+ `'else' is only valid inside an 'alt' fragment, not '${fw}'`,
10602
+ ln.n
10603
+ );
10604
+ }
10605
+ this.i++;
10606
+ operands.push({
10607
+ guard: extractGuard(ln.text.slice(4)),
10608
+ statements: this.parseBlock()
10609
+ });
10610
+ continue;
10611
+ }
10612
+ if (term === "and") {
10613
+ if (!MULTI_FRAG.has(fw)) {
10614
+ throw new SequenceParseError(
10615
+ `'and' is only valid inside par/seq/strict, not '${fw}'`,
10616
+ ln.n
10617
+ );
10618
+ }
10619
+ this.i++;
10620
+ const label = ln.text.slice(3).trim();
10621
+ operands.push({
10622
+ guard: label ? extractGuard(label) : void 0,
10623
+ statements: this.parseBlock()
10624
+ });
10625
+ continue;
10626
+ }
10627
+ throw new SequenceParseError(`Unexpected '${term}' in '${fw}'`, ln.n);
10628
+ }
10629
+ const frag = { kind: "fragment", op: fw, operands, line: open.n };
10630
+ if (messageSet && messageSet.length) frag.messageSet = messageSet;
10631
+ return frag;
10632
+ }
10633
+ /** Non-fragment, non-participant statements. */
10634
+ parseSimpleStatement(ln) {
10635
+ const text2 = ln.text;
10636
+ const fw = firstWord(text2).toLowerCase();
10637
+ if (fw === "activate" || fw === "deactivate") {
10638
+ const m = /^(?:de)?activate\s+(\S+)$/i.exec(text2);
10639
+ if (!m) throw new SequenceParseError(`Malformed ${fw} statement`, ln.n);
10640
+ this.ensure(m[1], "participant", ln.n);
10641
+ return { kind: fw, id: m[1], line: ln.n };
10642
+ }
10643
+ if (fw === "note") {
10644
+ return this.parseNote(ln);
10645
+ }
10646
+ if (fw === "ref") {
10647
+ const m = /^ref\s+over\s+(.+?)\s*:\s*(.*)$/i.exec(text2);
10648
+ if (!m) throw new SequenceParseError("Malformed ref (expected: ref over A, B : text)", ln.n);
10649
+ const ids = m[1].split(",").map((s) => stripQuotes(s.trim())).filter(Boolean);
10650
+ ids.forEach((id) => this.ensure(id, "participant", ln.n));
10651
+ return { kind: "ref", ids, text: m[2].trim(), line: ln.n };
10652
+ }
10653
+ if (text2.startsWith("==")) {
10654
+ const m = /^==\s*(.*?)\s*==\s*$/.exec(text2);
10655
+ return { kind: "divider", text: m ? m[1].trim() : "", line: ln.n };
10656
+ }
10657
+ if (fw === "state") {
10658
+ const m = /^state\s+(\S+)\s*:\s*(.*)$/i.exec(text2);
10659
+ if (!m) throw new SequenceParseError("Malformed state invariant (expected: state A : text)", ln.n);
10660
+ this.ensure(m[1], "participant", ln.n);
10661
+ return { kind: "invariant", id: m[1], text: m[2].trim(), line: ln.n };
10662
+ }
10663
+ if (fw === "destroy") {
10664
+ const m = /^destroy\s+(\S+)$/i.exec(text2);
10665
+ if (!m) throw new SequenceParseError("Malformed destroy statement", ln.n);
10666
+ this.ensure(m[1], "participant", ln.n);
10667
+ return { kind: "destroy", id: m[1], line: ln.n };
10668
+ }
10669
+ if (fw === "autonumber") {
10670
+ const m = /^autonumber(?:\s+(\d+))?(?:\s+(\d+))?$/i.exec(text2);
10671
+ const start = m && m[1] ? parseInt(m[1], 10) : 1;
10672
+ const step = m && m[2] ? parseInt(m[2], 10) : 1;
10673
+ this.autonumberSpec = { start, step };
10674
+ return null;
10675
+ }
10676
+ return this.parseMessage(ln);
10677
+ }
10678
+ parseNote(ln) {
10679
+ const m = /^note\s+(over|left of|right of)\s+(.+?)\s*:\s*(.*)$/i.exec(ln.text);
10680
+ if (!m) {
10681
+ throw new SequenceParseError(
10682
+ "Malformed note (expected: note over|left of|right of A[, B] : text)",
10683
+ ln.n
10684
+ );
10685
+ }
10686
+ const placementRaw = m[1].toLowerCase();
10687
+ const placement = placementRaw === "over" ? "over" : placementRaw.startsWith("left") ? "left" : "right";
10688
+ const ids = m[2].split(",").map((s) => stripQuotes(s.trim())).filter(Boolean);
10689
+ ids.forEach((id) => this.ensure(id, "participant", ln.n));
10690
+ return { kind: "note", placement, ids, text: m[3].trim(), line: ln.n };
10691
+ }
10692
+ parseMessage(ln) {
10693
+ const text2 = ln.text;
10694
+ const arrowMatch = ARROW_RE.exec(text2);
10695
+ if (!arrowMatch) {
10696
+ throw new SequenceParseError(`Cannot parse statement: "${text2}"`, ln.n);
10697
+ }
10698
+ const token = arrowMatch[1];
10699
+ const at = arrowMatch.index;
10700
+ const leftRaw = text2.slice(0, at);
10701
+ let rightRaw = text2.slice(at + token.length);
10702
+ let label;
10703
+ const colon = rightRaw.indexOf(":");
10704
+ if (colon >= 0) {
10705
+ label = rightRaw.slice(colon + 1).trim();
10706
+ rightRaw = rightRaw.slice(0, colon);
10707
+ }
10708
+ let activateTarget = false;
10709
+ let deactivateSource = false;
10710
+ let left = leftRaw.trim();
10711
+ if (left.endsWith("+")) {
10712
+ activateTarget = true;
10713
+ left = left.slice(0, -1).trim();
10714
+ } else if (left.endsWith("-")) {
10715
+ deactivateSource = true;
10716
+ left = left.slice(0, -1).trim();
10717
+ }
10718
+ let right = rightRaw.trim();
10719
+ if (right.startsWith("+")) {
10720
+ activateTarget = true;
10721
+ right = right.slice(1).trim();
10722
+ } else if (right.startsWith("-")) {
10723
+ deactivateSource = true;
10724
+ right = right.slice(1).trim();
10725
+ }
10726
+ const arrow2 = arrowKind(token);
10727
+ let create = false;
10728
+ if (right.startsWith("*")) {
10729
+ create = true;
10730
+ right = right.slice(1).trim();
10731
+ }
10732
+ const from = arrow2 === "found" ? "" : stripQuotes(left);
10733
+ const to = arrow2 === "lost" ? "" : stripQuotes(right);
10734
+ if (from) this.ensure(from, "participant", ln.n);
10735
+ if (to) {
10736
+ const p = this.ensure(to, "participant", ln.n);
10737
+ if (create) p.createdInline = true;
10738
+ }
10739
+ const msg = {
10740
+ kind: "message",
10741
+ from,
10742
+ to,
10743
+ arrow: arrow2,
10744
+ line: ln.n
10745
+ };
10746
+ if (label) msg.label = label;
10747
+ if (activateTarget) msg.activateTarget = true;
10748
+ if (deactivateSource) msg.deactivateSource = true;
10749
+ if (create) msg.create = true;
10750
+ return msg;
10751
+ }
10752
+ };
10753
+ function parseSequence(text2) {
10754
+ return new SequenceParser(text2).parse();
10755
+ }
10756
+
10757
+ // src/diagrams/sequence/layout.ts
10758
+ var SEQ_CONST = {
10759
+ HEAD_W_MIN: 90,
10760
+ HEAD_H: 38,
10761
+ HEAD_PAD_X: 14,
10762
+ LIFELINE_GAP: 140,
10763
+ LEFT_MARGIN: 24,
10764
+ RIGHT_PAD: 28,
10765
+ TOP_PAD: 16,
10766
+ HEAD_TO_FIRST: 30,
10767
+ EVENT_GAP: 38,
10768
+ BOTTOM_PAD: 30,
10769
+ SELF_GAP: 30,
10770
+ SELF_LOOP_W: 48,
10771
+ ACT_W: 10,
10772
+ ACT_NEST_DX: 6,
10773
+ ACT_STEP: 30,
10774
+ FRAG_PAD_X: 10,
10775
+ FRAG_LABEL_H: 18,
10776
+ FRAG_PAD_TOP: 16,
10777
+ FRAG_PAD_BOTTOM: 12,
10778
+ FRAG_GAP_BEFORE: 12,
10779
+ FRAG_OPERAND_GAP: 18,
10780
+ FRAG_NEST_INSET: 8,
10781
+ REF_H: 40,
10782
+ REF_GAP_BEFORE: 12,
10783
+ NOTE_GAP_BEFORE: 10,
10784
+ NOTE_PAD: 8,
10785
+ NOTE_MIN_W: 70,
10786
+ NOTE_LINE_H: 15,
10787
+ NOTE_SIDE_GAP: 16,
10788
+ DIV_H: 26,
10789
+ DIV_GAP_BEFORE: 8,
10790
+ INV_GAP_BEFORE: 10,
10791
+ INV_PAD: 10,
10792
+ INV_H: 24,
10793
+ LABEL_GAP_PAD: 26,
10794
+ LOST_LEN: 72
10795
+ };
10796
+ var LABEL_SIZE = 11;
10797
+ var HEAD_TEXT_SIZE = 12.5;
10798
+ function textWidth(s, size) {
10799
+ let w = 0;
10800
+ for (const ch of s) {
10801
+ w += /[⺀-￿]/.test(ch) ? size : size * 0.55;
10802
+ }
10803
+ return w;
10804
+ }
10805
+ var SequenceLayout = class {
10806
+ ast;
10807
+ lifelines = [];
10808
+ colOf = /* @__PURE__ */ new Map();
10809
+ llById = /* @__PURE__ */ new Map();
10810
+ x = [];
10811
+ headW = [];
10812
+ y = 0;
10813
+ open = /* @__PURE__ */ new Map();
10814
+ colAccStack = [];
10815
+ messages = [];
10816
+ activations = [];
10817
+ fragments = [];
10818
+ refs = [];
10819
+ notes = [];
10820
+ dividers = [];
10821
+ invariants = [];
10822
+ destroys = [];
10823
+ warnings = [];
10824
+ autoCounter = null;
10825
+ contentRight = 0;
10826
+ constructor(ast) {
10827
+ this.ast = ast;
10828
+ }
10829
+ run() {
10830
+ this.buildColumns();
10831
+ if (this.ast.autonumber) this.autoCounter = this.ast.autonumber.start;
10832
+ const headBottom = SEQ_CONST.TOP_PAD + SEQ_CONST.HEAD_H;
10833
+ this.y = headBottom + SEQ_CONST.HEAD_TO_FIRST - SEQ_CONST.EVENT_GAP;
10834
+ this.walk(this.ast.statements, 0);
10835
+ const bottomY = this.y + SEQ_CONST.EVENT_GAP;
10836
+ for (const [id, stack] of this.open) {
10837
+ while (stack.length) {
10838
+ const b = stack.pop();
10839
+ this.activations.push({ id, x: b.x, yTop: b.yTop, yBottom: bottomY, level: b.level });
10840
+ }
10841
+ }
10842
+ const bodyBottom = bottomY + SEQ_CONST.BOTTOM_PAD;
10843
+ for (const ll of this.lifelines) {
10844
+ if (!ll.destroyed) ll.axisBottom = bodyBottom;
10845
+ }
10846
+ const lastCenter = this.x[this.x.length - 1] ?? SEQ_CONST.LEFT_MARGIN;
10847
+ const lastHalf = (this.headW[this.headW.length - 1] ?? SEQ_CONST.HEAD_W_MIN) / 2;
10848
+ this.contentRight = Math.max(this.contentRight, lastCenter + lastHalf);
10849
+ const width = Math.round(this.contentRight + SEQ_CONST.RIGHT_PAD);
10850
+ const height = Math.round(bodyBottom);
10851
+ for (const d of this.dividers) d.width = width;
10852
+ const result = {
10853
+ width,
10854
+ height,
10855
+ lifelines: this.lifelines,
10856
+ messages: this.messages,
10857
+ activations: this.activations,
10858
+ fragments: this.fragments,
10859
+ refs: this.refs,
10860
+ notes: this.notes,
10861
+ dividers: this.dividers,
10862
+ invariants: this.invariants,
10863
+ destroys: this.destroys,
10864
+ warnings: this.warnings.concat(this.ast.warnings),
10865
+ ast: this.ast
10866
+ };
10867
+ if (this.ast.title) result.title = this.ast.title;
10868
+ return result;
10869
+ }
10870
+ // ── horizontal pass ──────────────────────────────────────────
10871
+ buildColumns() {
10872
+ const ps = this.ast.participants;
10873
+ this.headW = ps.map(
10874
+ (p) => Math.max(SEQ_CONST.HEAD_W_MIN, Math.ceil(textWidth(p.name, HEAD_TEXT_SIZE)) + SEQ_CONST.HEAD_PAD_X * 2)
10875
+ );
10876
+ ps.forEach((p, i) => this.colOf.set(p.id, i));
10877
+ const n = ps.length;
10878
+ const gaps = new Array(Math.max(0, n - 1)).fill(SEQ_CONST.LIFELINE_GAP);
10879
+ for (let k = 0; k < gaps.length; k++) {
10880
+ gaps[k] = Math.max(gaps[k], this.headW[k] / 2 + this.headW[k + 1] / 2 + 28);
10881
+ }
10882
+ const msgs = collectMessages(this.ast.statements);
10883
+ for (const m of msgs) {
10884
+ const label = m.label ?? "";
10885
+ const w = textWidth(label, LABEL_SIZE) + SEQ_CONST.LABEL_GAP_PAD;
10886
+ const cf = this.colOf.get(m.from);
10887
+ const ct = this.colOf.get(m.to);
10888
+ if (cf === void 0 && ct === void 0) continue;
10889
+ if (cf === void 0 || ct === void 0) continue;
10890
+ if (cf === ct) {
10891
+ if (cf < gaps.length) gaps[cf] = Math.max(gaps[cf], SEQ_CONST.SELF_LOOP_W + w);
10892
+ continue;
10893
+ }
10894
+ const i = Math.min(cf, ct);
10895
+ const j = Math.max(cf, ct);
10896
+ if (j === i + 1) {
10897
+ gaps[i] = Math.max(gaps[i], w);
10898
+ } else {
10899
+ let span = 0;
10900
+ for (let k = i; k < j; k++) span += gaps[k];
10901
+ if (span < w) {
10902
+ const add = (w - span) / (j - i);
10903
+ for (let k = i; k < j; k++) gaps[k] += add;
10904
+ }
10905
+ }
10906
+ }
10907
+ this.x = new Array(n);
10908
+ let cx = SEQ_CONST.LEFT_MARGIN + (this.headW[0] ?? SEQ_CONST.HEAD_W_MIN) / 2;
10909
+ for (let i = 0; i < n; i++) {
10910
+ this.x[i] = cx;
10911
+ if (i < gaps.length) cx += gaps[i];
10912
+ }
10913
+ ps.forEach((p, i) => {
10914
+ const created = p.createdInline === true;
10915
+ const headY = created ? -1 : SEQ_CONST.TOP_PAD;
10916
+ const ll = {
10917
+ participant: p,
10918
+ index: i,
10919
+ x: this.x[i],
10920
+ headX: this.x[i] - this.headW[i] / 2,
10921
+ headY,
10922
+ headW: this.headW[i],
10923
+ headH: SEQ_CONST.HEAD_H,
10924
+ axisTop: created ? -1 : SEQ_CONST.TOP_PAD + SEQ_CONST.HEAD_H,
10925
+ axisBottom: 0,
10926
+ destroyed: false
10927
+ };
10928
+ this.lifelines.push(ll);
10929
+ this.llById.set(p.id, ll);
10930
+ });
10931
+ }
10932
+ // ── column accounting (for fragment extents) ─────────────────
10933
+ touch(col) {
10934
+ if (col === void 0) return;
10935
+ for (const acc of this.colAccStack) {
10936
+ if (col < acc.min) acc.min = col;
10937
+ if (col > acc.max) acc.max = col;
10938
+ }
10939
+ }
10940
+ noteRight(x, w) {
10941
+ this.contentRight = Math.max(this.contentRight, x + w);
10942
+ }
10943
+ // ── activation bars ──────────────────────────────────────────
10944
+ openBar(id) {
10945
+ const col = this.colOf.get(id);
10946
+ if (col === void 0) return;
10947
+ const stack = this.open.get(id) ?? [];
10948
+ const level = stack.length;
10949
+ const x = this.x[col] - SEQ_CONST.ACT_W / 2 + level * SEQ_CONST.ACT_NEST_DX;
10950
+ stack.push({ x, yTop: this.y, level });
10951
+ this.open.set(id, stack);
10952
+ }
10953
+ closeBar(id) {
10954
+ const stack = this.open.get(id);
10955
+ if (!stack || stack.length === 0) {
10956
+ this.warnings.push(`deactivate '${id}' with no matching activation`);
10957
+ return;
10958
+ }
10959
+ const b = stack.pop();
10960
+ this.activations.push({ id, x: b.x, yTop: b.yTop, yBottom: this.y, level: b.level });
10961
+ }
10962
+ faceEdge(id, col, towardRight, extraLevel = 0) {
10963
+ const stack = this.open.get(id) ?? [];
10964
+ const levels = stack.length + extraLevel;
10965
+ if (levels <= 0) return this.x[col];
10966
+ if (!towardRight) return this.x[col] - SEQ_CONST.ACT_W / 2;
10967
+ return this.x[col] + SEQ_CONST.ACT_W / 2 + (levels - 1) * SEQ_CONST.ACT_NEST_DX;
10968
+ }
10969
+ // ── vertical walk ────────────────────────────────────────────
10970
+ walk(stmts, depth) {
10971
+ for (const s of stmts) {
10972
+ switch (s.kind) {
10973
+ case "message":
10974
+ this.placeMessage(s);
10975
+ break;
10976
+ case "activate":
10977
+ this.y += SEQ_CONST.ACT_STEP;
10978
+ this.openBar(s.id);
10979
+ this.touch(this.colOf.get(s.id));
10980
+ break;
10981
+ case "deactivate":
10982
+ this.y += SEQ_CONST.ACT_STEP;
10983
+ this.closeBar(s.id);
10984
+ this.touch(this.colOf.get(s.id));
10985
+ break;
10986
+ case "note":
10987
+ this.placeNote(s);
10988
+ break;
10989
+ case "ref":
10990
+ this.placeRef(s.ids, s.text);
10991
+ break;
10992
+ case "divider":
10993
+ this.y += SEQ_CONST.DIV_GAP_BEFORE;
10994
+ this.dividers.push({ text: s.text, y: this.y + SEQ_CONST.DIV_H / 2, width: 0 });
10995
+ this.y += SEQ_CONST.DIV_H;
10996
+ break;
10997
+ case "invariant":
10998
+ this.placeInvariant(s.id, s.text);
10999
+ break;
11000
+ case "destroy":
11001
+ this.placeDestroy(s.id);
11002
+ break;
11003
+ case "fragment":
11004
+ this.placeFragment(s.op, s.operands, depth, s.messageSet);
11005
+ break;
11006
+ }
11007
+ }
11008
+ }
11009
+ nextNumber() {
11010
+ if (this.autoCounter === null || !this.ast.autonumber) return void 0;
11011
+ const n = this.autoCounter;
11012
+ this.autoCounter += this.ast.autonumber.step;
11013
+ return n;
11014
+ }
11015
+ placeMessage(m) {
11016
+ this.y += SEQ_CONST.EVENT_GAP;
11017
+ const y = this.y;
11018
+ const cf = this.colOf.get(m.from);
11019
+ const ct = this.colOf.get(m.to);
11020
+ const self = m.from !== "" && m.from === m.to;
11021
+ let x1;
11022
+ let x2;
11023
+ if (m.arrow === "found") {
11024
+ const c = ct;
11025
+ x2 = this.faceEdge(m.to, c, false);
11026
+ x1 = x2 - SEQ_CONST.LOST_LEN;
11027
+ } else if (m.arrow === "lost") {
11028
+ const c = cf;
11029
+ x1 = this.faceEdge(m.from, c, true);
11030
+ x2 = x1 + SEQ_CONST.LOST_LEN;
11031
+ } else if (self) {
11032
+ const c = cf;
11033
+ x1 = this.faceEdge(m.from, c, true);
11034
+ x2 = x1 + SEQ_CONST.SELF_LOOP_W;
11035
+ } else {
11036
+ const fromRight = (ct ?? 0) > (cf ?? 0);
11037
+ x1 = this.faceEdge(m.from, cf, fromRight);
11038
+ if (m.create) {
11039
+ const halfW = this.headW[ct] / 2;
11040
+ x2 = fromRight ? this.x[ct] - halfW : this.x[ct] + halfW;
11041
+ } else {
11042
+ x2 = this.faceEdge(m.to, ct, !fromRight, m.activateTarget ? 1 : 0);
11043
+ }
11044
+ }
11045
+ const row = { message: m, y, x1, x2, self };
11046
+ const num = this.nextNumber();
11047
+ if (num !== void 0) row.number = num;
11048
+ if (self) {
11049
+ row.selfBottomY = y + SEQ_CONST.SELF_GAP;
11050
+ this.y = row.selfBottomY;
11051
+ }
11052
+ this.messages.push(row);
11053
+ this.contentRight = Math.max(this.contentRight, x1, x2);
11054
+ this.touch(cf);
11055
+ this.touch(ct);
11056
+ if (m.create && ct !== void 0) {
11057
+ const ll = this.llById.get(m.to);
11058
+ ll.headY = y - SEQ_CONST.HEAD_H / 2;
11059
+ ll.axisTop = ll.headY + SEQ_CONST.HEAD_H;
11060
+ }
11061
+ if (m.activateTarget && ct !== void 0) this.openBar(m.to);
11062
+ if (m.deactivateSource && cf !== void 0) this.closeBar(m.from);
11063
+ }
11064
+ placeNote(note) {
11065
+ this.y += SEQ_CONST.NOTE_GAP_BEFORE;
11066
+ const lines = note.text.split(/<br\s*\/?>|\\n/);
11067
+ const textW = lines.reduce((mx, l) => Math.max(mx, textWidth(l, LABEL_SIZE)), 0);
11068
+ const w = Math.max(SEQ_CONST.NOTE_MIN_W, Math.ceil(textW) + SEQ_CONST.NOTE_PAD * 2);
11069
+ const h = lines.length * SEQ_CONST.NOTE_LINE_H + SEQ_CONST.NOTE_PAD * 2;
11070
+ const top = this.y;
11071
+ const cols = note.ids.map((id) => this.colOf.get(id)).filter((c) => c !== void 0);
11072
+ cols.forEach((c) => this.touch(c));
11073
+ let x;
11074
+ let boxW = w;
11075
+ if (note.placement === "over" && cols.length >= 2) {
11076
+ const a = this.x[Math.min(...cols)];
11077
+ const b = this.x[Math.max(...cols)];
11078
+ const span = b - a + this.headW[Math.min(...cols)] / 2 + this.headW[Math.max(...cols)] / 2;
11079
+ boxW = Math.max(w, span);
11080
+ x = (a + b) / 2 - boxW / 2;
11081
+ } else if (note.placement === "left" && cols.length) {
11082
+ x = this.x[cols[0]] - SEQ_CONST.NOTE_SIDE_GAP - w;
11083
+ } else if (note.placement === "right" && cols.length) {
11084
+ x = this.x[cols[0]] + SEQ_CONST.NOTE_SIDE_GAP;
11085
+ } else {
11086
+ const c = cols[0] ?? 0;
11087
+ x = this.x[c] - w / 2;
11088
+ }
11089
+ this.notes.push({ note, x, y: top, width: boxW, height: h });
11090
+ this.noteRight(x, boxW);
11091
+ this.y = top + h;
11092
+ }
11093
+ placeRef(ids, text2) {
11094
+ this.y += SEQ_CONST.REF_GAP_BEFORE;
11095
+ const cols = ids.map((id) => this.colOf.get(id)).filter((c) => c !== void 0);
11096
+ cols.forEach((c) => this.touch(c));
11097
+ const minC = cols.length ? Math.min(...cols) : 0;
11098
+ const maxC = cols.length ? Math.max(...cols) : 0;
11099
+ const left = this.x[minC] - SEQ_CONST.FRAG_PAD_X;
11100
+ const right = this.x[maxC] + SEQ_CONST.FRAG_PAD_X;
11101
+ const top = this.y;
11102
+ this.refs.push({ text: text2, x: left, y: top, width: right - left, height: SEQ_CONST.REF_H });
11103
+ this.noteRight(left, right - left);
11104
+ this.y = top + SEQ_CONST.REF_H;
11105
+ }
11106
+ placeInvariant(id, text2) {
11107
+ this.y += SEQ_CONST.INV_GAP_BEFORE;
11108
+ const col = this.colOf.get(id);
11109
+ this.touch(col);
11110
+ const w = Math.ceil(textWidth(text2, LABEL_SIZE)) + SEQ_CONST.INV_PAD * 2;
11111
+ const cx = col !== void 0 ? this.x[col] : SEQ_CONST.LEFT_MARGIN;
11112
+ const top = this.y;
11113
+ this.invariants.push({ text: text2, cx, y: top, width: w, height: SEQ_CONST.INV_H });
11114
+ this.noteRight(cx - w / 2, w);
11115
+ this.y = top + SEQ_CONST.INV_H;
11116
+ }
11117
+ placeDestroy(id) {
11118
+ this.y += SEQ_CONST.EVENT_GAP / 2;
11119
+ const ll = this.llById.get(id);
11120
+ const col = this.colOf.get(id);
11121
+ if (ll && col !== void 0) {
11122
+ ll.destroyed = true;
11123
+ ll.axisBottom = this.y;
11124
+ this.destroys.push({ x: this.x[col], y: this.y });
11125
+ this.touch(col);
11126
+ }
11127
+ }
11128
+ placeFragment(op, operands, depth, messageSet) {
11129
+ const frameTop = this.y + SEQ_CONST.FRAG_GAP_BEFORE;
11130
+ const acc = { min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY };
11131
+ this.colAccStack.push(acc);
11132
+ const operandGeom = [];
11133
+ this.y = frameTop + SEQ_CONST.FRAG_LABEL_H + SEQ_CONST.FRAG_PAD_TOP - SEQ_CONST.EVENT_GAP;
11134
+ const firstGeom = {};
11135
+ if (operands[0]?.guard) firstGeom.guard = operands[0].guard;
11136
+ operandGeom.push(firstGeom);
11137
+ this.walk(operands[0]?.statements ?? [], depth + 1);
11138
+ for (let oi = 1; oi < operands.length; oi++) {
11139
+ const sepY = this.y + SEQ_CONST.FRAG_OPERAND_GAP;
11140
+ const geom = { sepY };
11141
+ if (operands[oi].guard) geom.guard = operands[oi].guard;
11142
+ operandGeom.push(geom);
11143
+ this.y = sepY + SEQ_CONST.FRAG_PAD_TOP - SEQ_CONST.EVENT_GAP;
11144
+ this.walk(operands[oi].statements, depth + 1);
11145
+ }
11146
+ const frameBottom = this.y + SEQ_CONST.FRAG_PAD_BOTTOM;
11147
+ this.colAccStack.pop();
11148
+ let minC = acc.min;
11149
+ let maxC = acc.max;
11150
+ if (!isFinite(minC)) {
11151
+ minC = 0;
11152
+ maxC = Math.max(0, this.lifelines.length - 1);
11153
+ }
11154
+ const inset = depth * SEQ_CONST.FRAG_NEST_INSET;
11155
+ let left = this.x[minC] - SEQ_CONST.FRAG_PAD_X + inset;
11156
+ let right = this.x[maxC] + SEQ_CONST.FRAG_PAD_X - inset;
11157
+ if (right - left < 40) {
11158
+ const mid = (left + right) / 2;
11159
+ left = mid - 20;
11160
+ right = mid + 20;
11161
+ }
11162
+ const frame = {
11163
+ op,
11164
+ x: left,
11165
+ y: frameTop,
11166
+ width: right - left,
11167
+ height: frameBottom - frameTop,
11168
+ operands: operandGeom
11169
+ };
11170
+ if (messageSet && messageSet.length) frame.messageSet = messageSet;
11171
+ this.fragments.push(frame);
11172
+ this.noteRight(left, right - left);
11173
+ this.y = frameBottom;
11174
+ }
11175
+ };
11176
+ function collectMessages(stmts) {
11177
+ const out = [];
11178
+ for (const s of stmts) {
11179
+ if (s.kind === "message") out.push(s);
11180
+ else if (s.kind === "fragment") {
11181
+ for (const op of s.operands) out.push(...collectMessages(op.statements));
11182
+ }
11183
+ }
11184
+ return out;
11185
+ }
11186
+ function layoutSequence(ast) {
11187
+ return new SequenceLayout(ast).run();
11188
+ }
11189
+
11190
+ // src/diagrams/sequence/renderer.ts
11191
+ var HEAD = "#e8f0fb";
11192
+ var HEAD_STROKE = "#5b85c0";
11193
+ function buildCss5(t) {
11194
+ return `
11195
+ .sx-seq { font-family: system-ui, -apple-system, sans-serif; }
11196
+ .sx-seq-axis { stroke: ${t.neutral}; stroke-width: 1; stroke-dasharray: 4 4; }
11197
+ .sx-seq-head rect, .sx-seq-head path { fill: ${HEAD}; stroke: ${HEAD_STROKE}; stroke-width: 1.4; }
11198
+ .sx-seq-head-name { font: 600 12.5px sans-serif; fill: ${t.text}; }
11199
+ .sx-seq-head-stereo { font: italic 10px sans-serif; fill: ${t.textMuted}; }
11200
+ .sx-seq-actor line, .sx-seq-actor circle { stroke: ${t.stroke}; stroke-width: 1.6; fill: none; stroke-linecap: round; }
11201
+ .sx-seq-actor-head { fill: ${t.bg}; }
11202
+ .sx-seq-icon { fill: ${t.bg}; stroke: ${HEAD_STROKE}; stroke-width: 1.6; }
11203
+ .sx-seq-icon-line { stroke: ${HEAD_STROKE}; stroke-width: 1.6; fill: none; stroke-linecap: round; }
11204
+ .sx-seq-icon-fill { fill: ${HEAD_STROKE}; stroke: none; }
11205
+ .sx-seq-act { fill: ${t.bg}; stroke: ${HEAD_STROKE}; stroke-width: 1.2; }
11206
+ .sx-seq-msg { stroke: ${t.stroke}; stroke-width: 1.5; fill: none; }
11207
+ .sx-seq-msg-reply { stroke: ${t.stroke}; stroke-width: 1.4; fill: none; stroke-dasharray: 6 4; }
11208
+ .sx-seq-msg-label { font: 11px sans-serif; fill: ${t.text}; }
11209
+ .sx-seq-msg-num { font: 600 10px sans-serif; fill: ${t.textMuted}; }
11210
+ .sx-seq-endpoint { fill: ${t.stroke}; }
11211
+ .sx-seq-frame { fill: none; stroke: ${t.neutral}; stroke-width: 1.2; }
11212
+ .sx-seq-frame-neg { fill: ${t.negative}; fill-opacity: 0.06; }
11213
+ .sx-seq-frame-sep { stroke: ${t.neutral}; stroke-width: 1; stroke-dasharray: 5 4; }
11214
+ .sx-seq-frame-tab { fill: ${t.fillMuted}; stroke: ${t.neutral}; stroke-width: 1.2; }
11215
+ .sx-seq-frame-op { font: 700 11px sans-serif; fill: ${t.text}; }
11216
+ .sx-seq-guard { font: italic 10.5px sans-serif; fill: ${t.textMuted}; }
11217
+ .sx-seq-ref-name { font: 600 12px sans-serif; fill: ${t.text}; }
11218
+ .sx-seq-note rect { fill: #fdf6da; stroke: #d9c97e; stroke-width: 1.1; }
11219
+ .sx-seq-note path { fill: #efe2a6; stroke: #d9c97e; stroke-width: 1.1; }
11220
+ .sx-seq-note-text { font: 11px sans-serif; fill: ${t.text}; }
11221
+ .sx-seq-div line { stroke: ${t.neutral}; stroke-width: 1; }
11222
+ .sx-seq-div rect { fill: ${t.fillMuted}; stroke: ${t.neutral}; stroke-width: 1; }
11223
+ .sx-seq-div-text { font: 600 11px sans-serif; fill: ${t.textMuted}; }
11224
+ .sx-seq-inv rect { fill: ${t.bg}; stroke: ${t.neutral}; stroke-width: 1.1; }
11225
+ .sx-seq-inv-text { font: italic 10.5px sans-serif; fill: ${t.textMuted}; }
11226
+ .sx-seq-destroy { stroke: ${t.negative}; stroke-width: 2; }
11227
+ .sx-seq-title { font: 700 16px sans-serif; fill: ${t.text}; }
11228
+ `.trim();
11229
+ }
11230
+ function markers3(t) {
11231
+ return defs([
11232
+ el(
11233
+ "marker",
11234
+ { id: "sx-seq-filled", viewBox: "0 0 12 10", refX: 10, refY: 5, markerWidth: 11, markerHeight: 9, orient: "auto" },
11235
+ [el("polygon", { points: "0,0 11,5 0,10", fill: t.stroke })]
11236
+ ),
11237
+ el(
11238
+ "marker",
11239
+ { id: "sx-seq-open", viewBox: "0 0 12 10", refX: 10, refY: 5, markerWidth: 12, markerHeight: 10, orient: "auto" },
11240
+ [el("polyline", { points: "0,0 10,5 0,10", fill: "none", stroke: t.stroke, "stroke-width": 1.5 })]
11241
+ )
11242
+ ]);
11243
+ }
11244
+ var CHAR_W = 6;
11245
+ function renderLifeline(ll) {
11246
+ const parts = [];
11247
+ parts.push(line({ class: "sx-seq-axis", x1: ll.x, y1: ll.axisTop, x2: ll.x, y2: ll.axisBottom }));
11248
+ parts.push(renderHead(ll));
11249
+ return group({ class: "sx-seq-lifeline", "data-id": ll.participant.id, "data-kind": ll.participant.kind }, parts);
11250
+ }
11251
+ function renderHead(ll) {
11252
+ const p = ll.participant;
11253
+ const cx = ll.x;
11254
+ const stereo = p.stereotype ? `\xAB${p.stereotype}\xBB` : null;
11255
+ if (p.kind === "actor") {
11256
+ const topY = ll.headY;
11257
+ const fig = [];
11258
+ if (stereo) {
11259
+ fig.push(text({ class: "sx-seq-head-stereo", x: cx, y: topY - 4, "text-anchor": "middle" }, stereo));
11260
+ }
11261
+ fig.push(
11262
+ circle({ class: "sx-seq-actor-head", cx, cy: topY + 6, r: 4.5 }),
11263
+ line({ x1: cx, y1: topY + 10, x2: cx, y2: topY + 22 }),
11264
+ line({ x1: cx - 8, y1: topY + 14, x2: cx + 8, y2: topY + 14 }),
11265
+ line({ x1: cx, y1: topY + 22, x2: cx - 7, y2: topY + 32 }),
11266
+ line({ x1: cx, y1: topY + 22, x2: cx + 7, y2: topY + 32 }),
11267
+ text({ class: "sx-seq-head-name", x: cx, y: topY + 44, "text-anchor": "middle" }, p.name)
11268
+ );
11269
+ return group({ class: "sx-seq-actor sx-seq-head" }, fig);
11270
+ }
11271
+ if (p.kind === "boundary" || p.kind === "control" || p.kind === "entity") {
11272
+ return renderRobustnessIcon(ll, stereo);
11273
+ }
11274
+ if (p.kind === "database") {
11275
+ const w = ll.headW;
11276
+ const h = ll.headH;
11277
+ const x = ll.headX;
11278
+ const y = ll.headY;
11279
+ const ry = 5;
11280
+ const d = `M ${x} ${y + ry} a ${w / 2} ${ry} 0 0 1 ${w} 0 v ${h - 2 * ry} a ${w / 2} ${ry} 0 0 1 ${-w} 0 Z M ${x} ${y + ry} a ${w / 2} ${ry} 0 0 0 ${w} 0`;
11281
+ return group({ class: "sx-seq-head" }, [
11282
+ path({ d }),
11283
+ text({ class: "sx-seq-head-name", x: cx, y: y + h / 2 + 6, "text-anchor": "middle" }, p.name)
11284
+ ]);
11285
+ }
11286
+ const parts = [
11287
+ rect({ x: ll.headX, y: ll.headY, width: ll.headW, height: ll.headH, rx: 3, ry: 3 })
11288
+ ];
11289
+ const stereoText = stereo ?? (p.kind === "collections" || p.kind === "queue" ? `\xAB${p.kind}\xBB` : null);
11290
+ if (stereoText) {
11291
+ parts.push(
11292
+ text({ class: "sx-seq-head-stereo", x: cx, y: ll.headY + 14, "text-anchor": "middle" }, stereoText)
11293
+ );
11294
+ parts.push(
11295
+ text({ class: "sx-seq-head-name", x: cx, y: ll.headY + 28, "text-anchor": "middle" }, p.name)
11296
+ );
11297
+ } else {
11298
+ parts.push(
11299
+ text({ class: "sx-seq-head-name", x: cx, y: ll.headY + ll.headH / 2 + 5, "text-anchor": "middle" }, p.name)
11300
+ );
11301
+ }
11302
+ return group({ class: "sx-seq-head" }, parts);
11303
+ }
11304
+ function renderRobustnessIcon(ll, stereo) {
11305
+ const p = ll.participant;
11306
+ const cx = ll.x;
11307
+ const R = 11;
11308
+ const cy = ll.headY + 14;
11309
+ const parts = [];
11310
+ if (stereo) {
11311
+ parts.push(text({ class: "sx-seq-head-stereo", x: cx, y: ll.headY - 2, "text-anchor": "middle" }, stereo));
11312
+ }
11313
+ parts.push(circle({ class: "sx-seq-icon", cx, cy, r: R }));
11314
+ if (p.kind === "boundary") {
11315
+ const bx = cx - R - 9;
11316
+ parts.push(line({ class: "sx-seq-icon-line", x1: bx, y1: cy - R, x2: bx, y2: cy + R }));
11317
+ parts.push(line({ class: "sx-seq-icon-line", x1: bx, y1: cy, x2: cx - R, y2: cy }));
11318
+ } else if (p.kind === "entity") {
11319
+ parts.push(line({ class: "sx-seq-icon-line", x1: cx - R - 2, y1: cy + R + 1, x2: cx + R + 2, y2: cy + R + 1 }));
11320
+ } else {
11321
+ parts.push(
11322
+ polygon({ class: "sx-seq-icon-fill", points: `${cx - 2},${cy - R - 6} ${cx + 5},${cy - R - 1} ${cx - 3},${cy - R + 2}` })
11323
+ );
11324
+ }
11325
+ parts.push(text({ class: "sx-seq-head-name", x: cx, y: ll.headY + 44, "text-anchor": "middle" }, p.name));
11326
+ return group({ class: "sx-seq-head sx-seq-icon-head", "data-icon": p.kind }, parts);
11327
+ }
11328
+ function renderActivation(a) {
11329
+ const h = Math.max(6, a.yBottom - a.yTop);
11330
+ return rect({
11331
+ class: "sx-seq-act",
11332
+ "data-id": a.id,
11333
+ x: a.x,
11334
+ y: a.yTop,
11335
+ width: 10,
11336
+ height: h
11337
+ });
11338
+ }
11339
+ function renderMessage(m) {
11340
+ const k = m.message.arrow;
11341
+ const lineClass2 = k === "reply" ? "sx-seq-msg-reply" : "sx-seq-msg";
11342
+ const markerEnd = k === "async" || k === "reply" ? "url(#sx-seq-open)" : "url(#sx-seq-filled)";
11343
+ const parts = [];
11344
+ if (m.self) {
11345
+ const bottom = m.selfBottomY ?? m.y + 28;
11346
+ const right = m.x2;
11347
+ const d = `M ${m.x1} ${m.y} H ${right} V ${bottom} H ${m.x1}`;
11348
+ parts.push(path({ class: lineClass2, d, "marker-end": markerEnd }));
11349
+ if (m.message.label) {
11350
+ parts.push(
11351
+ text(
11352
+ { class: "sx-seq-msg-label", x: right + 6, y: (m.y + bottom) / 2 + 3 },
11353
+ numbered(m, m.message.label)
11354
+ )
11355
+ );
11356
+ }
11357
+ } else {
11358
+ parts.push(line({ class: lineClass2, x1: m.x1, y1: m.y, x2: m.x2, y2: m.y, "marker-end": markerEnd }));
11359
+ if (k === "lost") parts.push(circle({ class: "sx-seq-endpoint", cx: m.x2, cy: m.y, r: 4 }));
11360
+ if (k === "found") parts.push(circle({ class: "sx-seq-endpoint", cx: m.x1, cy: m.y, r: 4 }));
11361
+ if (m.message.label) {
11362
+ const mid = (m.x1 + m.x2) / 2;
11363
+ parts.push(
11364
+ text(
11365
+ { class: "sx-seq-msg-label", x: mid, y: m.y - 6, "text-anchor": "middle" },
11366
+ numbered(m, m.message.label)
11367
+ )
11368
+ );
11369
+ }
11370
+ }
11371
+ return group(
11372
+ {
11373
+ class: "sx-seq-message",
11374
+ "data-kind": k,
11375
+ "data-from": m.message.from,
11376
+ "data-to": m.message.to
11377
+ },
11378
+ parts
11379
+ );
11380
+ }
11381
+ function numbered(m, label) {
11382
+ return m.number !== void 0 ? `${m.number}. ${label}` : label;
11383
+ }
11384
+ function tabWidth(f) {
11385
+ return Math.max(40, f.op.length * 8 + 22);
11386
+ }
11387
+ function renderFragmentBox(f) {
11388
+ const frameClass = f.op === "neg" ? "sx-seq-frame sx-seq-frame-neg" : "sx-seq-frame";
11389
+ const parts = [
11390
+ rect({ class: frameClass, x: f.x, y: f.y, width: f.width, height: f.height, rx: 2, ry: 2 })
11391
+ ];
11392
+ for (const op of f.operands) {
11393
+ if (op.sepY !== void 0) {
11394
+ parts.push(line({ class: "sx-seq-frame-sep", x1: f.x, y1: op.sepY, x2: f.x + f.width, y2: op.sepY }));
11395
+ }
11396
+ }
11397
+ return group({ class: "sx-seq-fragment", "data-op": f.op }, parts);
11398
+ }
11399
+ function renderFragmentTab(f) {
11400
+ const tabW = tabWidth(f);
11401
+ const tabH = 18;
11402
+ const fold = 6;
11403
+ const tx = f.x;
11404
+ const ty = f.y;
11405
+ const tab = `M ${tx} ${ty} H ${tx + tabW} V ${ty + tabH - fold} L ${tx + tabW - fold} ${ty + tabH} H ${tx} Z`;
11406
+ const parts = [
11407
+ path({ class: "sx-seq-frame-tab", d: tab }),
11408
+ text({ class: "sx-seq-frame-op", x: tx + 8, y: ty + 13 }, f.op)
11409
+ ];
11410
+ if (f.messageSet && f.messageSet.length) {
11411
+ parts.push(
11412
+ text({ class: "sx-seq-guard", x: tx + tabW + 8, y: ty + 13 }, `{${f.messageSet.join(", ")}}`)
11413
+ );
11414
+ }
11415
+ f.operands.forEach((op, i) => {
11416
+ if (!op.guard) return;
11417
+ const gx = i === 0 ? f.x + tabW + 8 : f.x + 8;
11418
+ const gy = i === 0 ? f.y + 13 : (op.sepY ?? f.y) + 14;
11419
+ parts.push(text({ class: "sx-seq-guard", x: gx, y: gy }, `[${op.guard}]`));
11420
+ });
11421
+ return group({ class: "sx-seq-fragment-tab-g", "data-op": f.op }, parts);
11422
+ }
11423
+ function renderRef(r) {
11424
+ const tabW = 40;
11425
+ const tabH = 18;
11426
+ const fold = 6;
11427
+ const tab = `M ${r.x} ${r.y} H ${r.x + tabW} V ${r.y + tabH - fold} L ${r.x + tabW - fold} ${r.y + tabH} H ${r.x} Z`;
11428
+ return group({ class: "sx-seq-fragment", "data-op": "ref" }, [
11429
+ rect({ class: "sx-seq-frame", x: r.x, y: r.y, width: r.width, height: r.height, rx: 2, ry: 2 }),
11430
+ path({ class: "sx-seq-frame-tab", d: tab }),
11431
+ text({ class: "sx-seq-frame-op", x: r.x + 8, y: r.y + 13 }, "ref"),
11432
+ text(
11433
+ { class: "sx-seq-ref-name", x: r.x + r.width / 2, y: r.y + r.height / 2 + 8, "text-anchor": "middle" },
11434
+ r.text
11435
+ )
11436
+ ]);
11437
+ }
11438
+ function renderNote2(nb) {
11439
+ const fold = 8;
11440
+ const x = nb.x;
11441
+ const y = nb.y;
11442
+ const w = nb.width;
11443
+ const h = nb.height;
11444
+ const body = `M ${x} ${y} H ${x + w - fold} L ${x + w} ${y + fold} V ${y + h} H ${x} Z`;
11445
+ const corner = `M ${x + w - fold} ${y} V ${y + fold} H ${x + w} Z`;
11446
+ const lines = nb.note.text.split(/<br\s*\/?>|\\n/);
11447
+ const startY = y + h / 2 - (lines.length - 1) * 15 / 2 + 4;
11448
+ const texts = lines.map(
11449
+ (l, i) => text({ class: "sx-seq-note-text", x: x + w / 2, y: startY + i * 15, "text-anchor": "middle" }, l)
11450
+ );
11451
+ return group({ class: "sx-seq-note" }, [path({ d: body }), path({ d: corner }), ...texts]);
11452
+ }
11453
+ function renderDivider(d) {
11454
+ const labelW = Math.max(60, d.text.length * CHAR_W + 24);
11455
+ const cx = d.width / 2;
11456
+ const parts = [
11457
+ line({ x1: 8, y1: d.y, x2: cx - labelW / 2, y2: d.y }),
11458
+ line({ x1: cx + labelW / 2, y1: d.y, x2: d.width - 8, y2: d.y }),
11459
+ rect({ x: cx - labelW / 2, y: d.y - 11, width: labelW, height: 22, rx: 4, ry: 4 }),
11460
+ text({ class: "sx-seq-div-text", x: cx, y: d.y + 4, "text-anchor": "middle" }, d.text)
11461
+ ];
11462
+ return group({ class: "sx-seq-div" }, parts);
11463
+ }
11464
+ function renderInvariant(iv) {
11465
+ return group({ class: "sx-seq-inv" }, [
11466
+ rect({ x: iv.cx - iv.width / 2, y: iv.y, width: iv.width, height: iv.height, rx: iv.height / 2, ry: iv.height / 2 }),
11467
+ text({ class: "sx-seq-inv-text", x: iv.cx, y: iv.y + iv.height / 2 + 4, "text-anchor": "middle" }, `{${iv.text}}`)
11468
+ ]);
11469
+ }
11470
+ function renderDestroy(d) {
11471
+ const r = 7;
11472
+ return group({ class: "sx-seq-destroy-g" }, [
11473
+ line({ class: "sx-seq-destroy", x1: d.x - r, y1: d.y - r, x2: d.x + r, y2: d.y + r }),
11474
+ line({ class: "sx-seq-destroy", x1: d.x - r, y1: d.y + r, x2: d.x + r, y2: d.y - r })
11475
+ ]);
11476
+ }
11477
+ function renderSequenceLayout(layout, config) {
11478
+ const t = resolveBaseTheme(config?.theme ?? "default");
11479
+ const children = [];
11480
+ const nMsg = layout.messages.length;
11481
+ const nFrag = layout.fragments.length;
11482
+ children.push(title(`Sequence Diagram${layout.title ? " \u2014 " + layout.title : ""}`));
11483
+ children.push(
11484
+ desc(
11485
+ `${layout.lifelines.length} participants, ${nMsg} messages, ${nFrag} combined fragments.`
11486
+ )
11487
+ );
11488
+ children.push(el("style", {}, buildCss5(t)));
11489
+ children.push(markers3(t));
11490
+ const titleBand = layout.title ? 32 : 0;
11491
+ if (layout.title) {
11492
+ children.push(
11493
+ text({ x: layout.width / 2, y: 22, class: "sx-seq-title", "text-anchor": "middle" }, layout.title)
11494
+ );
11495
+ }
11496
+ const body = [];
11497
+ body.push(group({ class: "sx-seq-lifelines" }, layout.lifelines.map(renderLifeline)));
11498
+ body.push(group({ class: "sx-seq-frames" }, layout.fragments.map(renderFragmentBox)));
11499
+ body.push(group({ class: "sx-seq-acts" }, layout.activations.map(renderActivation)));
11500
+ body.push(group({ class: "sx-seq-frame-tabs" }, layout.fragments.map(renderFragmentTab)));
11501
+ body.push(group({ class: "sx-seq-refs" }, layout.refs.map(renderRef)));
11502
+ body.push(group({ class: "sx-seq-messages" }, layout.messages.map(renderMessage)));
11503
+ body.push(group({ class: "sx-seq-notes" }, layout.notes.map(renderNote2)));
11504
+ body.push(group({ class: "sx-seq-divs" }, layout.dividers.map(renderDivider)));
11505
+ body.push(group({ class: "sx-seq-invs" }, layout.invariants.map(renderInvariant)));
11506
+ body.push(group({ class: "sx-seq-destroys" }, layout.destroys.map(renderDestroy)));
11507
+ children.push(
11508
+ titleBand ? group({ transform: `translate(0, ${titleBand})` }, body) : group({}, body)
11509
+ );
11510
+ const height = layout.height + titleBand;
11511
+ return svgRoot(
11512
+ {
11513
+ class: "sx-seq",
11514
+ role: "img",
11515
+ "aria-label": escapeXml(layout.title ?? "UML sequence diagram"),
11516
+ width: layout.width,
11517
+ height,
11518
+ viewBox: `0 0 ${layout.width} ${height}`,
11519
+ "data-diagram-type": "sequence"
11520
+ },
11521
+ children
11522
+ );
11523
+ }
11524
+ function renderSequence(textOrAst, config) {
11525
+ const ast = typeof textOrAst === "string" ? parseSequence(textOrAst) : textOrAst;
11526
+ const layout = layoutSequence(ast);
11527
+ return renderSequenceLayout(layout, config);
11528
+ }
11529
+
11530
+ // src/diagrams/sequence/index.ts
11531
+ var sequence = {
11532
+ type: "sequence",
11533
+ detect(text2) {
11534
+ for (const raw of text2.split(/\r?\n/)) {
11535
+ const t = raw.trim();
11536
+ if (!t) continue;
11537
+ if (t.startsWith("#") || t.startsWith("//")) continue;
11538
+ return /^sequence\b/i.test(t);
11539
+ }
11540
+ return false;
11541
+ },
11542
+ parse: parseSequence,
11543
+ render(text2, config) {
11544
+ return renderSequence(text2, config);
11545
+ }
11546
+ };
11547
+
11548
+ // src/diagrams/petri/parser.ts
11549
+ var PetriParseError = class extends Error {
11550
+ line;
11551
+ constructor(message, line2) {
11552
+ super(line2 !== void 0 ? `${message} (line ${line2})` : message);
11553
+ this.name = "PetriParseError";
11554
+ this.line = line2;
11555
+ }
11556
+ };
11557
+ var OPENERS = ['"', "\u201C", "\u300C", "\u300E", "\xAB"];
11558
+ var CLOSERS = ['"', "\u201D", "\u300D", "\u300F", "\xBB"];
11559
+ function tokenize3(s) {
11560
+ const out = [];
11561
+ let i = 0;
11562
+ while (i < s.length) {
11563
+ const c = s[i];
11564
+ if (c === " " || c === " ") {
11565
+ i++;
11566
+ continue;
11567
+ }
11568
+ const oi = OPENERS.indexOf(c);
11569
+ if (oi >= 0) {
11570
+ const close = CLOSERS[oi];
11571
+ let j2 = i + 1;
11572
+ let buf2 = "";
11573
+ while (j2 < s.length && s[j2] !== close && s[j2] !== '"') {
11574
+ buf2 += s[j2];
11575
+ j2++;
11576
+ }
11577
+ out.push({ text: buf2, quoted: true });
11578
+ i = j2 + 1;
11579
+ continue;
11580
+ }
11581
+ let j = i;
11582
+ let buf = "";
11583
+ while (j < s.length && s[j] !== " " && s[j] !== " ") {
11584
+ buf += s[j];
11585
+ j++;
11586
+ }
11587
+ out.push({ text: buf, quoted: false });
11588
+ i = j;
11589
+ }
11590
+ return out;
11591
+ }
11592
+ function normalizeKeyNums(line2) {
11593
+ return line2.replace(/([A-Za-z]+)\s*:\s*(-?\d+(?:\.\d+)?)/g, "$1:$2");
11594
+ }
11595
+ var DOT_RE = /^[•●・]+$/;
11596
+ var ARC_RE = /^(\S+?)\s*(->|-o|=>|--)\s*(\S+)(.*)$/;
11597
+ function toInt(raw, what, line2) {
11598
+ if (!/^-?\d+$/.test(raw)) {
11599
+ throw new PetriParseError(`${what} must be an integer, got "${raw}"`, line2);
11600
+ }
11601
+ return parseInt(raw, 10);
11602
+ }
11603
+ function parsePetri(text2) {
11604
+ const rawLines = text2.split(/\r?\n/);
11605
+ const ast = {
11606
+ type: "petri",
11607
+ direction: "lr",
11608
+ tokenStyle: "auto",
11609
+ places: [],
11610
+ transitions: [],
11611
+ arcs: [],
11612
+ fireSequence: [],
11613
+ warnings: []
11614
+ };
11615
+ const placeIds = /* @__PURE__ */ new Set();
11616
+ const transIds = /* @__PURE__ */ new Set();
11617
+ let sawHeader = false;
11618
+ for (let ln = 0; ln < rawLines.length; ln++) {
11619
+ const rawLine = rawLines[ln];
11620
+ const lineNo = ln + 1;
11621
+ const trimmed = rawLine.trim();
11622
+ if (!trimmed) continue;
11623
+ if (trimmed.startsWith("#") || trimmed.startsWith("//")) continue;
11624
+ if (!sawHeader && /^petri(net)?\b/i.test(trimmed)) {
11625
+ sawHeader = true;
11626
+ const toks = tokenize3(trimmed);
11627
+ const titleTok = toks.find((t, idx) => idx > 0 && t.quoted);
11628
+ if (titleTok) ast.title = titleTok.text;
11629
+ else if (toks[1] && !toks[1].quoted) ast.title = toks.slice(1).map((t) => t.text).join(" ");
11630
+ continue;
11631
+ }
11632
+ if (!sawHeader) {
11633
+ if (/^petri(net)?\b/i.test(trimmed)) {
11634
+ sawHeader = true;
11635
+ continue;
11636
+ }
11637
+ }
11638
+ const lower = trimmed.toLowerCase();
11639
+ if (lower.startsWith("layout:")) {
11640
+ const v = trimmed.slice(trimmed.indexOf(":") + 1).trim().toLowerCase();
11641
+ if (v === "lr" || v === "tb") ast.direction = v;
11642
+ else ast.warnings.push(`Unknown layout "${v}" (line ${lineNo}); using lr.`);
11643
+ continue;
11644
+ }
11645
+ if (lower.startsWith("tokens:")) {
11646
+ const v = trimmed.slice(trimmed.indexOf(":") + 1).trim().toLowerCase();
11647
+ if (v === "dots" || v === "count" || v === "auto") ast.tokenStyle = v;
11648
+ else ast.warnings.push(`Unknown token style "${v}" (line ${lineNo}); using auto.`);
11649
+ continue;
11650
+ }
11651
+ if (lower.startsWith("marking:")) {
11652
+ parseMarking(trimmed.slice(trimmed.indexOf(":") + 1), lineNo, ast, placeIds);
11653
+ continue;
11654
+ }
11655
+ if (lower.startsWith("fire:")) {
11656
+ const ids = trimmed.slice(trimmed.indexOf(":") + 1).split(",").map((s) => s.trim()).filter(Boolean);
11657
+ ast.fireSequence.push(...ids);
11658
+ continue;
11659
+ }
11660
+ if (/^place\b/i.test(trimmed)) {
11661
+ ast.places.push(parsePlace(trimmed, lineNo, placeIds, transIds));
11662
+ continue;
11663
+ }
11664
+ if (/^transition\b/i.test(trimmed)) {
11665
+ ast.transitions.push(parseTransition(rawLine.trim(), lineNo, placeIds, transIds));
11666
+ continue;
11667
+ }
11668
+ const arcM = ARC_RE.exec(trimmed);
11669
+ if (arcM) {
11670
+ ast.arcs.push(parseArc(arcM, lineNo, placeIds, transIds));
11671
+ continue;
11672
+ }
11673
+ ast.warnings.push(`Unrecognized line ${lineNo}: "${trimmed}"`);
11674
+ }
11675
+ for (const id of ast.fireSequence) {
11676
+ if (!transIds.has(id)) {
11677
+ throw new PetriParseError(`fire: references unknown transition "${id}"`);
11678
+ }
11679
+ }
11680
+ return ast;
11681
+ }
11682
+ function declareDup(id, lineNo, placeIds, transIds) {
11683
+ if (placeIds.has(id) || transIds.has(id)) {
11684
+ throw new PetriParseError(`duplicate node id "${id}"`, lineNo);
11685
+ }
11686
+ }
11687
+ function parsePlace(line2, lineNo, placeIds, transIds) {
11688
+ const toks = tokenize3(normalizeKeyNums(line2));
11689
+ const idTok = toks[1];
11690
+ if (!idTok || idTok.quoted) throw new PetriParseError(`place is missing an id`, lineNo);
11691
+ const id = idTok.text;
11692
+ declareDup(id, lineNo, placeIds, transIds);
11693
+ const place = { id, tokens: 0, line: lineNo };
11694
+ const labelParts = [];
11695
+ for (let i = 2; i < toks.length; i++) {
11696
+ const t = toks[i];
11697
+ if (t.quoted) {
11698
+ labelParts.push(t.text);
11699
+ continue;
11700
+ }
11701
+ const txt = t.text;
11702
+ if (/^\*\d+$/.test(txt)) {
11703
+ place.tokens = parseInt(txt.slice(1), 10);
11704
+ } else if (DOT_RE.test(txt)) {
11705
+ place.tokens = [...txt].length;
11706
+ } else if (/^tokens:/i.test(txt)) {
11707
+ place.tokens = toInt(txt.slice(txt.indexOf(":") + 1), "tokens", lineNo);
11708
+ } else if (/^capacity:/i.test(txt)) {
11709
+ const k = toInt(txt.slice(txt.indexOf(":") + 1), "capacity", lineNo);
11710
+ if (k <= 0) throw new PetriParseError(`capacity must be a positive integer`, lineNo);
11711
+ place.capacity = k;
11712
+ } else {
11713
+ labelParts.push(txt);
11714
+ }
11715
+ }
11716
+ if (place.tokens < 0) throw new PetriParseError(`token count cannot be negative`, lineNo);
11717
+ if (labelParts.length) place.label = labelParts.join(" ");
11718
+ placeIds.add(id);
11719
+ return place;
11720
+ }
11721
+ function parseTransition(line2, lineNo, placeIds, transIds) {
11722
+ let guard;
11723
+ const stripped = line2.replace(/\[([^\]]*)\]/, (_m, g) => {
11724
+ guard = String(g).trim();
11725
+ return " ";
11726
+ });
11727
+ const toks = tokenize3(normalizeKeyNums(stripped));
11728
+ const idTok = toks[1];
11729
+ if (!idTok || idTok.quoted) throw new PetriParseError(`transition is missing an id`, lineNo);
11730
+ const id = idTok.text;
11731
+ declareDup(id, lineNo, placeIds, transIds);
11732
+ const tr = { id, kind: "immediate", line: lineNo };
11733
+ if (guard) tr.guard = guard;
11734
+ const labelParts = [];
11735
+ for (let i = 2; i < toks.length; i++) {
11736
+ const t = toks[i];
11737
+ if (t.quoted) {
11738
+ labelParts.push(t.text);
11739
+ continue;
11740
+ }
11741
+ const txt = t.text;
11742
+ const low = txt.toLowerCase();
11743
+ if (low === "immediate") tr.kind = "immediate";
11744
+ else if (low === "timed") tr.kind = "timed";
11745
+ else if (/^rate:/i.test(txt)) {
11746
+ tr.rate = parseFloat(txt.slice(txt.indexOf(":") + 1));
11747
+ tr.kind = "timed";
11748
+ } else if (/^(prio|priority):/i.test(txt)) {
11749
+ tr.priority = toInt(txt.slice(txt.indexOf(":") + 1), "priority", lineNo);
11750
+ } else {
11751
+ labelParts.push(txt);
11752
+ }
11753
+ }
11754
+ if (labelParts.length) tr.label = labelParts.join(" ");
11755
+ transIds.add(id);
11756
+ return tr;
11757
+ }
11758
+ var ARROW_TYPE = {
11759
+ "->": "standard",
11760
+ "-o": "inhibitor",
11761
+ "--": "read",
11762
+ "=>": "reset"
11763
+ };
11764
+ function parseArc(m, lineNo, placeIds, transIds) {
11765
+ const from = m[1];
11766
+ const arrow2 = m[2];
11767
+ const to = m[3];
11768
+ const rest = m[4] ?? "";
11769
+ const type = ARROW_TYPE[arrow2];
11770
+ const known = (id) => placeIds.has(id) ? "place" : transIds.has(id) ? "transition" : null;
11771
+ const kf = known(from);
11772
+ const kt = known(to);
11773
+ if (!kf) throw new PetriParseError(`arc references unknown node "${from}" \u2014 declare it as a place or transition first`, lineNo);
11774
+ if (!kt) throw new PetriParseError(`arc references unknown node "${to}" \u2014 declare it as a place or transition first`, lineNo);
11775
+ if (kf === kt) {
11776
+ throw new PetriParseError(
11777
+ `arc connects two ${kf}s \u2014 a Petri net arc must go place\u2192transition or transition\u2192place`,
11778
+ lineNo
11779
+ );
11780
+ }
11781
+ if ((type === "inhibitor" || type === "reset") && kf !== "place") {
11782
+ throw new PetriParseError(`${type} arcs are place\u2192transition only`, lineNo);
11783
+ }
11784
+ const arc = { from, to, type, weight: 1, line: lineNo };
11785
+ const toks = tokenize3(normalizeKeyNums(rest));
11786
+ const labelParts = [];
11787
+ for (const t of toks) {
11788
+ if (t.quoted) {
11789
+ labelParts.push(t.text);
11790
+ continue;
11791
+ }
11792
+ const txt = t.text;
11793
+ if (/^\*\d+$/.test(txt)) arc.weight = parseInt(txt.slice(1), 10);
11794
+ else if (/^weight:/i.test(txt)) arc.weight = toInt(txt.slice(txt.indexOf(":") + 1), "weight", lineNo);
11795
+ else labelParts.push(txt);
11796
+ }
11797
+ if (arc.weight <= 0) throw new PetriParseError(`arc weight must be a positive integer`, lineNo);
11798
+ if (labelParts.length) arc.label = labelParts.join(" ");
11799
+ return arc;
11800
+ }
11801
+ function parseMarking(body, lineNo, ast, placeIds) {
11802
+ for (const part of body.split(",")) {
11803
+ const p = part.trim();
11804
+ if (!p) continue;
11805
+ const eq = p.indexOf("=");
11806
+ if (eq < 0) {
11807
+ ast.warnings.push(`Bad marking entry "${p}" (line ${lineNo}); expected id=n.`);
11808
+ continue;
11809
+ }
11810
+ const id = p.slice(0, eq).trim();
11811
+ const n = toInt(p.slice(eq + 1).trim(), "marking", lineNo);
11812
+ if (!placeIds.has(id)) {
11813
+ throw new PetriParseError(`marking references unknown place "${id}"`, lineNo);
11814
+ }
11815
+ const place = ast.places.find((pl) => pl.id === id);
11816
+ if (place) place.tokens = n;
11817
+ }
11818
+ }
11819
+
11820
+ // src/diagrams/petri/layout.ts
11821
+ var PETRI_CONST = {
11822
+ PLACE_R: 18,
11823
+ TRANS_BAR_W: 8,
11824
+ TRANS_BAR_H: 44,
11825
+ TRANS_BOX_W: 26,
11826
+ TRANS_BOX_H: 40,
11827
+ LAYER_GAP: 70,
11828
+ RANK_GAP: 46,
11829
+ TOKEN_R: 3.5,
11830
+ TOKEN_COUNT_MAX_DOTS: 4,
11831
+ ARC_WEIGHT_OFFSET: 9,
11832
+ LABEL_GAP: 6,
11833
+ MARGIN: 22,
11834
+ BACKEDGE_BOW: 30,
11835
+ LABEL_LINE_H: 13,
11836
+ CHAR_W: 6.2
11837
+ };
11838
+ function layoutPetri(ast) {
11839
+ const C2 = PETRI_CONST;
11840
+ const dir = ast.direction;
11841
+ const warnings = [...ast.warnings];
11842
+ const kindOf = /* @__PURE__ */ new Map();
11843
+ for (const p of ast.places) kindOf.set(p.id, "place");
11844
+ for (const t of ast.transitions) kindOf.set(t.id, "transition");
11845
+ const ids = [...ast.places.map((p) => p.id), ...ast.transitions.map((t) => t.id)];
11846
+ const declOrder = /* @__PURE__ */ new Map();
11847
+ ids.forEach((id, i) => declOrder.set(id, i));
11848
+ const edges = ast.arcs.map((a) => ({ from: a.from, to: a.to, reversed: false }));
11849
+ const adj = /* @__PURE__ */ new Map();
11850
+ ids.forEach((id) => adj.set(id, []));
11851
+ edges.forEach((e, i) => adj.get(e.from).push(i));
11852
+ const state2 = /* @__PURE__ */ new Map();
11853
+ ids.forEach((id) => state2.set(id, 0));
11854
+ const dfs = (u) => {
11855
+ state2.set(u, 1);
11856
+ for (const ei of adj.get(u)) {
11857
+ const e = edges[ei];
11858
+ const s = state2.get(e.to);
11859
+ if (s === 1) e.reversed = true;
11860
+ else if (s === 0) dfs(e.to);
11861
+ }
11862
+ state2.set(u, 2);
11863
+ };
11864
+ for (const id of ids) if (state2.get(id) === 0) dfs(id);
11865
+ const succ = /* @__PURE__ */ new Map();
11866
+ const indeg = /* @__PURE__ */ new Map();
11867
+ ids.forEach((id) => {
11868
+ succ.set(id, []);
11869
+ indeg.set(id, 0);
11870
+ });
11871
+ for (const e of edges) {
11872
+ const [from, to] = e.reversed ? [e.to, e.from] : [e.from, e.to];
11873
+ succ.get(from).push(to);
11874
+ indeg.set(to, indeg.get(to) + 1);
11875
+ }
11876
+ const layer = /* @__PURE__ */ new Map();
11877
+ ids.forEach((id) => layer.set(id, 0));
11878
+ const queue = ids.filter((id) => indeg.get(id) === 0);
11879
+ const indegWork = new Map(indeg);
11880
+ let qi = 0;
11881
+ const q = [...queue];
11882
+ while (qi < q.length) {
11883
+ const u = q[qi++];
11884
+ for (const v of succ.get(u)) {
11885
+ layer.set(v, Math.max(layer.get(v), layer.get(u) + 1));
11886
+ indegWork.set(v, indegWork.get(v) - 1);
11887
+ if (indegWork.get(v) === 0) q.push(v);
11888
+ }
11889
+ }
11890
+ const maxLayer = Math.max(0, ...ids.map((id) => layer.get(id)));
11891
+ const layers = Array.from({ length: maxLayer + 1 }, () => []);
11892
+ for (const id of ids) layers[layer.get(id)].push(id);
11893
+ layers.forEach((arr) => arr.sort((a, b) => declOrder.get(a) - declOrder.get(b)));
11894
+ const pos = /* @__PURE__ */ new Map();
11895
+ const reindex = () => layers.forEach((arr) => arr.forEach((id, i) => pos.set(id, i)));
11896
+ reindex();
11897
+ const preds = /* @__PURE__ */ new Map();
11898
+ const sucs = /* @__PURE__ */ new Map();
11899
+ ids.forEach((id) => {
11900
+ preds.set(id, []);
11901
+ sucs.set(id, []);
11902
+ });
11903
+ for (const e of edges) {
11904
+ const [from, to] = e.reversed ? [e.to, e.from] : [e.from, e.to];
11905
+ sucs.get(from).push(to);
11906
+ preds.get(to).push(from);
11907
+ }
11908
+ const bary = (id, neigh) => {
11909
+ const ns = neigh.get(id);
11910
+ if (!ns.length) return pos.get(id);
11911
+ return ns.reduce((s, n) => s + pos.get(n), 0) / ns.length;
11912
+ };
11913
+ for (let sweep = 0; sweep < 4; sweep++) {
11914
+ const downward = sweep % 2 === 0;
11915
+ const range = downward ? [...Array(layers.length).keys()].slice(1) : [...Array(layers.length).keys()].slice(0, -1).reverse();
11916
+ for (const L of range) {
11917
+ const neigh = downward ? preds : sucs;
11918
+ layers[L].sort((a, b) => bary(a, neigh) - bary(b, neigh) || declOrder.get(a) - declOrder.get(b));
11919
+ reindex();
11920
+ }
11921
+ }
11922
+ const sizeOf2 = (id) => {
11923
+ if (kindOf.get(id) === "place") return { halfW: C2.PLACE_R, halfH: C2.PLACE_R, r: C2.PLACE_R };
11924
+ const tr = ast.transitions.find((t) => t.id === id);
11925
+ const long = tr.kind === "timed" ? C2.TRANS_BOX_H : C2.TRANS_BAR_H;
11926
+ const thin = tr.kind === "timed" ? C2.TRANS_BOX_W : C2.TRANS_BAR_W;
11927
+ const halfW = dir === "lr" ? thin / 2 : long / 2;
11928
+ const halfH = dir === "lr" ? long / 2 : thin / 2;
11929
+ return { halfW, halfH, r: 0 };
11930
+ };
11931
+ const flowHalf = (id) => {
11932
+ const s = sizeOf2(id);
11933
+ return dir === "lr" ? s.halfW : s.halfH;
11934
+ };
11935
+ const crossHalf = (id) => {
11936
+ const s = sizeOf2(id);
11937
+ return dir === "lr" ? s.halfH : s.halfW;
11938
+ };
11939
+ const layerHalf = layers.map((arr) => Math.max(0, ...arr.map(flowHalf)));
11940
+ const slot = Math.max(0, ...ids.map(crossHalf)) * 2 + C2.RANK_GAP;
11941
+ const maxCount = Math.max(1, ...layers.map((a) => a.length));
11942
+ const crossCenter = C2.MARGIN + C2.LABEL_LINE_H * 2 + maxCount * slot / 2;
11943
+ const flowCenter = [];
11944
+ let acc = C2.MARGIN + C2.LABEL_LINE_H * 2;
11945
+ for (let L = 0; L < layers.length; L++) {
11946
+ acc += layerHalf[L];
11947
+ flowCenter[L] = acc;
11948
+ acc += layerHalf[L] + C2.LAYER_GAP;
11949
+ }
11950
+ const geom = /* @__PURE__ */ new Map();
11951
+ layers.forEach((arr, L) => {
11952
+ const n = arr.length;
11953
+ const total = (n - 1) * slot;
11954
+ arr.forEach((id, i) => {
11955
+ const cross = crossCenter - total / 2 + i * slot;
11956
+ const flow = flowCenter[L];
11957
+ const cx = dir === "lr" ? flow : cross;
11958
+ const cy = dir === "lr" ? cross : flow;
11959
+ const s = sizeOf2(id);
11960
+ geom.set(id, { id, kind: kindOf.get(id), cx, cy, halfW: s.halfW, halfH: s.halfH, r: s.r, layer: L });
11961
+ });
11962
+ });
11963
+ const marking = /* @__PURE__ */ new Map();
11964
+ for (const p of ast.places) marking.set(p.id, p.tokens);
11965
+ const inArcs = (tid) => ast.arcs.filter((a) => a.to === tid);
11966
+ const outArcs = (tid) => ast.arcs.filter((a) => a.from === tid);
11967
+ const capOf = (pid2) => ast.places.find((p) => p.id === pid2)?.capacity;
11968
+ const isEnabled = (tid, M) => {
11969
+ for (const a of inArcs(tid)) {
11970
+ const have = M.get(a.from) ?? 0;
11971
+ if (a.type === "standard" || a.type === "read") {
11972
+ if (have < a.weight) return false;
11973
+ } else if (a.type === "inhibitor") {
11974
+ if (have >= a.weight) return false;
11975
+ }
11976
+ }
11977
+ for (const a of outArcs(tid)) {
11978
+ const cap = capOf(a.to);
11979
+ if (cap !== void 0 && (M.get(a.to) ?? 0) + a.weight > cap) return false;
11980
+ }
11981
+ return true;
11982
+ };
11983
+ const applyFire = (tid, M) => {
11984
+ for (const a of inArcs(tid)) {
11985
+ if (a.type === "standard") M.set(a.from, (M.get(a.from) ?? 0) - a.weight);
11986
+ else if (a.type === "reset") M.set(a.from, 0);
11987
+ }
11988
+ for (const a of outArcs(tid)) M.set(a.to, (M.get(a.to) ?? 0) + a.weight);
11989
+ };
11990
+ ast.fireSequence.forEach((tid, i) => {
11991
+ if (isEnabled(tid, marking)) applyFire(tid, marking);
11992
+ else warnings.push(`fire step ${i + 1}: transition "${tid}" is not enabled in the current marking; skipped.`);
11993
+ });
11994
+ const producers = (pid2) => ast.arcs.filter((a) => a.to === pid2 && a.type === "standard").length;
11995
+ const enabledIds = [];
11996
+ const deadIds = /* @__PURE__ */ new Set();
11997
+ for (const tr of ast.transitions) {
11998
+ if (isEnabled(tr.id, marking)) {
11999
+ enabledIds.push(tr.id);
12000
+ continue;
12001
+ }
12002
+ const dead = inArcs(tr.id).some(
12003
+ (a) => (a.type === "standard" || a.type === "read") && (marking.get(a.from) ?? 0) < a.weight && producers(a.from) === 0
12004
+ );
12005
+ if (dead) deadIds.add(tr.id);
12006
+ }
12007
+ const boundary = (g, dx2, dy2) => {
12008
+ const len = Math.hypot(dx2, dy2) || 1;
12009
+ const ux = dx2 / len;
12010
+ const uy = dy2 / len;
12011
+ if (g.kind === "place") return { x: g.cx + ux * g.r, y: g.cy + uy * g.r };
12012
+ const tx = ux !== 0 ? g.halfW / Math.abs(ux) : Infinity;
12013
+ const ty = uy !== 0 ? g.halfH / Math.abs(uy) : Infinity;
12014
+ const t = Math.min(tx, ty);
12015
+ return { x: g.cx + ux * t, y: g.cy + uy * t };
12016
+ };
12017
+ const bandMaxCross = Math.max(...[...geom.values()].map((g) => dir === "lr" ? g.cy + g.halfH : g.cx + g.halfW));
12018
+ const arcGeoms = ast.arcs.map((a, i) => {
12019
+ const A = geom.get(a.from);
12020
+ const B = geom.get(a.to);
12021
+ const reversed = edges[i].reversed;
12022
+ let points;
12023
+ if (!reversed) {
12024
+ const pA = boundary(A, B.cx - A.cx, B.cy - A.cy);
12025
+ const pB = boundary(B, A.cx - B.cx, A.cy - B.cy);
12026
+ points = [pA, pB];
12027
+ } else {
12028
+ if (dir === "lr") {
12029
+ const pA = boundary(A, 0, 1);
12030
+ const pB = boundary(B, 0, 1);
12031
+ const bowY = bandMaxCross + C2.BACKEDGE_BOW;
12032
+ points = [pA, { x: pA.x, y: bowY }, { x: pB.x, y: bowY }, pB];
12033
+ } else {
12034
+ const pA = boundary(A, 1, 0);
12035
+ const pB = boundary(B, 1, 0);
12036
+ const bowX = bandMaxCross + C2.BACKEDGE_BOW;
12037
+ points = [pA, { x: bowX, y: pA.y }, { x: bowX, y: pB.y }, pB];
12038
+ }
12039
+ }
12040
+ const p0 = points[0];
12041
+ const p1 = points[points.length - 1];
12042
+ const mx = (p0.x + p1.x) / 2;
12043
+ const my = (p0.y + p1.y) / 2;
12044
+ const ddx = p1.x - p0.x;
12045
+ const ddy = p1.y - p0.y;
12046
+ const dl = Math.hypot(ddx, ddy) || 1;
12047
+ const labelX = mx - ddy / dl * C2.ARC_WEIGHT_OFFSET;
12048
+ const labelY = my + ddx / dl * C2.ARC_WEIGHT_OFFSET;
12049
+ return { arc: a, type: a.type, weight: a.weight, points, reversed, labelX, labelY };
12050
+ });
12051
+ const hasIncoming = (pid2) => ast.arcs.some((a) => a.to === pid2);
12052
+ const hasOutgoing = (pid2) => ast.arcs.some((a) => a.from === pid2);
12053
+ const placeBoxes = ast.places.map((p) => {
12054
+ const g = geom.get(p.id);
12055
+ return {
12056
+ place: p,
12057
+ cx: g.cx,
12058
+ cy: g.cy,
12059
+ r: g.r,
12060
+ tokens: marking.get(p.id) ?? 0,
12061
+ isSource: !hasIncoming(p.id),
12062
+ isSink: !hasOutgoing(p.id)
12063
+ };
12064
+ });
12065
+ const transBoxes = ast.transitions.map((t) => {
12066
+ const g = geom.get(t.id);
12067
+ return {
12068
+ transition: t,
12069
+ cx: g.cx,
12070
+ cy: g.cy,
12071
+ w: g.halfW * 2,
12072
+ h: g.halfH * 2,
12073
+ enabled: enabledIds.includes(t.id),
12074
+ dead: deadIds.has(t.id)
12075
+ };
12076
+ });
12077
+ const bb = { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity };
12078
+ const addBox = (x0, y0, x1, y1) => {
12079
+ bb.minX = Math.min(bb.minX, x0, x1);
12080
+ bb.minY = Math.min(bb.minY, y0, y1);
12081
+ bb.maxX = Math.max(bb.maxX, x0, x1);
12082
+ bb.maxY = Math.max(bb.maxY, y0, y1);
12083
+ };
12084
+ const addPt = (p) => addBox(p.x, p.y, p.x, p.y);
12085
+ const labelW = (s) => s ? s.length * C2.CHAR_W : 0;
12086
+ for (const pb of placeBoxes) {
12087
+ addBox(pb.cx - pb.r, pb.cy - pb.r, pb.cx + pb.r, pb.cy + pb.r);
12088
+ const lw = Math.max(labelW(pb.place.id), labelW(pb.place.label)) / 2;
12089
+ addBox(pb.cx - lw, pb.cy - pb.r - C2.LABEL_LINE_H * 2, pb.cx + lw, pb.cy);
12090
+ if (pb.place.capacity !== void 0) addBox(pb.cx, pb.cy + pb.r, pb.cx + 24, pb.cy + pb.r + C2.LABEL_LINE_H);
12091
+ }
12092
+ for (const tb of transBoxes) {
12093
+ addBox(tb.cx - tb.w / 2, tb.cy - tb.h / 2, tb.cx + tb.w / 2, tb.cy + tb.h / 2);
12094
+ const lw = Math.max(labelW(tb.transition.id), labelW(tb.transition.label)) / 2;
12095
+ addBox(tb.cx - lw, tb.cy - tb.h / 2 - C2.LABEL_LINE_H * 2, tb.cx + lw, tb.cy);
12096
+ }
12097
+ for (const ag of arcGeoms) {
12098
+ ag.points.forEach(addPt);
12099
+ if (ag.weight > 1) addBox(ag.labelX - 6, ag.labelY - 8, ag.labelX + 6, ag.labelY + 4);
12100
+ }
12101
+ const dx = C2.MARGIN - bb.minX;
12102
+ const dy = C2.MARGIN - bb.minY;
12103
+ const shift = (p) => ({ x: p.x + dx, y: p.y + dy });
12104
+ placeBoxes.forEach((pb) => {
12105
+ pb.cx += dx;
12106
+ pb.cy += dy;
12107
+ });
12108
+ transBoxes.forEach((tb) => {
12109
+ tb.cx += dx;
12110
+ tb.cy += dy;
12111
+ });
12112
+ arcGeoms.forEach((ag) => {
12113
+ ag.points = ag.points.map(shift);
12114
+ ag.labelX += dx;
12115
+ ag.labelY += dy;
12116
+ });
12117
+ const width = bb.maxX - bb.minX + 2 * C2.MARGIN;
12118
+ const height = bb.maxY - bb.minY + 2 * C2.MARGIN;
12119
+ const subclass = detectSubclass(ast);
12120
+ return {
12121
+ width: Math.round(width),
12122
+ height: Math.round(height),
12123
+ title: ast.title,
12124
+ direction: dir,
12125
+ places: placeBoxes,
12126
+ transitions: transBoxes,
12127
+ arcs: arcGeoms,
12128
+ subclass,
12129
+ enabledIds,
12130
+ warnings,
12131
+ ast
12132
+ };
12133
+ }
12134
+ function detectSubclass(ast) {
12135
+ if (!ast.transitions.length || !ast.places.length) return void 0;
12136
+ const inT = (tid) => ast.arcs.filter((a) => a.to === tid).length;
12137
+ const outT = (tid) => ast.arcs.filter((a) => a.from === tid).length;
12138
+ const inP = (pid2) => ast.arcs.filter((a) => a.to === pid2).length;
12139
+ const outP = (pid2) => ast.arcs.filter((a) => a.from === pid2).length;
12140
+ const stateMachine = ast.transitions.every((t) => inT(t.id) === 1 && outT(t.id) === 1);
12141
+ const markedGraph = ast.places.every((p) => inP(p.id) === 1 && outP(p.id) === 1);
12142
+ const sources = ast.places.filter((p) => inP(p.id) === 0);
12143
+ const sinks = ast.places.filter((p) => outP(p.id) === 0);
12144
+ const workflowNet = sources.length === 1 && sinks.length === 1;
12145
+ const tags = [];
12146
+ if (stateMachine) tags.push("state machine");
12147
+ if (markedGraph) tags.push("marked graph");
12148
+ if (workflowNet) tags.push("workflow net");
12149
+ return tags.length ? tags.join(", ") : void 0;
12150
+ }
12151
+
12152
+ // src/diagrams/petri/renderer.ts
12153
+ function buildCss6(t) {
12154
+ return `
12155
+ .sx-petri { font-family: system-ui, -apple-system, sans-serif; }
12156
+ .sx-petri-place { fill: ${t.placeFill}; stroke: ${t.placeStroke}; stroke-width: 2; }
12157
+ .sx-petri-place-cap { stroke-dasharray: 4 3; }
12158
+ .sx-petri-token { fill: ${t.tokenFill}; stroke: none; }
12159
+ .sx-petri-token-num { font: 600 11px sans-serif; fill: ${t.tokenFill}; }
12160
+ .sx-petri-bar { fill: ${t.transitionBarFill}; stroke: ${t.transitionStroke}; stroke-width: 2; }
12161
+ .sx-petri-box { fill: ${t.transitionBoxFill}; stroke: ${t.transitionStroke}; stroke-width: 2; }
12162
+ .sx-petri-enabled { fill: ${t.enabledFill}; stroke: ${t.enabledStroke}; stroke-width: 3; }
12163
+ .sx-petri-dead .sx-petri-bar, .sx-petri-dead .sx-petri-box { stroke: ${t.deadStroke}; }
12164
+ .sx-petri-dead { opacity: 0.7; }
12165
+ .sx-petri-label { font: 12px sans-serif; fill: ${t.text}; }
12166
+ .sx-petri-sublabel { font: italic 10px sans-serif; fill: ${t.textMuted}; }
12167
+ .sx-petri-cap { font: 9px sans-serif; fill: ${t.textMuted}; }
12168
+ .sx-petri-rate { font: italic 9px sans-serif; fill: ${t.textMuted}; }
12169
+ .sx-petri-arc { stroke: ${t.arcStroke}; stroke-width: 2; fill: none; }
12170
+ .sx-petri-arc-inhibitor, .sx-petri-arc-reset { stroke: ${t.inhibitorStroke}; }
12171
+ .sx-petri-inhibitor-dot { fill: ${t.placeFill}; stroke: ${t.inhibitorStroke}; stroke-width: 2; }
12172
+ .sx-petri-weight { font: 600 9px sans-serif; fill: ${t.weightLabel}; }
12173
+ .sx-petri-title { font: 700 16px sans-serif; fill: ${t.text}; }
12174
+ `.trim();
12175
+ }
12176
+ function markers4(t) {
12177
+ return defs([
12178
+ el(
12179
+ "marker",
12180
+ { id: "sx-petri-head", viewBox: "0 0 10 10", refX: 9, refY: 5, markerWidth: 8, markerHeight: 8, orient: "auto-start-reverse" },
12181
+ [polygon({ points: "0,0 9,5 0,10", fill: t.arcStroke })]
12182
+ ),
12183
+ el(
12184
+ "marker",
12185
+ { id: "sx-petri-head-reset", viewBox: "0 0 16 10", refX: 15, refY: 5, markerWidth: 13, markerHeight: 9, orient: "auto-start-reverse" },
12186
+ [
12187
+ polygon({ points: "0,0 7,5 0,10", fill: t.inhibitorStroke }),
12188
+ polygon({ points: "7,0 14,5 7,10", fill: t.inhibitorStroke })
12189
+ ]
12190
+ )
12191
+ ]);
12192
+ }
12193
+ function tokenPositions(n) {
12194
+ const d = PETRI_CONST.TOKEN_R + 2.2;
12195
+ switch (n) {
12196
+ case 1:
12197
+ return [{ x: 0, y: 0 }];
12198
+ case 2:
12199
+ return [
12200
+ { x: -d, y: 0 },
12201
+ { x: d, y: 0 }
12202
+ ];
12203
+ case 3:
12204
+ return [
12205
+ { x: 0, y: -d },
12206
+ { x: -d, y: d },
12207
+ { x: d, y: d }
12208
+ ];
12209
+ default:
12210
+ return [
12211
+ { x: -d, y: -d },
12212
+ { x: d, y: -d },
12213
+ { x: -d, y: d },
12214
+ { x: d, y: d }
12215
+ ];
12216
+ }
12217
+ }
12218
+ function renderTokens(pb, style) {
12219
+ const n = pb.tokens;
12220
+ if (n <= 0) return "";
12221
+ const asDots = style !== "count" && n <= PETRI_CONST.TOKEN_COUNT_MAX_DOTS;
12222
+ if (asDots) {
12223
+ return tokenPositions(n).map((p) => circle({ class: "sx-petri-token", cx: pb.cx + p.x, cy: pb.cy + p.y, r: PETRI_CONST.TOKEN_R })).join("");
12224
+ }
12225
+ return text(
12226
+ { class: "sx-petri-token-num", x: pb.cx, y: pb.cy + 4, "text-anchor": "middle" },
12227
+ String(n)
12228
+ );
12229
+ }
12230
+ function renderPlace(pb, style) {
12231
+ const cls = pb.place.capacity !== void 0 ? "sx-petri-place sx-petri-place-cap" : "sx-petri-place";
12232
+ const parts = [circle({ class: cls, cx: pb.cx, cy: pb.cy, r: pb.r })];
12233
+ parts.push(renderTokens(pb, style));
12234
+ const labelY = pb.cy - pb.r - PETRI_CONST.LABEL_GAP;
12235
+ parts.push(text({ class: "sx-petri-label", x: pb.cx, y: labelY, "text-anchor": "middle" }, pb.place.id));
12236
+ if (pb.place.label) {
12237
+ parts.push(
12238
+ text({ class: "sx-petri-sublabel", x: pb.cx, y: labelY - PETRI_CONST.LABEL_LINE_H, "text-anchor": "middle" }, pb.place.label)
12239
+ );
12240
+ }
12241
+ if (pb.place.capacity !== void 0) {
12242
+ parts.push(
12243
+ text({ class: "sx-petri-cap", x: pb.cx + pb.r + 3, y: pb.cy + pb.r + 9 }, `K=${pb.place.capacity}`)
12244
+ );
12245
+ }
12246
+ const attrs = {
12247
+ class: "sx-petri-place-g",
12248
+ "data-id": pb.place.id,
12249
+ "data-tokens": pb.tokens
12250
+ };
12251
+ if (pb.place.capacity !== void 0) attrs["data-capacity"] = pb.place.capacity;
12252
+ if (pb.isSource) attrs["data-source"] = "true";
12253
+ if (pb.isSink) attrs["data-sink"] = "true";
12254
+ return group(attrs, parts);
12255
+ }
12256
+ function renderTransition(tb) {
12257
+ const x = tb.cx - tb.w / 2;
12258
+ const y = tb.cy - tb.h / 2;
12259
+ const parts = [];
12260
+ if (tb.enabled) {
12261
+ parts.push(
12262
+ rect({ class: "sx-petri-enabled", x: x - 4, y: y - 4, width: tb.w + 8, height: tb.h + 8, rx: 4, ry: 4 })
12263
+ );
12264
+ }
12265
+ const shapeClass = tb.transition.kind === "timed" ? "sx-petri-box" : "sx-petri-bar";
12266
+ parts.push(rect({ class: shapeClass, x, y, width: tb.w, height: tb.h, rx: 1, ry: 1 }));
12267
+ const labelY = y - PETRI_CONST.LABEL_GAP;
12268
+ parts.push(text({ class: "sx-petri-label", x: tb.cx, y: labelY, "text-anchor": "middle" }, tb.transition.id));
12269
+ if (tb.transition.label) {
12270
+ parts.push(
12271
+ text({ class: "sx-petri-sublabel", x: tb.cx, y: labelY - PETRI_CONST.LABEL_LINE_H, "text-anchor": "middle" }, tb.transition.label)
12272
+ );
12273
+ }
12274
+ if (tb.transition.kind === "timed" && tb.transition.rate !== void 0) {
12275
+ parts.push(text({ class: "sx-petri-rate", x: tb.cx, y: y + tb.h + 10, "text-anchor": "middle" }, `\u03BB=${tb.transition.rate}`));
12276
+ }
12277
+ if (tb.transition.guard) {
12278
+ parts.push(text({ class: "sx-petri-rate", x: tb.cx + tb.w / 2 + 4, y: tb.cy + 3 }, `[${tb.transition.guard}]`));
12279
+ }
12280
+ return group(
12281
+ {
12282
+ class: tb.dead ? "sx-petri-trans-g sx-petri-dead" : "sx-petri-trans-g",
12283
+ "data-id": tb.transition.id,
12284
+ "data-kind": tb.transition.kind,
12285
+ "data-enabled": tb.enabled ? "true" : "false"
12286
+ },
12287
+ parts
12288
+ );
12289
+ }
12290
+ function arcPath(ag) {
12291
+ const p = ag.points;
12292
+ if (p.length === 4) {
12293
+ return `M ${p[0].x} ${p[0].y} C ${p[1].x} ${p[1].y} ${p[2].x} ${p[2].y} ${p[3].x} ${p[3].y}`;
12294
+ }
12295
+ return `M ${p[0].x} ${p[0].y} L ${p[p.length - 1].x} ${p[p.length - 1].y}`;
12296
+ }
12297
+ function renderArc(ag) {
12298
+ const parts = [];
12299
+ let cls = "sx-petri-arc";
12300
+ let markerEnd;
12301
+ if (ag.type === "standard") {
12302
+ markerEnd = "url(#sx-petri-head)";
12303
+ } else if (ag.type === "reset") {
12304
+ cls += " sx-petri-arc-reset";
12305
+ markerEnd = "url(#sx-petri-head-reset)";
12306
+ } else if (ag.type === "inhibitor") {
12307
+ cls += " sx-petri-arc-inhibitor";
12308
+ }
12309
+ parts.push(path({ class: cls, d: arcPath(ag), ...markerEnd ? { "marker-end": markerEnd } : {} }));
12310
+ if (ag.type === "inhibitor") {
12311
+ const pts = ag.points;
12312
+ const last = pts[pts.length - 1];
12313
+ const prev = pts[pts.length - 2];
12314
+ const dx = last.x - prev.x;
12315
+ const dy = last.y - prev.y;
12316
+ const len = Math.hypot(dx, dy) || 1;
12317
+ const r = 4;
12318
+ const cx = last.x - dx / len * r;
12319
+ const cy = last.y - dy / len * r;
12320
+ parts.push(circle({ class: "sx-petri-inhibitor-dot", cx, cy, r }));
12321
+ }
12322
+ if (ag.weight > 1) {
12323
+ parts.push(
12324
+ text({ class: "sx-petri-weight", x: ag.labelX, y: ag.labelY + 3, "text-anchor": "middle" }, String(ag.weight))
12325
+ );
12326
+ }
12327
+ return group(
12328
+ { class: "sx-petri-arc-g", "data-from": ag.arc.from, "data-to": ag.arc.to, "data-type": ag.type, "data-weight": ag.weight },
12329
+ parts
12330
+ );
12331
+ }
12332
+ function renderPetriLayout(layout, config) {
12333
+ const t = resolvePetriTheme(config?.theme ?? "default");
12334
+ const children = [];
12335
+ const marking = layout.places.filter((p) => p.tokens > 0).map((p) => `${p.place.id}:${p.tokens}`).join(", ");
12336
+ const descParts = [
12337
+ `${layout.places.length} places, ${layout.transitions.length} transitions, ${layout.arcs.length} arcs.`,
12338
+ marking ? `Marking {${marking}}.` : "Empty marking.",
12339
+ layout.enabledIds.length ? `Enabled: ${layout.enabledIds.join(", ")}.` : "No enabled transitions.",
12340
+ layout.subclass ? `Class: ${layout.subclass}.` : ""
12341
+ ].filter(Boolean);
12342
+ children.push(title(`Petri net${layout.title ? " \u2014 " + layout.title : ""}`));
12343
+ children.push(desc(descParts.join(" ")));
12344
+ children.push(el("style", {}, buildCss6(t)));
12345
+ children.push(markers4(t));
12346
+ const titleBand = layout.title ? 32 : 0;
12347
+ if (layout.title) {
12348
+ children.push(text({ x: layout.width / 2, y: 22, class: "sx-petri-title", "text-anchor": "middle" }, layout.title));
12349
+ }
12350
+ const body = [];
12351
+ body.push(group({ class: "sx-petri-arcs" }, layout.arcs.map(renderArc)));
12352
+ body.push(group({ class: "sx-petri-places" }, layout.places.map((p) => renderPlace(p, layout.ast.tokenStyle))));
12353
+ body.push(group({ class: "sx-petri-transitions" }, layout.transitions.map(renderTransition)));
12354
+ children.push(titleBand ? group({ transform: `translate(0, ${titleBand})` }, body) : group({}, body));
12355
+ const height = layout.height + titleBand;
12356
+ return svgRoot(
12357
+ {
12358
+ class: "sx-petri",
12359
+ role: "img",
12360
+ "aria-label": escapeXml(layout.title ?? "Petri net"),
12361
+ width: layout.width,
12362
+ height,
12363
+ viewBox: `0 0 ${layout.width} ${height}`,
12364
+ "data-diagram-type": "petri"
12365
+ },
12366
+ children
12367
+ );
12368
+ }
12369
+ function renderPetri(textOrAst, config) {
12370
+ const ast = typeof textOrAst === "string" ? parsePetri(textOrAst) : textOrAst;
12371
+ const layout = layoutPetri(ast);
12372
+ return renderPetriLayout(layout, config);
12373
+ }
12374
+
12375
+ // src/diagrams/petri/index.ts
12376
+ var petri = {
12377
+ type: "petri",
12378
+ detect(text2) {
12379
+ for (const raw of text2.split(/\r?\n/)) {
12380
+ const t = raw.trim();
12381
+ if (!t) continue;
12382
+ if (t.startsWith("#") || t.startsWith("//")) continue;
12383
+ return /^petri(net)?\b/i.test(t);
12384
+ }
12385
+ return false;
12386
+ },
12387
+ parse: parsePetri,
12388
+ render(text2, config) {
12389
+ return renderPetri(text2, config);
12390
+ }
12391
+ };
12392
+
12393
+ // src/diagrams/network/parser.ts
12394
+ var NetworkParseError = class extends Error {
12395
+ name = "NetworkParseError";
12396
+ };
12397
+ var DEVICE_KINDS = /* @__PURE__ */ new Set([
12398
+ "router",
12399
+ "switch",
12400
+ "l3switch",
12401
+ "firewall",
12402
+ "loadbalancer",
12403
+ "ap",
12404
+ "wlc",
12405
+ "gateway",
12406
+ "modem",
12407
+ "ids",
12408
+ "proxy",
12409
+ "vpngw",
12410
+ "server",
12411
+ "serverfarm",
12412
+ "pc",
12413
+ "laptop",
12414
+ "mobile",
12415
+ "ipphone",
12416
+ "printer",
12417
+ "storage",
12418
+ "camera",
12419
+ "nvr",
12420
+ "dvr",
12421
+ "poeswitch",
12422
+ "encoder",
12423
+ "monitor",
12424
+ "internet",
12425
+ "wan",
12426
+ "cloud",
12427
+ "pstn",
12428
+ "lan"
12429
+ ]);
12430
+ var KIND_ALIASES = {
12431
+ multilayer: "l3switch",
12432
+ wifi: "ap",
12433
+ workstation: "pc",
12434
+ phone: "mobile",
12435
+ voip: "ipphone",
12436
+ nas: "storage",
12437
+ san: "storage",
12438
+ servers: "serverfarm",
12439
+ ips: "ids",
12440
+ decoder: "encoder",
12441
+ videowall: "monitor",
12442
+ segment: "lan"
12443
+ };
12444
+ var GROUP_KINDS = /* @__PURE__ */ new Set(["site", "building", "campus", "rack", "subnet", "vlan", "zone", "dmz"]);
12445
+ var GROUP_KIND_ALIAS = { building: "site", campus: "site" };
12446
+ var TIERS = /* @__PURE__ */ new Set(["edge", "core", "distribution", "access"]);
12447
+ var CAMERA_TYPES = /* @__PURE__ */ new Set(["fixed", "bullet", "dome", "ptz", "turret"]);
12448
+ var LAYOUT_MODES = /* @__PURE__ */ new Set([
12449
+ "tiered",
12450
+ "tree",
12451
+ "star",
12452
+ "ring",
12453
+ "bus",
12454
+ "mesh",
12455
+ "spine-leaf",
12456
+ "manual"
12457
+ ]);
12458
+ var LINK_TYPE_KEYWORDS = {
12459
+ copper: "copper",
12460
+ ethernet: "copper",
12461
+ fiber: "fiber",
12462
+ fibre: "fiber",
12463
+ wireless: "wireless",
12464
+ wifi: "wireless",
12465
+ serial: "serial",
12466
+ wan: "serial",
12467
+ poe: "poe",
12468
+ vpn: "vpn",
12469
+ lag: "lag",
12470
+ portchannel: "lag"
12471
+ };
12472
+ var CONNECTORS = /* @__PURE__ */ new Set(["--", "->", "=="]);
12473
+ var SPEED_RE = /^\d+(?:\.\d+)?[MGT]$/;
12474
+ var QUOTE_RE = /"([^"]*)"|「([^」]*)」|『([^』]*)』|“([^”]*)”|«([^»]*)»|'([^']*)'/g;
12475
+ function stripComment2(line2) {
12476
+ let inQuote = false;
12477
+ let close = "";
12478
+ const pairs = { '"': '"', "\u300C": "\u300D", "\u300E": "\u300F", "\u201C": "\u201D", "\xAB": "\xBB", "'": "'" };
12479
+ for (let i = 0; i < line2.length; i++) {
12480
+ const ch = line2[i];
12481
+ if (inQuote) {
12482
+ if (ch === close) inQuote = false;
12483
+ continue;
12484
+ }
12485
+ if (pairs[ch]) {
12486
+ inQuote = true;
12487
+ close = pairs[ch];
12488
+ continue;
12489
+ }
12490
+ if (ch === "#") return line2.slice(0, i);
12491
+ if (ch === "/" && line2[i + 1] === "/") return line2.slice(0, i);
12492
+ }
12493
+ return line2;
12494
+ }
12495
+ function splitStatements(line2) {
12496
+ const out = [];
12497
+ let buf = "";
12498
+ let inQuote = false;
12499
+ let close = "";
12500
+ const pairs = { '"': '"', "\u300C": "\u300D", "\u300E": "\u300F", "\u201C": "\u201D", "\xAB": "\xBB", "'": "'" };
12501
+ for (let i = 0; i < line2.length; i++) {
12502
+ const ch = line2[i];
12503
+ if (inQuote) {
12504
+ if (ch === close) inQuote = false;
12505
+ buf += ch;
12506
+ continue;
12507
+ }
12508
+ if (pairs[ch]) {
12509
+ inQuote = true;
12510
+ close = pairs[ch];
12511
+ buf += ch;
12512
+ continue;
12513
+ }
12514
+ if (ch === ";") {
12515
+ out.push(buf);
12516
+ buf = "";
12517
+ continue;
12518
+ }
12519
+ buf += ch;
12520
+ }
12521
+ out.push(buf);
12522
+ return out;
12523
+ }
12524
+ function tokenize4(raw) {
12525
+ const line2 = stripComment2(raw);
12526
+ const strings = [];
12527
+ const masked = line2.replace(QUOTE_RE, (...m) => {
12528
+ const inner = m.slice(1, 7).find((g) => g !== void 0) ?? "";
12529
+ strings.push(inner);
12530
+ return ` @@${strings.length - 1}@@ `;
12531
+ });
12532
+ const spaced = masked.replace(/(--|->|==)/g, " $1 ");
12533
+ const out = [];
12534
+ for (const w of spaced.split(/\s+/)) {
12535
+ if (!w) continue;
12536
+ const sm = /^@@(\d+)@@$/.exec(w);
12537
+ if (sm) {
12538
+ out.push({ value: strings[Number(sm[1])] ?? "", str: true });
12539
+ } else {
12540
+ out.push({ value: w, str: false });
12541
+ }
12542
+ }
12543
+ return out;
12544
+ }
12545
+ function levenshtein(a, b) {
12546
+ const m = a.length, n = b.length;
12547
+ const d = Array.from({ length: m + 1 }, (_, i) => [i, ...Array(n).fill(0)]);
12548
+ for (let j = 0; j <= n; j++) d[0][j] = j;
12549
+ for (let i = 1; i <= m; i++) {
12550
+ for (let j = 1; j <= n; j++) {
12551
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
12552
+ d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
12553
+ }
12554
+ }
12555
+ return d[m][n];
12556
+ }
12557
+ function nearestKind(bad) {
12558
+ let best;
12559
+ let bestDist = Infinity;
12560
+ for (const k of [...DEVICE_KINDS, ...Object.keys(KIND_ALIASES)]) {
12561
+ const dist = levenshtein(bad.toLowerCase(), k);
12562
+ if (dist < bestDist) {
12563
+ bestDist = dist;
12564
+ best = k;
12565
+ }
12566
+ }
12567
+ return bestDist <= 3 ? best : void 0;
12568
+ }
12569
+ function ipToInt(ip) {
12570
+ const parts = ip.split(".");
12571
+ if (parts.length !== 4) return null;
12572
+ let v = 0;
12573
+ for (const p of parts) {
12574
+ const n = Number(p);
12575
+ if (!Number.isInteger(n) || n < 0 || n > 255) return null;
12576
+ v = v * 256 + n;
12577
+ }
12578
+ return v >>> 0;
12579
+ }
12580
+ function ipInCidr(ip, cidr) {
12581
+ const m = /^(\d+\.\d+\.\d+\.\d+)\s*\/\s*(\d+)$/.exec(cidr.trim());
12582
+ if (!m) return null;
12583
+ const netInt = ipToInt(m[1]);
12584
+ const prefix = Number(m[2]);
12585
+ const hostInt = ipToInt(ip.split("/")[0]);
12586
+ if (netInt === null || hostInt === null || prefix < 0 || prefix > 32) return null;
12587
+ const mask = prefix === 0 ? 0 : 4294967295 << 32 - prefix >>> 0;
12588
+ return (netInt & mask) === (hostInt & mask);
12589
+ }
12590
+ var SWITCH_CLASS = /* @__PURE__ */ new Set(["switch", "l3switch", "poeswitch"]);
12591
+ function parseNetwork(text2) {
12592
+ const ast = {
12593
+ type: "network",
12594
+ layout: "tiered",
12595
+ direction: "tb",
12596
+ spines: [],
12597
+ leaves: [],
12598
+ devices: [],
12599
+ links: [],
12600
+ groups: [],
12601
+ warnings: []
12602
+ };
12603
+ const deviceById = /* @__PURE__ */ new Map();
12604
+ const groupById = /* @__PURE__ */ new Map();
12605
+ const groupStack = [];
12606
+ let headerSeen = false;
12607
+ const rawLines = text2.split(/\r?\n/);
12608
+ const addDevice = (d, lineNo) => {
12609
+ if (deviceById.has(d.id)) {
12610
+ throw new NetworkParseError(`device id "${d.id}" already declared (line ${lineNo})`);
12611
+ }
12612
+ deviceById.set(d.id, d);
12613
+ ast.devices.push(d);
12614
+ const top = groupStack[groupStack.length - 1];
12615
+ if (top && !top.members.includes(d.id)) top.members.push(d.id);
12616
+ };
12617
+ const ensureDevice = (id, kind) => {
12618
+ let d = deviceById.get(id);
12619
+ if (!d) {
12620
+ d = { id, kind, groups: [] };
12621
+ deviceById.set(id, d);
12622
+ ast.devices.push(d);
12623
+ }
12624
+ return d;
12625
+ };
12626
+ const applyAttrs = (d, toks, start, lineNo) => {
12627
+ let i = start;
12628
+ while (i < toks.length) {
12629
+ const tk = toks[i];
12630
+ if (tk.str) {
12631
+ i++;
12632
+ continue;
12633
+ }
12634
+ const key = tk.value.endsWith(":") ? tk.value.slice(0, -1) : null;
12635
+ if (key === null) {
12636
+ i++;
12637
+ continue;
12638
+ }
12639
+ const val = toks[i + 1];
12640
+ i += 2;
12641
+ if (!val) continue;
12642
+ switch (key) {
12643
+ case "tier":
12644
+ if (TIERS.has(val.value)) d.tier = val.value;
12645
+ break;
12646
+ case "type":
12647
+ if (CAMERA_TYPES.has(val.value)) d.cameraType = val.value;
12648
+ break;
12649
+ case "ip":
12650
+ d.ip = val.value;
12651
+ break;
12652
+ case "model":
12653
+ d.model = val.value;
12654
+ break;
12655
+ case "count": {
12656
+ const n = Number(val.value);
12657
+ if (Number.isFinite(n)) d.count = n;
12658
+ break;
12659
+ }
12660
+ case "icon":
12661
+ d.icon = val.value;
12662
+ break;
12663
+ case "at": {
12664
+ const mm = /^(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)$/.exec(val.value);
12665
+ if (mm) d.at = { x: Number(mm[1]), y: Number(mm[2]) };
12666
+ break;
12667
+ }
12668
+ default:
12669
+ ast.warnings.push(`unknown attribute "${key}:" on device "${d.id}" (line ${lineNo})`);
12670
+ }
12671
+ }
12672
+ };
12673
+ const statements = [];
12674
+ rawLines.forEach((raw, i) => {
12675
+ for (const seg of splitStatements(stripComment2(raw))) statements.push({ text: seg, no: i + 1 });
12676
+ });
12677
+ for (const stmt of statements) {
12678
+ const lineNo = stmt.no;
12679
+ const toks = tokenize4(stmt.text);
12680
+ if (toks.length === 0) continue;
12681
+ const t0 = toks[0];
12682
+ if (!headerSeen) {
12683
+ if (!t0.str && /^(network|topology)$/i.test(t0.value)) {
12684
+ headerSeen = true;
12685
+ if (toks[1]?.str) ast.title = toks[1].value;
12686
+ continue;
12687
+ }
12688
+ throw new NetworkParseError(
12689
+ `network diagram must start with "network" or "topology" (line ${lineNo})`
12690
+ );
12691
+ }
12692
+ if (toks.length === 1 && t0.value === "}") {
12693
+ if (groupStack.length === 0) {
12694
+ throw new NetworkParseError(`unmatched "}" (line ${lineNo})`);
12695
+ }
12696
+ groupStack.pop();
12697
+ continue;
12698
+ }
12699
+ if (!t0.str && t0.value.endsWith(":")) {
12700
+ const key = t0.value.slice(0, -1).toLowerCase();
12701
+ const rest = toks.slice(1);
12702
+ switch (key) {
12703
+ case "layout": {
12704
+ const v = rest[0]?.value;
12705
+ if (v && LAYOUT_MODES.has(v)) ast.layout = v;
12706
+ else if (v) ast.warnings.push(`unknown layout "${v}" \u2014 using "tiered" (line ${lineNo})`);
12707
+ break;
12708
+ }
12709
+ case "direction": {
12710
+ const v = rest[0]?.value?.toLowerCase();
12711
+ if (v === "tb" || v === "lr") ast.direction = v;
12712
+ break;
12713
+ }
12714
+ case "title":
12715
+ if (rest[0]) ast.title = rest[0].value;
12716
+ break;
12717
+ case "spines":
12718
+ ast.spines.push(...rest.filter((r) => !r.str).map((r) => r.value));
12719
+ break;
12720
+ case "leaves":
12721
+ ast.leaves.push(...rest.filter((r) => !r.str).map((r) => r.value));
12722
+ break;
12723
+ case "legend":
12724
+ break;
12725
+ // parsed-and-ignored in v0.1
12726
+ default:
12727
+ ast.warnings.push(`unknown directive "${key}:" (line ${lineNo})`);
12728
+ }
12729
+ continue;
12730
+ }
12731
+ const ci = toks.findIndex((t) => !t.str && CONNECTORS.has(t.value));
12732
+ if (ci >= 0) {
12733
+ const fromT = toks[ci - 1];
12734
+ const toT = toks[ci + 1];
12735
+ if (!fromT || !toT || fromT.str || toT.str) {
12736
+ throw new NetworkParseError(`malformed link (line ${lineNo})`);
12737
+ }
12738
+ const conn = toks[ci].value;
12739
+ const link = {
12740
+ from: fromT.value,
12741
+ to: toT.value,
12742
+ directed: conn === "->",
12743
+ linkType: conn === "==" ? "lag" : "copper",
12744
+ line: lineNo
12745
+ };
12746
+ let si = ci + 2;
12747
+ if (toks[si] && !toks[si].str && toks[si].value === ":") si++;
12748
+ while (si < toks.length) {
12749
+ const tk = toks[si];
12750
+ if (tk.str) {
12751
+ link.label = tk.value;
12752
+ si++;
12753
+ continue;
12754
+ }
12755
+ const low = tk.value.toLowerCase();
12756
+ if (LINK_TYPE_KEYWORDS[low]) {
12757
+ link.linkType = LINK_TYPE_KEYWORDS[low];
12758
+ si++;
12759
+ continue;
12760
+ }
12761
+ if (low === "trunk" || low === "access") {
12762
+ link.mode = low;
12763
+ si++;
12764
+ continue;
12765
+ }
12766
+ if (SPEED_RE.test(tk.value)) {
12767
+ link.speed = tk.value;
12768
+ si++;
12769
+ continue;
12770
+ }
12771
+ if (low === "vlan:") {
12772
+ const v = toks[si + 1];
12773
+ if (v) {
12774
+ link.vlans = v.value.split(",").map((s) => Number(s.trim())).filter((n) => Number.isFinite(n));
12775
+ for (const vid of link.vlans) {
12776
+ if (vid < 1 || vid > 4094) {
12777
+ ast.warnings.push(`VLAN id ${vid} out of range 1\u20134094 (line ${lineNo})`);
12778
+ }
12779
+ }
12780
+ }
12781
+ si += 2;
12782
+ continue;
12783
+ }
12784
+ if (low === "port:") {
12785
+ const v = toks[si + 1];
12786
+ if (v) {
12787
+ const [near, far] = v.value.split(">");
12788
+ link.portNear = near;
12789
+ if (far) link.portFar = far;
12790
+ }
12791
+ si += 2;
12792
+ continue;
12793
+ }
12794
+ if (low === "ip:") {
12795
+ link.ip = toks[si + 1]?.value;
12796
+ si += 2;
12797
+ continue;
12798
+ }
12799
+ si++;
12800
+ }
12801
+ ast.links.push(link);
12802
+ continue;
12803
+ }
12804
+ if (!t0.str && GROUP_KINDS.has(t0.value) && toks[toks.length - 1].value === "{") {
12805
+ const idT = toks[1];
12806
+ if (!idT || idT.str) throw new NetworkParseError(`group needs an id (line ${lineNo})`);
12807
+ const kind = GROUP_KIND_ALIAS[t0.value] ?? t0.value;
12808
+ const label = toks[2]?.str ? toks[2].value : void 0;
12809
+ if (groupById.has(idT.value)) {
12810
+ throw new NetworkParseError(`group id "${idT.value}" already declared (line ${lineNo})`);
12811
+ }
12812
+ const parent = groupStack[groupStack.length - 1];
12813
+ const g = {
12814
+ id: idT.value,
12815
+ kind,
12816
+ label,
12817
+ members: [],
12818
+ children: [],
12819
+ parent: parent?.id,
12820
+ line: lineNo
12821
+ };
12822
+ if (parent) parent.children.push(g.id);
12823
+ groupById.set(g.id, g);
12824
+ ast.groups.push(g);
12825
+ groupStack.push(g);
12826
+ continue;
12827
+ }
12828
+ const canonKind = KIND_ALIASES[t0.value] ?? t0.value;
12829
+ if (!t0.str && DEVICE_KINDS.has(canonKind)) {
12830
+ const idT = toks[1];
12831
+ if (!idT || idT.str) throw new NetworkParseError(`device "${t0.value}" needs an id (line ${lineNo})`);
12832
+ let idx = 2;
12833
+ let label;
12834
+ if (toks[2]?.str) {
12835
+ label = toks[2].value;
12836
+ idx = 3;
12837
+ }
12838
+ const d = { id: idT.value, kind: canonKind, label, groups: groupStack.map((g) => g.id) };
12839
+ applyAttrs(d, toks, idx, lineNo);
12840
+ addDevice(d, lineNo);
12841
+ continue;
12842
+ }
12843
+ const sep = toks.findIndex((t) => !t.str && t.value === ":");
12844
+ if (sep > 0) {
12845
+ const ids = toks.slice(0, sep);
12846
+ const kindT = toks[sep + 1];
12847
+ if (kindT && !kindT.str && ids.every((t) => !t.str)) {
12848
+ const k = KIND_ALIASES[kindT.value] ?? kindT.value;
12849
+ if (!DEVICE_KINDS.has(k)) {
12850
+ const hint = nearestKind(kindT.value);
12851
+ throw new NetworkParseError(
12852
+ `unknown device kind "${kindT.value}"${hint ? ` \u2014 did you mean "${hint}"?` : ""} (line ${lineNo})`
12853
+ );
12854
+ }
12855
+ for (const idT of ids) {
12856
+ const d = { id: idT.value, kind: k, groups: groupStack.map((g) => g.id) };
12857
+ applyAttrs(d, toks, sep + 2, lineNo);
12858
+ addDevice(d, lineNo);
12859
+ }
12860
+ continue;
12861
+ }
12862
+ }
12863
+ if (toks.length === 1 && !t0.str && groupStack.length > 0) {
12864
+ const top = groupStack[groupStack.length - 1];
12865
+ if (!top.members.includes(t0.value)) top.members.push(t0.value);
12866
+ continue;
12867
+ }
12868
+ if (!t0.str && toks.length >= 2 && !toks[1].str) {
12869
+ const hint = nearestKind(t0.value);
12870
+ throw new NetworkParseError(
12871
+ `unknown device kind "${t0.value}"${hint ? ` \u2014 did you mean "${hint}"?` : ""} (line ${lineNo})`
12872
+ );
12873
+ }
12874
+ throw new NetworkParseError(`cannot parse line ${lineNo}: "${stmt.text.trim()}"`);
12875
+ }
12876
+ if (groupStack.length > 0) {
12877
+ throw new NetworkParseError(`unclosed group "${groupStack[groupStack.length - 1].id}"`);
12878
+ }
12879
+ for (const id of ast.spines) ensureDevice(id, "l3switch");
12880
+ for (const id of ast.leaves) ensureDevice(id, "switch");
12881
+ for (const link of ast.links) {
12882
+ for (const end of [link.from, link.to]) {
12883
+ if (!deviceById.has(end)) {
12884
+ throw new NetworkParseError(
12885
+ `link references undeclared device "${end}" (line ${link.line ?? "?"})`
12886
+ );
12887
+ }
12888
+ }
12889
+ if (link.mode === "trunk") {
12890
+ const a = deviceById.get(link.from), b = deviceById.get(link.to);
12891
+ if (!SWITCH_CLASS.has(a.kind) && !SWITCH_CLASS.has(b.kind)) {
12892
+ ast.warnings.push(`trunk link ${link.from}\u2013${link.to} connects no switch-class device (line ${link.line ?? "?"})`);
12893
+ }
12894
+ }
12895
+ }
12896
+ for (const g of ast.groups) {
12897
+ if (g.kind !== "subnet" || !g.label) continue;
12898
+ for (const memberId of g.members) {
12899
+ const d = deviceById.get(memberId);
12900
+ if (!d?.ip) continue;
12901
+ const inside = ipInCidr(d.ip, g.label);
12902
+ if (inside === false) {
12903
+ throw new NetworkParseError(
12904
+ `${d.id} ip ${d.ip} is not inside subnet ${g.label} (line ${d.line ?? g.line ?? "?"})`
12905
+ );
12906
+ }
12907
+ }
12908
+ }
12909
+ return ast;
12910
+ }
12911
+
12912
+ // src/diagrams/network/symbols.ts
12913
+ var BODY = "sx-net-body";
12914
+ var DET = "sx-net-detail";
12915
+ var GLY = "sx-net-glyph";
12916
+ var GLYL = "sx-net-glyph-line";
12917
+ var ITX = "sx-net-icontext";
12918
+ var CLOUD = "sx-net-cloud-body";
12919
+ var CTX = "sx-net-cloudtext";
12920
+ var r2 = (n) => Math.round(n * 100) / 100;
12921
+ function arrow(x1, y1, x2, y2, hs = 4) {
12922
+ const ang = Math.atan2(y2 - y1, x2 - x1);
12923
+ const a1 = ang + Math.PI - 0.5;
12924
+ const a2 = ang + Math.PI + 0.5;
12925
+ const p1x = r2(x2 + hs * Math.cos(a1)), p1y = r2(y2 + hs * Math.sin(a1));
12926
+ const p2x = r2(x2 + hs * Math.cos(a2)), p2y = r2(y2 + hs * Math.sin(a2));
12927
+ return line({ class: GLYL, x1: r2(x1), y1: r2(y1), x2: r2(x2), y2: r2(y2) }) + polygon({ class: GLY, points: `${r2(x2)},${r2(y2)} ${p1x},${p1y} ${p2x},${p2y}` });
12928
+ }
12929
+ function router(b) {
12930
+ const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
12931
+ const rw = b.w * 0.78, rh = b.h * 0.62;
12932
+ const x = cx - rw / 2, y = cy - rh / 2;
12933
+ const parts = [
12934
+ rect({ class: BODY, x: r2(x), y: r2(y), width: r2(rw), height: r2(rh), rx: rh / 2, ry: rh / 2 })
12935
+ ];
12936
+ parts.push(arrow(cx - 2, cy - rh * 0.18, x + rw - 5, cy - rh * 0.18));
12937
+ parts.push(arrow(cx + 2, cy + rh * 0.18, x + 5, cy + rh * 0.18));
12938
+ parts.push(arrow(cx + rw * 0.18, cy + 2, cx + rw * 0.18, y + rh - 4));
12939
+ parts.push(arrow(cx - rw * 0.18, cy - 2, cx - rw * 0.18, y + 4));
12940
+ return group({}, parts);
12941
+ }
12942
+ function switchBox(b, glyph) {
12943
+ const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
12944
+ const rw = b.w * 0.86, rh = b.h * 0.5;
12945
+ const x = cx - rw / 2, y = cy - rh / 2;
12946
+ const parts = [
12947
+ rect({ class: BODY, x: r2(x), y: r2(y), width: r2(rw), height: r2(rh), rx: 3, ry: 3 })
12948
+ ];
12949
+ if (glyph === "circular") {
12950
+ parts.push(arrow(x + rw * 0.25, cy - rh * 0.16, x + rw * 0.75, cy - rh * 0.16));
12951
+ parts.push(arrow(x + rw * 0.75, cy + rh * 0.16, x + rw * 0.25, cy + rh * 0.16));
12952
+ parts.push(arrow(x + rw * 0.62, cy - rh * 0.16, x + rw * 0.78, cy - rh * 0.16));
12953
+ } else {
12954
+ parts.push(arrow(x + rw * 0.2, cy - rh * 0.18, x + rw * 0.8, cy - rh * 0.18));
12955
+ parts.push(arrow(x + rw * 0.8, cy + rh * 0.18, x + rw * 0.2, cy + rh * 0.18));
12956
+ }
12957
+ return group({}, parts);
12958
+ }
12959
+ function poeSwitch(b) {
12960
+ const cx = b.x + b.w / 2;
12961
+ return group({}, [switchBox(b, "straight"), text({ class: ITX, x: r2(cx), y: r2(b.y + b.h * 0.78 + 8), "text-anchor": "middle" }, "PoE")]);
12962
+ }
12963
+ function firewall(b) {
12964
+ const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
12965
+ const rw = b.w * 0.78, rh = b.h * 0.62;
12966
+ const x = cx - rw / 2, y = cy - rh / 2;
12967
+ const parts = [rect({ class: BODY, x: r2(x), y: r2(y), width: r2(rw), height: r2(rh), rx: 2, ry: 2 })];
12968
+ const rows = 3;
12969
+ const rhh = rh / rows;
12970
+ for (let i = 1; i < rows; i++) parts.push(line({ class: DET, x1: r2(x), y1: r2(y + i * rhh), x2: r2(x + rw), y2: r2(y + i * rhh) }));
12971
+ for (let i = 0; i < rows; i++) {
12972
+ const yy = y + i * rhh;
12973
+ const offset = i % 2 === 0 ? rw / 3 : rw / 6;
12974
+ for (let vx = x + offset; vx < x + rw - 2; vx += rw / 3) {
12975
+ parts.push(line({ class: DET, x1: r2(vx), y1: r2(yy), x2: r2(vx), y2: r2(yy + rhh) }));
12976
+ }
12977
+ }
12978
+ return group({}, parts);
12979
+ }
12980
+ function brickless(b, glyph) {
12981
+ const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
12982
+ const rw = b.w * 0.78, rh = b.h * 0.6;
12983
+ const x = cx - rw / 2, y = cy - rh / 2;
12984
+ return group({}, [
12985
+ rect({ class: BODY, x: r2(x), y: r2(y), width: r2(rw), height: r2(rh), rx: 3, ry: 3 }),
12986
+ text({ class: ITX, x: r2(cx), y: r2(cy + 4), "text-anchor": "middle" }, glyph)
12987
+ ]);
12988
+ }
12989
+ function accessPoint(b) {
12990
+ const cx = b.x + b.w / 2, cy = b.y + b.h * 0.62;
12991
+ const rw = b.w * 0.5, rh = b.h * 0.34;
12992
+ const parts = [
12993
+ rect({ class: BODY, x: r2(cx - rw / 2), y: r2(cy - rh / 2), width: r2(rw), height: r2(rh), rx: rh / 2, ry: rh / 2 })
12994
+ ];
12995
+ for (let i = 1; i <= 2; i++) {
12996
+ const rr = i * b.w * 0.16;
12997
+ parts.push(path({ class: GLYL, d: `M ${r2(cx - rr)} ${r2(cy - rh / 2 - 2)} A ${r2(rr)} ${r2(rr)} 0 0 1 ${r2(cx + rr)} ${r2(cy - rh / 2 - 2)}` }));
12998
+ }
12999
+ return group({}, parts);
13000
+ }
13001
+ function server(b) {
13002
+ const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
13003
+ const rw = b.w * 0.46, rh = b.h * 0.82;
13004
+ const x = cx - rw / 2, y = cy - rh / 2;
13005
+ const parts = [rect({ class: BODY, x: r2(x), y: r2(y), width: r2(rw), height: r2(rh), rx: 2, ry: 2 })];
13006
+ for (let i = 1; i <= 3; i++) parts.push(line({ class: DET, x1: r2(x + 4), y1: r2(y + i * (rh / 4)), x2: r2(x + rw - 4), y2: r2(y + i * (rh / 4)) }));
13007
+ parts.push(circle({ class: GLY, cx: r2(x + rw - 7), cy: r2(y + 6), r: 1.6 }));
13008
+ return group({}, parts);
13009
+ }
13010
+ function serverFarm(b, d) {
13011
+ const parts = [];
13012
+ const off = Math.min(6, b.w * 0.1);
13013
+ for (let i = 2; i >= 0; i--) {
13014
+ parts.push(server({ x: b.x + i * off, y: b.y - i * off * 0.5, w: b.w - 2 * off, h: b.h - off }));
13015
+ }
13016
+ if (d.count) parts.push(text({ class: ITX, x: r2(b.x + b.w - 6), y: r2(b.y + b.h - 2), "text-anchor": "end" }, `\xD7${d.count}`));
13017
+ return group({}, parts);
13018
+ }
13019
+ function pc(b) {
13020
+ const cx = b.x + b.w / 2;
13021
+ const sw = b.w * 0.7, sh = b.h * 0.52;
13022
+ const sx = cx - sw / 2, sy = b.y + b.h * 0.12;
13023
+ return group({}, [
13024
+ rect({ class: BODY, x: r2(sx), y: r2(sy), width: r2(sw), height: r2(sh), rx: 2, ry: 2 }),
13025
+ rect({ class: DET, x: r2(sx + 3), y: r2(sy + 3), width: r2(sw - 6), height: r2(sh - 6), rx: 1, ry: 1, fill: "none" }),
13026
+ line({ class: DET, x1: r2(cx), y1: r2(sy + sh), x2: r2(cx), y2: r2(sy + sh + b.h * 0.16) }),
13027
+ line({ class: DET, x1: r2(cx - sw * 0.28), y1: r2(b.y + b.h - 2), x2: r2(cx + sw * 0.28), y2: r2(b.y + b.h - 2) })
13028
+ ]);
13029
+ }
13030
+ function laptop(b) {
13031
+ const cx = b.x + b.w / 2;
13032
+ const sw = b.w * 0.6, sh = b.h * 0.46;
13033
+ const sx = cx - sw / 2, sy = b.y + b.h * 0.16;
13034
+ return group({}, [
13035
+ rect({ class: BODY, x: r2(sx), y: r2(sy), width: r2(sw), height: r2(sh), rx: 2, ry: 2 }),
13036
+ polygon({ class: BODY, points: `${r2(sx - sw * 0.18)},${r2(sy + sh + b.h * 0.18)} ${r2(sx + sw + sw * 0.18)},${r2(sy + sh + b.h * 0.18)} ${r2(sx + sw)},${r2(sy + sh)} ${r2(sx)},${r2(sy + sh)}` })
13037
+ ]);
13038
+ }
13039
+ function mobile(b) {
13040
+ const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
13041
+ const rw = b.w * 0.32, rh = b.h * 0.82;
13042
+ return group({}, [
13043
+ rect({ class: BODY, x: r2(cx - rw / 2), y: r2(cy - rh / 2), width: r2(rw), height: r2(rh), rx: 3, ry: 3 }),
13044
+ line({ class: DET, x1: r2(cx - rw * 0.2), y1: r2(cy + rh / 2 - 4), x2: r2(cx + rw * 0.2), y2: r2(cy + rh / 2 - 4) })
13045
+ ]);
13046
+ }
13047
+ function ipphone(b) {
13048
+ const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
13049
+ const rw = b.w * 0.6, rh = b.h * 0.6;
13050
+ const x = cx - rw / 2, y = cy - rh / 2;
13051
+ return group({}, [
13052
+ rect({ class: BODY, x: r2(x), y: r2(y), width: r2(rw), height: r2(rh * 0.7), rx: 2, ry: 2 }),
13053
+ path({ class: GLYL, d: `M ${r2(x + rw * 0.2)} ${r2(y + 5)} q ${r2(rw * 0.3)} ${r2(rh * 0.5)} ${r2(rw * 0.6)} 0` })
13054
+ ]);
13055
+ }
13056
+ function printer(b) {
13057
+ const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
13058
+ const rw = b.w * 0.7, rh = b.h * 0.42;
13059
+ const x = cx - rw / 2, y = cy - rh * 0.1;
13060
+ return group({}, [
13061
+ rect({ class: DET, x: r2(x + rw * 0.18), y: r2(y - rh * 0.7), width: r2(rw * 0.64), height: r2(rh * 0.7), fill: "none" }),
13062
+ rect({ class: BODY, x: r2(x), y: r2(y), width: r2(rw), height: r2(rh), rx: 2, ry: 2 }),
13063
+ rect({ class: DET, x: r2(x + rw * 0.18), y: r2(y + rh - 2), width: r2(rw * 0.64), height: r2(rh * 0.5), fill: "none" }),
13064
+ circle({ class: GLY, cx: r2(x + rw - 7), cy: r2(y + rh / 2), r: 1.6 })
13065
+ ]);
13066
+ }
13067
+ function storage(b) {
13068
+ const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
13069
+ const rw = b.w * 0.5, rh = b.h * 0.78;
13070
+ const x = cx - rw / 2, y = cy - rh / 2;
13071
+ const ry = rw * 0.18;
13072
+ const parts = [
13073
+ path({ class: BODY, d: `M ${r2(x)} ${r2(y + ry)} A ${r2(rw / 2)} ${r2(ry)} 0 0 1 ${r2(x + rw)} ${r2(y + ry)} L ${r2(x + rw)} ${r2(y + rh - ry)} A ${r2(rw / 2)} ${r2(ry)} 0 0 1 ${r2(x)} ${r2(y + rh - ry)} Z` }),
13074
+ path({ class: DET, fill: "none", d: `M ${r2(x)} ${r2(y + ry)} A ${r2(rw / 2)} ${r2(ry)} 0 0 0 ${r2(x + rw)} ${r2(y + ry)}` })
13075
+ ];
13076
+ return group({}, parts);
13077
+ }
13078
+ function camera(b, d) {
13079
+ const t = d.cameraType ?? "fixed";
13080
+ const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
13081
+ if (t === "dome" || t === "ptz") {
13082
+ const rr = b.w * (t === "ptz" ? 0.34 : 0.28);
13083
+ const parts = [
13084
+ path({ class: BODY, d: `M ${r2(cx - rr)} ${r2(cy + rr * 0.2)} A ${r2(rr)} ${r2(rr)} 0 0 1 ${r2(cx + rr)} ${r2(cy + rr * 0.2)} Z` }),
13085
+ rect({ class: BODY, x: r2(cx - rr * 1.15), y: r2(cy + rr * 0.2), width: r2(rr * 2.3), height: r2(b.h * 0.12), rx: 1, ry: 1 }),
13086
+ circle({ class: GLY, cx: r2(cx), cy: r2(cy - rr * 0.15), r: r2(rr * 0.3) })
13087
+ ];
13088
+ return group({}, parts);
13089
+ }
13090
+ if (t === "bullet") {
13091
+ const bw2 = b.w * 0.6, bh2 = b.h * 0.34;
13092
+ const x2 = cx - bw2 / 2, y2 = cy - bh2 / 2;
13093
+ return group({}, [
13094
+ rect({ class: BODY, x: r2(x2), y: r2(y2), width: r2(bw2), height: r2(bh2), rx: bh2 / 2, ry: bh2 / 2 }),
13095
+ circle({ class: GLY, cx: r2(x2 + bw2 - 4), cy: r2(cy), r: r2(bh2 * 0.28) }),
13096
+ line({ class: DET, x1: r2(cx), y1: r2(y2 + bh2), x2: r2(cx), y2: r2(y2 + bh2 + b.h * 0.16) })
13097
+ ]);
13098
+ }
13099
+ const bw = b.w * 0.5, bh = b.h * 0.42;
13100
+ const x = cx - bw / 2, y = cy - bh / 2;
13101
+ return group({}, [
13102
+ rect({ class: BODY, x: r2(x), y: r2(y), width: r2(bw), height: r2(bh), rx: 2, ry: 2 }),
13103
+ circle({ class: GLY, cx: r2(cx), cy: r2(cy), r: r2(bh * 0.3) })
13104
+ ]);
13105
+ }
13106
+ function recorder(b, label) {
13107
+ const cx = b.x + b.w / 2, cy = b.y + b.h / 2;
13108
+ const rw = b.w * 0.82, rh = b.h * 0.46;
13109
+ const x = cx - rw / 2, y = cy - rh / 2;
13110
+ return group({}, [
13111
+ rect({ class: BODY, x: r2(x), y: r2(y), width: r2(rw), height: r2(rh), rx: 2, ry: 2 }),
13112
+ circle({ class: GLY, cx: r2(x + rw * 0.22), cy: r2(y + rh * 0.32), r: 2 }),
13113
+ circle({ class: GLY, cx: r2(x + rw * 0.4), cy: r2(y + rh * 0.32), r: 2 }),
13114
+ text({ class: ITX, x: r2(cx), y: r2(y + rh * 0.85), "text-anchor": "middle" }, label)
13115
+ ]);
13116
+ }
13117
+ function monitor(b, grid) {
13118
+ const cx = b.x + b.w / 2;
13119
+ const sw = b.w * 0.84, sh = b.h * 0.56;
13120
+ const sx = cx - sw / 2, sy = b.y + b.h * 0.1;
13121
+ const parts = [rect({ class: BODY, x: r2(sx), y: r2(sy), width: r2(sw), height: r2(sh), rx: 2, ry: 2 })];
13122
+ if (grid) {
13123
+ parts.push(line({ class: DET, x1: r2(cx), y1: r2(sy), x2: r2(cx), y2: r2(sy + sh) }));
13124
+ parts.push(line({ class: DET, x1: r2(sx), y1: r2(sy + sh / 2), x2: r2(sx + sw), y2: r2(sy + sh / 2) }));
13125
+ }
13126
+ parts.push(line({ class: DET, x1: r2(cx), y1: r2(sy + sh), x2: r2(cx), y2: r2(sy + sh + b.h * 0.14) }));
13127
+ parts.push(line({ class: DET, x1: r2(cx - sw * 0.2), y1: r2(b.y + b.h - 2), x2: r2(cx + sw * 0.2), y2: r2(b.y + b.h - 2) }));
13128
+ return group({}, parts);
13129
+ }
13130
+ function cloud(b, label) {
13131
+ const mx = (dx) => r2(b.x + dx / 200 * b.w);
13132
+ const my = (dy) => r2(b.y + dy / 110 * b.h);
13133
+ const d = `M ${mx(50)} ${my(96)} C ${mx(24)} ${my(96)} ${mx(24)} ${my(62)} ${mx(50)} ${my(58)} C ${mx(50)} ${my(30)} ${mx(94)} ${my(26)} ${mx(100)} ${my(50)} C ${mx(106)} ${my(26)} ${mx(150)} ${my(30)} ${mx(150)} ${my(58)} C ${mx(176)} ${my(62)} ${mx(176)} ${my(96)} ${mx(150)} ${my(96)} Z`;
13134
+ return group({}, [
13135
+ path({ class: CLOUD, d }),
13136
+ text({ class: CTX, x: r2(b.x + b.w / 2), y: my(80), "text-anchor": "middle" }, label)
13137
+ ]);
13138
+ }
13139
+ function busBar(b) {
13140
+ const y = b.y + b.h / 2;
13141
+ return line({ class: "sx-net-bus", x1: r2(b.x), y1: r2(y), x2: r2(b.x + b.w), y2: r2(y) });
13142
+ }
13143
+ function iconSize(kind) {
13144
+ if (kind === "internet" || kind === "wan" || kind === "cloud" || kind === "pstn") return { w: 110, h: 64 };
13145
+ if (kind === "lan") return { w: 150, h: 24 };
13146
+ if (kind === "serverfarm") return { w: 74, h: 58 };
13147
+ return { w: 64, h: 48 };
13148
+ }
13149
+ function drawDeviceIcon(d, b) {
13150
+ switch (d.kind) {
13151
+ case "router":
13152
+ return router(b);
13153
+ case "gateway":
13154
+ return group({}, [router(b), text({ class: ITX, x: r2(b.x + b.w / 2), y: r2(b.y + b.h - 1), "text-anchor": "middle" }, "GW")]);
13155
+ case "vpngw":
13156
+ return group({}, [router(b), text({ class: ITX, x: r2(b.x + b.w / 2), y: r2(b.y + b.h - 1), "text-anchor": "middle" }, "VPN")]);
13157
+ case "switch":
13158
+ return switchBox(b, "straight");
13159
+ case "l3switch":
13160
+ return switchBox(b, "circular");
13161
+ case "poeswitch":
13162
+ return poeSwitch(b);
13163
+ case "firewall":
13164
+ return firewall(b);
13165
+ case "loadbalancer":
13166
+ return brickless(b, "LB");
13167
+ case "ids":
13168
+ return brickless(b, "IDS");
13169
+ case "proxy":
13170
+ return brickless(b, "PXY");
13171
+ case "modem":
13172
+ return brickless(b, "MDM");
13173
+ case "wlc":
13174
+ return brickless(b, "WLC");
13175
+ case "ap":
13176
+ return accessPoint(b);
13177
+ case "server":
13178
+ return server(b);
13179
+ case "serverfarm":
13180
+ return serverFarm(b, d);
13181
+ case "pc":
13182
+ return pc(b);
13183
+ case "laptop":
13184
+ return laptop(b);
13185
+ case "mobile":
13186
+ return mobile(b);
13187
+ case "ipphone":
13188
+ return ipphone(b);
13189
+ case "printer":
13190
+ return printer(b);
13191
+ case "storage":
13192
+ return storage(b);
13193
+ case "camera":
13194
+ return camera(b, d);
13195
+ case "nvr":
13196
+ return recorder(b, "NVR");
13197
+ case "dvr":
13198
+ return recorder(b, "DVR");
13199
+ case "encoder":
13200
+ return brickless(b, "ENC");
13201
+ case "monitor":
13202
+ return monitor(b, d.icon === "videowall");
13203
+ case "internet":
13204
+ return cloud(b, d.label ?? "Internet");
13205
+ case "wan":
13206
+ return cloud(b, d.label ?? "WAN");
13207
+ case "pstn":
13208
+ return cloud(b, d.label ?? "PSTN");
13209
+ case "cloud":
13210
+ return cloud(b, d.label ?? "Cloud");
13211
+ case "lan":
13212
+ return busBar(b);
13213
+ default:
13214
+ return brickless(b, "?");
13215
+ }
13216
+ }
13217
+ function isCloudKind(kind) {
13218
+ return kind === "internet" || kind === "wan" || kind === "pstn" || kind === "cloud";
13219
+ }
13220
+
13221
+ // src/diagrams/network/layout.ts
13222
+ var NET_CONST = {
13223
+ DEVICE_W: 64,
13224
+ DEVICE_H: 48,
13225
+ TIER_BAND_GAP: 104,
13226
+ SIBLING_GAP: 44,
13227
+ RING_RADIUS_MIN: 120,
13228
+ STAR_HUB_GAP: 130,
13229
+ SPINE_LEAF_GAP: 140,
13230
+ LABEL_GAP: 6,
13231
+ LABEL_H: 15,
13232
+ SUBLABEL_H: 12,
13233
+ GROUP_PAD: 18,
13234
+ GROUP_LABEL_INSET: 12,
13235
+ GROUP_HEADER: 16,
13236
+ CHAR_W: 6.3,
13237
+ PAD: 30
13238
+ };
13239
+ var ENDPOINT_KINDS = /* @__PURE__ */ new Set([
13240
+ "pc",
13241
+ "laptop",
13242
+ "mobile",
13243
+ "ipphone",
13244
+ "printer",
13245
+ "camera",
13246
+ "server",
13247
+ "serverfarm",
13248
+ "storage",
13249
+ "monitor",
13250
+ "nvr",
13251
+ "dvr"
13252
+ ]);
13253
+ function labelExtra(d) {
13254
+ if (isCloudKind(d.kind)) return 0;
13255
+ let h = NET_CONST.LABEL_GAP + NET_CONST.LABEL_H;
13256
+ if (d.ip || d.model) h += NET_CONST.SUBLABEL_H;
13257
+ return h;
13258
+ }
13259
+ function labelText(d) {
13260
+ return d.label ?? d.id;
13261
+ }
13262
+ function deviceFootprint(d) {
13263
+ return iconSize(d.kind);
13264
+ }
13265
+ function effBox(box) {
13266
+ const labelW = Math.max(box.w, labelText(box.device).length * NET_CONST.CHAR_W + 6);
13267
+ const half = labelW / 2;
13268
+ return {
13269
+ left: box.cx - half,
13270
+ top: box.y,
13271
+ right: box.cx + half,
13272
+ bottom: box.y + box.h + labelExtra(box.device)
13273
+ };
13274
+ }
13275
+ function edgePoint(box, tx, ty) {
13276
+ const dx = tx - box.cx;
13277
+ const dy = ty - box.cy;
13278
+ if (dx === 0 && dy === 0) return { x: box.cx, y: box.cy };
13279
+ const hw = box.w / 2;
13280
+ const hh = box.h / 2;
13281
+ const sx = dx !== 0 ? hw / Math.abs(dx) : Infinity;
13282
+ const sy = dy !== 0 ? hh / Math.abs(dy) : Infinity;
13283
+ const s = Math.min(sx, sy);
13284
+ return { x: box.cx + dx * s, y: box.cy + dy * s };
13285
+ }
13286
+ function placeBanded(ast, ranks) {
13287
+ const lr = ast.direction === "lr";
13288
+ const byRank = /* @__PURE__ */ new Map();
13289
+ for (const d of ast.devices) {
13290
+ const r = ranks.get(d.id) ?? 0;
13291
+ if (!byRank.has(r)) byRank.set(r, []);
13292
+ byRank.get(r).push(d);
13293
+ }
13294
+ const rankValues = [...byRank.keys()].sort((a, b) => a - b);
13295
+ const pos = /* @__PURE__ */ new Map();
13296
+ let maxRowSpan = 0;
13297
+ const rowSpans = /* @__PURE__ */ new Map();
13298
+ for (const r of rankValues) {
13299
+ const devs = byRank.get(r);
13300
+ let span = 0;
13301
+ devs.forEach((d, i) => {
13302
+ const fp = deviceFootprint(d);
13303
+ const cross = lr ? fp.h + labelExtra(d) : Math.max(fp.w, labelText(d).length * NET_CONST.CHAR_W + 6);
13304
+ span += cross + (i > 0 ? NET_CONST.SIBLING_GAP : 0);
13305
+ });
13306
+ rowSpans.set(r, span);
13307
+ maxRowSpan = Math.max(maxRowSpan, span);
13308
+ }
13309
+ rankValues.forEach((r, rowIdx) => {
13310
+ const devs = byRank.get(r);
13311
+ const along = rowIdx * NET_CONST.TIER_BAND_GAP;
13312
+ let cursor = (maxRowSpan - rowSpans.get(r)) / 2;
13313
+ for (const d of devs) {
13314
+ const fp = deviceFootprint(d);
13315
+ const cross = lr ? fp.h + labelExtra(d) : Math.max(fp.w, labelText(d).length * NET_CONST.CHAR_W + 6);
13316
+ const center = cursor + cross / 2;
13317
+ if (lr) pos.set(d.id, { x: along, y: center });
13318
+ else pos.set(d.id, { x: center, y: along });
13319
+ cursor += cross + NET_CONST.SIBLING_GAP;
13320
+ }
13321
+ });
13322
+ return pos;
13323
+ }
13324
+ function adjacency(devices, links) {
13325
+ const adj = /* @__PURE__ */ new Map();
13326
+ for (const d of devices) adj.set(d.id, /* @__PURE__ */ new Set());
13327
+ for (const l of links) {
13328
+ adj.get(l.from)?.add(l.to);
13329
+ adj.get(l.to)?.add(l.from);
13330
+ }
13331
+ return adj;
13332
+ }
13333
+ function tieredRanks(ast, links) {
13334
+ const TIER_RANK = { edge: 1, core: 2, distribution: 3, access: 4 };
13335
+ const rank = /* @__PURE__ */ new Map();
13336
+ for (const d of ast.devices) {
13337
+ if (isCloudKind(d.kind)) rank.set(d.id, 0);
13338
+ else if (d.tier) rank.set(d.id, TIER_RANK[d.tier]);
13339
+ }
13340
+ const adj = adjacency(ast.devices, links);
13341
+ for (let pass = 0; pass < ast.devices.length + 4; pass++) {
13342
+ let changed = false;
13343
+ for (const d of ast.devices) {
13344
+ if (rank.has(d.id)) continue;
13345
+ const known = [];
13346
+ for (const n of adj.get(d.id) ?? []) {
13347
+ const r = rank.get(n);
13348
+ if (r !== void 0) known.push(r);
13349
+ }
13350
+ if (known.length === 0) continue;
13351
+ const next = ENDPOINT_KINDS.has(d.kind) ? Math.max(...known) + 1 : Math.min(...known) + 1;
13352
+ rank.set(d.id, next);
13353
+ changed = true;
13354
+ }
13355
+ if (!changed) break;
13356
+ }
13357
+ const maxR = Math.max(0, ...[...rank.values()]);
13358
+ for (const d of ast.devices) {
13359
+ if (!rank.has(d.id)) rank.set(d.id, ENDPOINT_KINDS.has(d.kind) ? maxR + 1 : 2);
13360
+ }
13361
+ return rank;
13362
+ }
13363
+ function treeRanks(ast, links) {
13364
+ const adj = adjacency(ast.devices, links);
13365
+ const rank = /* @__PURE__ */ new Map();
13366
+ let root = ast.devices.find((d) => isCloudKind(d.kind));
13367
+ if (!root) {
13368
+ let best = -1;
13369
+ for (const d of ast.devices) {
13370
+ const deg = adj.get(d.id)?.size ?? 0;
13371
+ if (deg > best) {
13372
+ best = deg;
13373
+ root = d;
13374
+ }
13375
+ }
13376
+ }
13377
+ if (!root) return rank;
13378
+ const queue = [root.id];
13379
+ rank.set(root.id, 0);
13380
+ while (queue.length) {
13381
+ const id = queue.shift();
13382
+ const r = rank.get(id);
13383
+ for (const n of adj.get(id) ?? []) {
13384
+ if (!rank.has(n)) {
13385
+ rank.set(n, r + 1);
13386
+ queue.push(n);
13387
+ }
13388
+ }
13389
+ }
13390
+ for (const d of ast.devices) if (!rank.has(d.id)) rank.set(d.id, 0);
13391
+ return rank;
13392
+ }
13393
+ function placeCircle(ast, radiusBase) {
13394
+ const pos = /* @__PURE__ */ new Map();
13395
+ const n = ast.devices.length;
13396
+ const radius = Math.max(radiusBase, n * 56 / (2 * Math.PI));
13397
+ ast.devices.forEach((d, i) => {
13398
+ const ang = i / Math.max(1, n) * 2 * Math.PI - Math.PI / 2;
13399
+ pos.set(d.id, { x: radius * Math.cos(ang), y: radius * Math.sin(ang) });
13400
+ });
13401
+ return pos;
13402
+ }
13403
+ function placeStar(ast, links) {
13404
+ const adj = adjacency(ast.devices, links);
13405
+ let hub = ast.devices[0];
13406
+ let best = -1;
13407
+ for (const d of ast.devices) {
13408
+ const deg = adj.get(d.id)?.size ?? 0;
13409
+ if (deg > best) {
13410
+ best = deg;
13411
+ hub = d;
13412
+ }
13413
+ }
13414
+ const pos = /* @__PURE__ */ new Map();
13415
+ if (!hub) return pos;
13416
+ pos.set(hub.id, { x: 0, y: 0 });
13417
+ const spokes = ast.devices.filter((d) => d.id !== hub.id);
13418
+ const radius = Math.max(NET_CONST.STAR_HUB_GAP, spokes.length * 50 / (2 * Math.PI));
13419
+ spokes.forEach((d, i) => {
13420
+ const ang = i / Math.max(1, spokes.length) * 2 * Math.PI - Math.PI / 2;
13421
+ pos.set(d.id, { x: radius * Math.cos(ang), y: radius * Math.sin(ang) });
13422
+ });
13423
+ return pos;
13424
+ }
13425
+ function placeBus(ast) {
13426
+ const pos = /* @__PURE__ */ new Map();
13427
+ let cursor = 0;
13428
+ for (const d of ast.devices) {
13429
+ const fp = deviceFootprint(d);
13430
+ pos.set(d.id, { x: cursor + fp.w / 2, y: 0 });
13431
+ cursor += fp.w + NET_CONST.SIBLING_GAP;
13432
+ }
13433
+ return pos;
13434
+ }
13435
+ function placeSpineLeaf(ast, links) {
13436
+ const pos = /* @__PURE__ */ new Map();
13437
+ const spineSet = new Set(ast.spines);
13438
+ const leafSet = new Set(ast.leaves);
13439
+ const rowGap = NET_CONST.SPINE_LEAF_GAP;
13440
+ const step = NET_CONST.DEVICE_W + NET_CONST.SIBLING_GAP;
13441
+ const centerRow = (ids, y) => {
13442
+ const span = (ids.length - 1) * step;
13443
+ ids.forEach((id, i) => pos.set(id, { x: i * step - span / 2, y }));
13444
+ };
13445
+ centerRow(ast.spines, 0);
13446
+ centerRow(ast.leaves, rowGap);
13447
+ const adj = adjacency(ast.devices, links);
13448
+ const hosts = ast.devices.filter((d) => !spineSet.has(d.id) && !leafSet.has(d.id));
13449
+ let seq = 0;
13450
+ for (const h of hosts) {
13451
+ let anchorX;
13452
+ for (const n of adj.get(h.id) ?? []) {
13453
+ if (leafSet.has(n)) {
13454
+ anchorX = pos.get(n)?.x;
13455
+ break;
13456
+ }
13457
+ }
13458
+ pos.set(h.id, { x: anchorX ?? seq++ * step, y: rowGap * 2 });
13459
+ }
13460
+ return pos;
13461
+ }
13462
+ function placeManual(ast) {
13463
+ const pos = /* @__PURE__ */ new Map();
13464
+ let fallback = 0;
13465
+ for (const d of ast.devices) {
13466
+ if (d.at) pos.set(d.id, { x: d.at.x, y: d.at.y });
13467
+ else {
13468
+ pos.set(d.id, { x: fallback * 120, y: 0 });
13469
+ fallback++;
13470
+ }
13471
+ }
13472
+ return pos;
13473
+ }
13474
+ function withAutoLinks(ast) {
13475
+ if (ast.layout !== "spine-leaf" || ast.spines.length === 0 || ast.leaves.length === 0) {
13476
+ return ast.links;
13477
+ }
13478
+ const have = new Set(ast.links.map((l) => [l.from, l.to].sort().join("\u2194")));
13479
+ const extra = [];
13480
+ for (const s of ast.spines) {
13481
+ for (const lf of ast.leaves) {
13482
+ const key = [s, lf].sort().join("\u2194");
13483
+ if (!have.has(key)) {
13484
+ extra.push({ from: s, to: lf, directed: false, linkType: "copper", auto: true });
13485
+ have.add(key);
13486
+ }
13487
+ }
13488
+ }
13489
+ return [...ast.links, ...extra];
13490
+ }
13491
+ function classify(ast) {
13492
+ if (ast.devices.length === 2 && ast.links.length === 1) return "point-to-point";
13493
+ switch (ast.layout) {
13494
+ case "star":
13495
+ return "star";
13496
+ case "ring":
13497
+ return "ring";
13498
+ case "bus":
13499
+ return "bus";
13500
+ case "mesh":
13501
+ return "mesh";
13502
+ case "spine-leaf":
13503
+ return "spine-leaf";
13504
+ case "tree":
13505
+ return "tree";
13506
+ case "tiered":
13507
+ return ast.devices.some((d) => d.tier) ? "hierarchical" : "tree";
13508
+ default:
13509
+ return "general";
13510
+ }
13511
+ }
13512
+ function layoutNetwork2(ast) {
13513
+ const links = withAutoLinks(ast);
13514
+ let centers;
13515
+ switch (ast.layout) {
13516
+ case "tree":
13517
+ centers = placeBanded(ast, treeRanks(ast, links));
13518
+ break;
13519
+ case "star":
13520
+ centers = placeStar(ast, links);
13521
+ break;
13522
+ case "ring":
13523
+ centers = placeCircle(ast, NET_CONST.RING_RADIUS_MIN);
13524
+ break;
13525
+ case "mesh":
13526
+ centers = placeCircle(ast, NET_CONST.RING_RADIUS_MIN);
13527
+ break;
13528
+ case "bus":
13529
+ centers = placeBus(ast);
13530
+ break;
13531
+ case "spine-leaf":
13532
+ centers = placeSpineLeaf(ast, links);
13533
+ break;
13534
+ case "manual":
13535
+ centers = placeManual(ast);
13536
+ break;
13537
+ case "tiered":
13538
+ default:
13539
+ centers = placeBanded(ast, tieredRanks(ast, links));
13540
+ break;
13541
+ }
13542
+ const boxes = ast.devices.map((d) => {
13543
+ const fp = deviceFootprint(d);
13544
+ const c = centers.get(d.id) ?? { x: 0, y: 0 };
13545
+ return {
13546
+ device: d,
13547
+ cx: c.x,
13548
+ cy: c.y,
13549
+ x: c.x - fp.w / 2,
13550
+ y: c.y - fp.h / 2,
13551
+ w: fp.w,
13552
+ h: fp.h,
13553
+ band: 0
13554
+ };
13555
+ });
13556
+ const boxById = new Map(boxes.map((b) => [b.device.id, b]));
13557
+ const groupBoxesRaw = /* @__PURE__ */ new Map();
13558
+ const depthOf = (id) => {
13559
+ let depth = 0;
13560
+ let g = ast.groups.find((x) => x.id === id);
13561
+ while (g?.parent) {
13562
+ depth++;
13563
+ g = ast.groups.find((x) => x.id === g.parent);
13564
+ }
13565
+ return depth;
13566
+ };
13567
+ const groupsByDepth = [...ast.groups].sort((a, b) => depthOf(b.id) - depthOf(a.id));
13568
+ for (const g of groupsByDepth) {
13569
+ let l = Infinity, t = Infinity, r = -Infinity, bm = -Infinity;
13570
+ const addBox = (e) => {
13571
+ l = Math.min(l, e.left);
13572
+ t = Math.min(t, e.top);
13573
+ r = Math.max(r, e.right);
13574
+ bm = Math.max(bm, e.bottom);
13575
+ };
13576
+ for (const mid of g.members) {
13577
+ const mb = boxById.get(mid);
13578
+ if (mb) addBox(effBox(mb));
13579
+ }
13580
+ for (const cid of g.children) {
13581
+ const cb = groupBoxesRaw.get(cid);
13582
+ if (cb) addBox(cb);
13583
+ }
13584
+ if (l === Infinity) continue;
13585
+ const depth = depthOf(g.id);
13586
+ const pad = NET_CONST.GROUP_PAD;
13587
+ groupBoxesRaw.set(g.id, {
13588
+ left: l - pad,
13589
+ top: t - pad - NET_CONST.GROUP_HEADER,
13590
+ right: r + pad,
13591
+ bottom: bm + pad,
13592
+ depth
13593
+ });
13594
+ }
13595
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
13596
+ for (const b of boxes) {
13597
+ const e = effBox(b);
13598
+ minX = Math.min(minX, e.left);
13599
+ minY = Math.min(minY, e.top);
13600
+ maxX = Math.max(maxX, e.right);
13601
+ maxY = Math.max(maxY, e.bottom);
13602
+ }
13603
+ for (const gb of groupBoxesRaw.values()) {
13604
+ minX = Math.min(minX, gb.left);
13605
+ minY = Math.min(minY, gb.top);
13606
+ maxX = Math.max(maxX, gb.right);
13607
+ maxY = Math.max(maxY, gb.bottom);
13608
+ }
13609
+ if (!Number.isFinite(minX)) {
13610
+ minX = 0;
13611
+ minY = 0;
13612
+ maxX = NET_CONST.DEVICE_W;
13613
+ maxY = NET_CONST.DEVICE_H;
13614
+ }
13615
+ const dx = NET_CONST.PAD - minX;
13616
+ const dy = NET_CONST.PAD - minY;
13617
+ for (const b of boxes) {
13618
+ b.x += dx;
13619
+ b.y += dy;
13620
+ b.cx += dx;
13621
+ b.cy += dy;
13622
+ }
13623
+ const groups = [];
13624
+ for (const g of ast.groups) {
13625
+ const gb = groupBoxesRaw.get(g.id);
13626
+ if (!gb) continue;
13627
+ groups.push({
13628
+ group: g,
13629
+ x: gb.left + dx,
13630
+ y: gb.top + dy,
13631
+ w: gb.right - gb.left,
13632
+ h: gb.bottom - gb.top,
13633
+ depth: gb.depth
13634
+ });
13635
+ }
13636
+ const linkGeoms = links.map((link) => {
13637
+ const a = boxById.get(link.from);
13638
+ const b = boxById.get(link.to);
13639
+ const p1 = edgePoint(a, b.cx, b.cy);
13640
+ const p2 = edgePoint(b, a.cx, a.cy);
13641
+ return {
13642
+ link,
13643
+ points: [p1, p2],
13644
+ labelX: (p1.x + p2.x) / 2,
13645
+ labelY: (p1.y + p2.y) / 2
13646
+ };
13647
+ });
13648
+ const width = maxX - minX + 2 * NET_CONST.PAD;
13649
+ const height = maxY - minY + 2 * NET_CONST.PAD;
13650
+ return {
13651
+ ast,
13652
+ width: Math.max(width, 120),
13653
+ height: Math.max(height, 80),
13654
+ devices: boxes,
13655
+ links: linkGeoms,
13656
+ groups,
13657
+ topologyClass: classify(ast),
13658
+ warnings: ast.warnings,
13659
+ title: ast.title
13660
+ };
13661
+ }
13662
+
13663
+ // src/diagrams/network/renderer.ts
13664
+ var r22 = (n) => Math.round(n * 100) / 100;
13665
+ function buildCss7(t) {
13666
+ return `
13667
+ .sx-net { font-family: system-ui, -apple-system, sans-serif; }
13668
+ .sx-net-body { fill: ${t.deviceFill}; stroke: ${t.deviceStroke}; stroke-width: 2; }
13669
+ .sx-net-detail { fill: none; stroke: ${t.deviceStroke}; stroke-width: 1; }
13670
+ .sx-net-glyph { fill: ${t.deviceAccent}; stroke: none; }
13671
+ .sx-net-glyph-line { fill: none; stroke: ${t.deviceAccent}; stroke-width: 1.4; }
13672
+ .sx-net-icontext { font: 700 8px sans-serif; fill: ${t.deviceAccent}; }
13673
+ .sx-net-cloud-body { fill: ${t.cloudFill}; stroke: ${t.cloudStroke}; stroke-width: 2; }
13674
+ .sx-net-cloudtext { font: 600 13px sans-serif; fill: ${t.text}; }
13675
+ .sx-net-bus { stroke: ${t.deviceStroke}; stroke-width: 4; stroke-linecap: round; }
13676
+ .sx-net-label { font: 12px sans-serif; fill: ${t.label}; }
13677
+ .sx-net-sublabel { font: 10px sans-serif; fill: ${t.subLabel}; }
13678
+ .sx-net-link { fill: none; stroke-width: 2; }
13679
+ .sx-net-link-wireless, .sx-net-link-vpn { stroke-dasharray: 5 4; }
13680
+ .sx-net-link-lag { stroke-width: 3; }
13681
+ .sx-net-linklabel { font: 9px sans-serif; fill: ${t.linkLabel}; paint-order: stroke; stroke: ${t.bg}; stroke-width: 3px; stroke-linejoin: round; }
13682
+ .sx-net-port { font: 8px sans-serif; fill: ${t.linkLabel}; paint-order: stroke; stroke: ${t.bg}; stroke-width: 2.5px; stroke-linejoin: round; }
13683
+ .sx-net-boundary-site { fill: none; stroke: ${t.siteStroke}; stroke-width: 1.5; }
13684
+ .sx-net-boundary-rack { fill: none; stroke: ${t.siteStroke}; stroke-width: 1.5; stroke-dasharray: 1 2; }
13685
+ .sx-net-boundary-subnet, .sx-net-boundary-vlan { fill: ${t.subnetFill}; stroke: ${t.subnetStroke}; stroke-width: 1.2; stroke-dasharray: 5 3; }
13686
+ .sx-net-boundary-zone, .sx-net-boundary-dmz { fill: none; stroke: ${t.zoneStroke}; stroke-width: 1.2; stroke-dasharray: 5 3; }
13687
+ .sx-net-boundary-label { font: 600 10px sans-serif; }
13688
+ .sx-net-title { font: 700 16px sans-serif; fill: ${t.text}; }
13689
+ `.trim();
13690
+ }
13691
+ function linkColor(t, link) {
13692
+ if (link.linkType === "copper" && link.vlans && link.vlans.length === 1) {
13693
+ const vid = link.vlans[0];
13694
+ const pal = t.vlanPalette.filter((c) => c !== t.negative);
13695
+ const safe = pal.length ? pal : t.vlanPalette;
13696
+ return safe[vid % safe.length];
13697
+ }
13698
+ switch (link.linkType) {
13699
+ case "fiber":
13700
+ return t.linkFiber;
13701
+ case "wireless":
13702
+ return t.linkWireless;
13703
+ case "serial":
13704
+ return t.linkSerial;
13705
+ case "poe":
13706
+ return t.linkPoe;
13707
+ case "vpn":
13708
+ return t.linkVpn;
13709
+ case "lag":
13710
+ return t.linkLag;
13711
+ default:
13712
+ return t.linkCopper;
13713
+ }
10089
13714
  }
10090
- function renderSentinel(s) {
10091
- return group({ class: "sx-pert-sentinel", "data-id": s.id }, [
10092
- circle({ cx: s.cx, cy: s.cy, r: s.r }),
10093
- text({ x: s.cx, y: s.cy + 3.5, "text-anchor": "middle" }, s.label)
10094
- ]);
13715
+ function annotation(link) {
13716
+ const parts = [];
13717
+ if (link.mode) parts.push(link.mode === "trunk" ? "Trunk" : "Access");
13718
+ if (link.vlans?.length) parts.push(`VLAN ${link.vlans.join(",")}`);
13719
+ if (link.speed) parts.push(link.speed);
13720
+ if (link.linkType === "poe") parts.push("PoE");
13721
+ if (link.linkType === "vpn") parts.push("VPN");
13722
+ if (link.label) parts.push(link.label);
13723
+ return parts.join(" \xB7 ");
13724
+ }
13725
+ function arrowHead(x1, y1, x2, y2, color, hs = 6) {
13726
+ const ang = Math.atan2(y2 - y1, x2 - x1);
13727
+ const a1 = ang + Math.PI - 0.45;
13728
+ const a2 = ang + Math.PI + 0.45;
13729
+ return polygon({
13730
+ fill: color,
13731
+ points: `${r22(x2)},${r22(y2)} ${r22(x2 + hs * Math.cos(a1))},${r22(y2 + hs * Math.sin(a1))} ${r22(x2 + hs * Math.cos(a2))},${r22(y2 + hs * Math.sin(a2))}`
13732
+ });
10095
13733
  }
10096
- function renderAxis2(axis, gridTop) {
13734
+ function renderLink(lg, t) {
13735
+ const { link } = lg;
13736
+ const p1 = lg.points[0];
13737
+ const p2 = lg.points[lg.points.length - 1];
13738
+ const color = linkColor(t, link);
13739
+ const cls = `sx-net-link sx-net-link-${link.linkType}`;
10097
13740
  const parts = [];
10098
- for (const tk of axis.ticks) {
10099
- if (tk.major) {
10100
- parts.push(line({ class: "sx-pert-grid", x1: tk.pos, y1: gridTop, x2: tk.pos, y2: axis.baseline }));
10101
- }
13741
+ if (link.linkType === "lag") {
13742
+ const ang = Math.atan2(p2.y - p1.y, p2.x - p1.x) + Math.PI / 2;
13743
+ const ox = 1.8 * Math.cos(ang);
13744
+ const oy = 1.8 * Math.sin(ang);
13745
+ parts.push(line({ class: cls, stroke: color, x1: r22(p1.x + ox), y1: r22(p1.y + oy), x2: r22(p2.x + ox), y2: r22(p2.y + oy) }));
13746
+ parts.push(line({ class: cls, stroke: color, x1: r22(p1.x - ox), y1: r22(p1.y - oy), x2: r22(p2.x - ox), y2: r22(p2.y - oy) }));
13747
+ } else {
13748
+ parts.push(el("polyline", { class: cls, stroke: color, points: lg.points.map((p) => `${r22(p.x)},${r22(p.y)}`).join(" ") }));
10102
13749
  }
10103
- parts.push(line({ class: "baseline", x1: axis.start, y1: axis.baseline, x2: axis.end, y2: axis.baseline }));
10104
- for (const tk of axis.ticks) {
10105
- const len = tk.major ? 7 : 4;
10106
- parts.push(line({ x1: tk.pos, y1: axis.baseline, x2: tk.pos, y2: axis.baseline + len }));
10107
- if (tk.major) {
10108
- parts.push(
10109
- text({ x: tk.pos, y: axis.baseline + 18, "text-anchor": "middle" }, fmtVal(tk.value))
10110
- );
13750
+ if (link.directed) parts.push(arrowHead(p1.x, p1.y, p2.x, p2.y, color));
13751
+ if (link.linkType === "fiber") {
13752
+ const ang = Math.atan2(p2.y - p1.y, p2.x - p1.x) + Math.PI / 2;
13753
+ for (const f of [0.4, 0.55]) {
13754
+ const mx = p1.x + (p2.x - p1.x) * f;
13755
+ const my = p1.y + (p2.y - p1.y) * f;
13756
+ parts.push(line({ class: cls, stroke: color, x1: r22(mx - 3 * Math.cos(ang)), y1: r22(my - 3 * Math.sin(ang)), x2: r22(mx + 3 * Math.cos(ang)), y2: r22(my + 3 * Math.sin(ang)) }));
10111
13757
  }
10112
13758
  }
10113
- return group({ class: "sx-pert-axis" }, parts);
13759
+ const ann = annotation(link);
13760
+ if (ann) parts.push(text({ class: "sx-net-linklabel", x: r22(lg.labelX), y: r22(lg.labelY - 3), "text-anchor": "middle" }, ann));
13761
+ if (link.portNear) parts.push(text({ class: "sx-net-port", x: r22(p1.x + (p2.x - p1.x) * 0.16), y: r22(p1.y + (p2.y - p1.y) * 0.16 - 3), "text-anchor": "middle" }, link.portNear));
13762
+ if (link.portFar) parts.push(text({ class: "sx-net-port", x: r22(p2.x + (p1.x - p2.x) * 0.16), y: r22(p2.y + (p1.y - p2.y) * 0.16 - 3), "text-anchor": "middle" }, link.portFar));
13763
+ return group(
13764
+ {
13765
+ class: "sx-net-link-g",
13766
+ "data-from": link.from,
13767
+ "data-to": link.to,
13768
+ "data-type": link.linkType,
13769
+ ...link.vlans?.length ? { "data-vlan": link.vlans.join(",") } : {},
13770
+ ...link.speed ? { "data-speed": link.speed } : {},
13771
+ ...link.mode ? { "data-mode": link.mode } : {}
13772
+ },
13773
+ parts
13774
+ );
10114
13775
  }
10115
- function renderAoa(aoa) {
10116
- const arcEls = [];
10117
- const labelEls = [];
10118
- for (const a of aoa.arcs) {
10119
- const cls = `sx-pert-aoa-arc${a.dummy ? " dummy" : ""}${a.critical ? " critical" : ""}`;
10120
- const marker = a.critical ? "url(#sx-pert-arrow-crit)" : "url(#sx-pert-arrow)";
10121
- arcEls.push(
10122
- path({
10123
- class: cls,
10124
- d: a.d,
10125
- "marker-end": marker,
10126
- "data-from": a.from,
10127
- "data-to": a.to,
10128
- "data-task": a.taskId ?? "",
10129
- "data-dummy": String(a.dummy),
10130
- "data-critical": String(a.critical)
10131
- })
10132
- );
10133
- if (!a.dummy && a.label) {
10134
- const critCls = a.critical ? " critical" : "";
10135
- const durStr = fmtVal(a.duration ?? 0);
10136
- const w = Math.max(a.label.length * 6.2, durStr.length * 6) + 8;
10137
- labelEls.push(
10138
- rect({ class: "sx-pert-edge-halo", x: a.labelX - w / 2, y: a.labelY - 16, width: w, height: 31, rx: 3, ry: 3 })
10139
- );
10140
- labelEls.push(
10141
- text({ class: `sx-pert-aoa-name${critCls}`, x: a.labelX, y: a.labelY - 4, "text-anchor": "middle" }, a.label)
10142
- );
10143
- labelEls.push(
10144
- text({ class: `sx-pert-aoa-dur${critCls}`, x: a.labelX, y: a.labelY + 12, "text-anchor": "middle" }, durStr)
10145
- );
10146
- }
10147
- }
10148
- const eventEls = [];
10149
- for (const e of aoa.events) {
10150
- eventEls.push(
10151
- group({ class: `sx-pert-aoa-event${e.critical ? " critical" : ""}`, "data-event": e.id }, [
10152
- circle({ cx: e.x, cy: e.y, r: e.r }),
10153
- text({ x: e.x, y: e.y + 4.5, "text-anchor": "middle" }, String(e.id))
10154
- ])
10155
- );
10156
- }
10157
- return group({ class: "sx-pert-aoa" }, [
10158
- group({ class: "sx-pert-aoa-arcs" }, arcEls),
10159
- group({ class: "sx-pert-aoa-labels" }, labelEls),
10160
- group({ class: "sx-pert-aoa-events" }, eventEls)
10161
- ]);
13776
+ function renderGroup(gb, t) {
13777
+ const k = gb.group.kind;
13778
+ const cls = `sx-net-boundary-${k}`;
13779
+ const labelColor = k === "zone" || k === "dmz" ? t.zoneStroke : k === "subnet" || k === "vlan" ? t.subnetStroke : t.siteStroke;
13780
+ const label = gb.group.label ?? gb.group.id;
13781
+ const tag = k === "vlan" ? `VLAN ${label}` : label;
13782
+ return group(
13783
+ { class: "sx-net-boundary", "data-kind": k, "data-label": escapeXml(label) },
13784
+ [
13785
+ rect({ class: cls, x: r22(gb.x), y: r22(gb.y), width: r22(gb.w), height: r22(gb.h), rx: 8, ry: 8 }),
13786
+ text({ class: "sx-net-boundary-label", fill: labelColor, x: r22(gb.x + NET_CONST.GROUP_LABEL_INSET), y: r22(gb.y + 13) }, tag)
13787
+ ]
13788
+ );
10162
13789
  }
10163
- function summaryText(summary) {
10164
- const u = unitWord(summary.unit);
10165
- const dur = `${fmtVal(summary.projectDuration)}${u ? " " + u : ""}`;
10166
- const sigma = summary.projectStdDev !== void 0 ? ` \xB7 \u03C3 \u2248 ${fmtVal(summary.projectStdDev)}` : "";
10167
- const plain = `Project duration ${dur} \xB7 ${summary.taskCount} tasks \xB7 ${summary.depCount} dependencies \xB7 ${summary.criticalCount} critical${sigma}`;
10168
- const critPath = summary.criticalPath.map((id) => id).join(" \u2192 ");
10169
- return { plain, critPath };
13790
+ function renderDevice(b, t) {
13791
+ const d = b.device;
13792
+ const parts = [drawDeviceIcon(d, { x: b.x, y: b.y, w: b.w, h: b.h })];
13793
+ if (!isCloudKind(d.kind)) {
13794
+ const labelY = b.y + b.h + NET_CONST.LABEL_GAP + 11;
13795
+ parts.push(text({ class: "sx-net-label", x: r22(b.cx), y: r22(labelY), "text-anchor": "middle" }, d.label ?? d.id));
13796
+ const sub = d.ip ?? d.model;
13797
+ if (sub) parts.push(text({ class: "sx-net-sublabel", x: r22(b.cx), y: r22(labelY + NET_CONST.SUBLABEL_H), "text-anchor": "middle" }, sub));
13798
+ }
13799
+ const attrs = {
13800
+ class: "sx-net-device",
13801
+ "data-id": d.id,
13802
+ "data-kind": d.kind
13803
+ };
13804
+ if (d.tier) attrs["data-tier"] = d.tier;
13805
+ if (d.ip) attrs["data-ip"] = d.ip;
13806
+ if (d.cameraType) attrs["data-type"] = d.cameraType;
13807
+ return group(attrs, parts);
10170
13808
  }
10171
- function renderPertLayout(layout, config) {
10172
- const t = resolveBaseTheme(config?.theme ?? "default");
13809
+ function renderNetworkLayout(layout, config) {
13810
+ const t = resolveNetworkTheme(config?.theme ?? "default");
10173
13811
  const children = [];
10174
- const cp = layout.summary.criticalPath;
10175
- children.push(title(`PERT network${layout.title ? " \u2014 " + layout.title : ""}`));
10176
- children.push(
10177
- desc(
10178
- `${layout.summary.taskCount} activities, project duration ${fmtVal(layout.summary.projectDuration)} ${unitWord(layout.unit)}` + (cp.length ? `, critical path ${cp.join(" \u2192 ")}` : "") + "."
10179
- )
10180
- );
10181
- children.push(el("style", {}, buildCss4(t)));
10182
- children.push(markers2());
13812
+ const counts = /* @__PURE__ */ new Map();
13813
+ for (const b of layout.devices) counts.set(b.device.kind, (counts.get(b.device.kind) ?? 0) + 1);
13814
+ const linkTypes = /* @__PURE__ */ new Map();
13815
+ for (const l of layout.links) linkTypes.set(l.link.linkType, (linkTypes.get(l.link.linkType) ?? 0) + 1);
13816
+ const descParts = [
13817
+ `${layout.devices.length} devices, ${layout.links.length} links, ${layout.groups.length} boundaries.`,
13818
+ `Topology: ${layout.topologyClass}.`,
13819
+ linkTypes.size ? `Links: ${[...linkTypes].map(([k, n]) => `${n} ${k}`).join(", ")}.` : "",
13820
+ layout.warnings.length ? `Warnings: ${layout.warnings.join("; ")}.` : ""
13821
+ ].filter(Boolean);
13822
+ children.push(title(`Network diagram${layout.title ? " \u2014 " + layout.title : ""}`));
13823
+ children.push(desc(descParts.join(" ")));
13824
+ children.push(el("style", {}, buildCss7(t)));
13825
+ const titleBand = layout.title ? 30 : 0;
10183
13826
  if (layout.title) {
10184
- children.push(
10185
- text({ x: layout.width / 2, y: 26, class: "sx-pert-title", "text-anchor": "middle" }, layout.title)
10186
- );
10187
- }
10188
- if (layout.aoa) {
10189
- children.push(renderAoa(layout.aoa));
10190
- } else {
10191
- if (layout.lanes && layout.lanes.length) {
10192
- children.push(renderLanes(layout.lanes, layout.width));
10193
- }
10194
- if (layout.axis) {
10195
- let minBoxY = Infinity;
10196
- for (const b of layout.boxes) minBoxY = Math.min(minBoxY, b.y);
10197
- children.push(renderAxis2(layout.axis, isFinite(minBoxY) ? minBoxY : layout.axis.baseline - 20));
10198
- }
10199
- const edgeEls = [];
10200
- for (const e of layout.edges) edgeEls.push(renderEdge3(e));
10201
- children.push(group({ class: "sx-pert-edges" }, edgeEls));
10202
- if (layout.sentinels.length) {
10203
- children.push(group({ class: "sx-pert-sentinels" }, layout.sentinels.map(renderSentinel)));
10204
- }
10205
- const boxEls = [];
10206
- for (const b of layout.boxes) boxEls.push(renderBox2(b, layout.mode));
10207
- children.push(group({ class: "sx-pert-tasks" }, boxEls));
10208
- const labelEls = [];
10209
- for (const e of layout.edges) {
10210
- const lbl = renderEdgeLabel2(e);
10211
- if (lbl) labelEls.push(lbl);
10212
- }
10213
- children.push(group({ class: "sx-pert-labels" }, labelEls));
13827
+ children.push(text({ x: r22(layout.width / 2), y: 21, class: "sx-net-title", "text-anchor": "middle" }, layout.title));
10214
13828
  }
10215
- const { plain, critPath } = summaryText(layout.summary);
10216
- const footerY = layout.height - 12;
10217
- const footerParts = [
10218
- text({ class: "sx-pert-summary", x: PERT_PAD, y: footerY, "text-anchor": "start" }, plain)
10219
- ];
10220
- if (critPath) {
10221
- footerParts.push(
10222
- text(
10223
- { class: "sx-pert-summary crit", x: layout.width - PERT_PAD, y: footerY, "text-anchor": "end" },
10224
- `Critical path: ${critPath}`
10225
- )
10226
- );
10227
- }
10228
- children.push(group({ class: "sx-pert-footer" }, footerParts));
13829
+ const body = [];
13830
+ const sortedGroups = [...layout.groups].sort((a, b) => a.depth - b.depth);
13831
+ body.push(group({ class: "sx-net-boundaries" }, sortedGroups.map((g) => renderGroup(g, t))));
13832
+ body.push(group({ class: "sx-net-links" }, layout.links.map((l) => renderLink(l, t))));
13833
+ body.push(group({ class: "sx-net-devices" }, layout.devices.map((b) => renderDevice(b))));
13834
+ children.push(titleBand ? group({ transform: `translate(0, ${titleBand})` }, body) : group({}, body));
13835
+ const height = layout.height + titleBand;
10229
13836
  return svgRoot(
10230
13837
  {
10231
- class: "sx-pert",
13838
+ class: "sx-net",
10232
13839
  role: "img",
10233
- "aria-label": escapeXml(layout.title ?? "PERT network diagram"),
10234
- width: layout.width,
10235
- height: layout.height,
10236
- viewBox: `0 0 ${layout.width} ${layout.height}`,
10237
- "data-diagram-type": "pert"
13840
+ "aria-label": escapeXml(layout.title ?? "Network diagram"),
13841
+ width: r22(layout.width),
13842
+ height: r22(height),
13843
+ viewBox: `0 0 ${r22(layout.width)} ${r22(height)}`,
13844
+ "data-diagram-type": "network"
10238
13845
  },
10239
13846
  children
10240
13847
  );
10241
13848
  }
10242
- var PERT_PAD = 24;
10243
- function renderPert(textOrAst, config) {
10244
- const ast = typeof textOrAst === "string" ? parsePert(textOrAst) : textOrAst;
10245
- const schedule = schedulePert(ast);
10246
- const layout = layoutPert(ast, schedule);
10247
- return renderPertLayout(layout, config);
13849
+ function renderNetwork(textOrAst, config) {
13850
+ const ast = typeof textOrAst === "string" ? parseNetwork(textOrAst) : textOrAst;
13851
+ const layout = layoutNetwork2(ast);
13852
+ return renderNetworkLayout(layout, config);
10248
13853
  }
10249
13854
 
10250
- // src/diagrams/pert/index.ts
10251
- var pert = {
10252
- type: "pert",
13855
+ // src/diagrams/network/index.ts
13856
+ var network = {
13857
+ type: "network",
10253
13858
  detect(text2) {
10254
13859
  for (const raw of text2.split(/\r?\n/)) {
10255
13860
  const t = raw.trim();
10256
13861
  if (!t) continue;
10257
13862
  if (t.startsWith("#") || t.startsWith("//")) continue;
10258
- return /^pert\b/i.test(t);
13863
+ return /^(network|topology)\b/i.test(t);
10259
13864
  }
10260
13865
  return false;
10261
13866
  },
10262
- parse: parsePert,
13867
+ parse: parseNetwork,
10263
13868
  render(text2, config) {
10264
- return renderPert(text2, config);
13869
+ return renderNetwork(text2, config);
10265
13870
  }
10266
13871
  };
10267
13872
 
13873
+ // src/core/diagnostics.ts
13874
+ var ENGINE_BUG_NAMES = /* @__PURE__ */ new Set([
13875
+ "ReferenceError",
13876
+ "TypeError",
13877
+ "RangeError"
13878
+ ]);
13879
+ function diagnosticFromError(err) {
13880
+ if (err instanceof Error) {
13881
+ const anyErr = err;
13882
+ const hasParseFields = typeof anyErr.line === "number";
13883
+ const isEngineBug = !hasParseFields && ENGINE_BUG_NAMES.has(err.name);
13884
+ const source = typeof anyErr.source === "string" ? anyErr.source : isEngineBug ? firstStackFrame(err.stack) : void 0;
13885
+ return {
13886
+ severity: "error",
13887
+ code: isEngineBug ? "ENGINE_BUG" : "DSL_INVALID",
13888
+ line: typeof anyErr.line === "number" ? anyErr.line : void 0,
13889
+ column: typeof anyErr.column === "number" ? anyErr.column : void 0,
13890
+ source,
13891
+ message: isEngineBug ? `[engine bug: ${err.name}] ${err.message}` : err.message,
13892
+ hint: typeof anyErr.hint === "string" ? anyErr.hint : isEngineBug ? "This looks like a Schematex internal error rather than a DSL syntax problem. Keep the failing DSL and file an issue." : void 0,
13893
+ fatal: true
13894
+ };
13895
+ }
13896
+ return {
13897
+ severity: "error",
13898
+ code: "UNKNOWN_THROW",
13899
+ message: String(err),
13900
+ fatal: true
13901
+ };
13902
+ }
13903
+ function renderDiagnosticSvg(diagnostics, type, config = {}) {
13904
+ const headline = type ? `${type} preview could not be rendered` : "Diagram preview could not be rendered";
13905
+ const detail = diagnostics[0]?.message ?? "Schematex could not parse this DSL.";
13906
+ const lines = wrapText3(detail, 88).slice(0, 5);
13907
+ const source = diagnostics[0]?.source ? wrapText3(`Source: ${diagnostics[0].source}`, 88).slice(0, 2) : [];
13908
+ const foot = "Strict validation failed before a diagram could be drawn.";
13909
+ const width = 760;
13910
+ const lineHeight = 20;
13911
+ const height = 174 + (lines.length + source.length) * lineHeight;
13912
+ const fontFamily = config.fontFamily ?? "system-ui, -apple-system, sans-serif";
13913
+ const children = [
13914
+ title(headline),
13915
+ desc(`${headline}. ${detail}`),
13916
+ el(
13917
+ "style",
13918
+ {},
13919
+ `
13920
+ .schematex-preview-error { font-family: ${escapeXml(fontFamily)}; }
13921
+ .schematex-preview-error-frame { fill: #fff7ed; stroke: #c2410c; stroke-width: 1.5; }
13922
+ .schematex-preview-error-mark { fill: #c2410c; }
13923
+ .schematex-preview-error-title { fill: #7c2d12; font-size: 18px; font-weight: 600; }
13924
+ .schematex-preview-error-copy { fill: #431407; font-size: 13px; }
13925
+ .schematex-preview-error-muted { fill: #9a3412; font-size: 12px; }
13926
+ `
13927
+ ),
13928
+ el("g", { class: "schematex-preview-error" }, [
13929
+ rect({
13930
+ x: 1,
13931
+ y: 1,
13932
+ width: width - 2,
13933
+ height: height - 2,
13934
+ rx: 6,
13935
+ class: "schematex-preview-error-frame"
13936
+ }),
13937
+ rect({
13938
+ x: 28,
13939
+ y: 30,
13940
+ width: 8,
13941
+ height: 34,
13942
+ rx: 4,
13943
+ class: "schematex-preview-error-mark"
13944
+ }),
13945
+ rect({
13946
+ x: 28,
13947
+ y: 72,
13948
+ width: 8,
13949
+ height: 8,
13950
+ rx: 4,
13951
+ class: "schematex-preview-error-mark"
13952
+ }),
13953
+ text(
13954
+ { x: 58, y: 49, class: "schematex-preview-error-title" },
13955
+ headline
13956
+ ),
13957
+ text(
13958
+ { x: 58, y: 76, class: "schematex-preview-error-muted" },
13959
+ "The DSL is still available to repair or retry."
13960
+ ),
13961
+ ...renderRows(lines, 58, 112, lineHeight, "schematex-preview-error-copy"),
13962
+ ...renderRows(
13963
+ source,
13964
+ 58,
13965
+ 112 + lines.length * lineHeight + 10,
13966
+ lineHeight,
13967
+ "schematex-preview-error-muted"
13968
+ ),
13969
+ text(
13970
+ { x: 58, y: height - 28, class: "schematex-preview-error-muted" },
13971
+ foot
13972
+ )
13973
+ ])
13974
+ ];
13975
+ return svgRoot(
13976
+ {
13977
+ width,
13978
+ height,
13979
+ viewBox: `0 0 ${width} ${height}`,
13980
+ role: "img",
13981
+ "aria-label": headline,
13982
+ "data-schematex-status": "invalid"
13983
+ },
13984
+ children
13985
+ );
13986
+ }
13987
+ function renderRows(rows, x, y, lineHeight, className) {
13988
+ return rows.map(
13989
+ (row, idx) => text({ x, y: y + idx * lineHeight, class: className }, row)
13990
+ );
13991
+ }
13992
+ function wrapText3(value, max) {
13993
+ const words = value.replace(/\s+/g, " ").trim().split(" ");
13994
+ const rows = [];
13995
+ let current = "";
13996
+ for (const word of words) {
13997
+ if (!current) {
13998
+ current = word;
13999
+ continue;
14000
+ }
14001
+ if (current.length + word.length + 1 <= max) {
14002
+ current += ` ${word}`;
14003
+ continue;
14004
+ }
14005
+ rows.push(current);
14006
+ current = word;
14007
+ }
14008
+ if (current) rows.push(current);
14009
+ return rows.length > 0 ? rows : [value];
14010
+ }
14011
+ function firstStackFrame(stack) {
14012
+ if (!stack) return void 0;
14013
+ for (const line2 of stack.split("\n")) {
14014
+ const trimmed = line2.trim();
14015
+ if (trimmed.startsWith("at ")) {
14016
+ return trimmed.replace(/\((?:.*\/)?([^/]+)\)/, "($1)");
14017
+ }
14018
+ }
14019
+ return void 0;
14020
+ }
14021
+
10268
14022
  // src/diagrams/mindmap/inline.ts
10269
14023
  var RE_TASK = /^\[( |x|X)\]\s+(.*)$/;
10270
14024
  function tokenizeInline(raw) {
@@ -11234,7 +14988,7 @@ function newAST() {
11234
14988
  config: { ...DEFAULT_CONFIG }
11235
14989
  };
11236
14990
  }
11237
- function stripQuotes3(s) {
14991
+ function stripQuotes4(s) {
11238
14992
  const t = s.trim();
11239
14993
  if (t.startsWith('"') && t.endsWith('"') || t.startsWith("'") && t.endsWith("'")) {
11240
14994
  return t.slice(1, -1);
@@ -11255,11 +15009,11 @@ function readQuoted2(line2, from) {
11255
15009
  function parseAxis(raw) {
11256
15010
  const arrowMatch = raw.match(/\s*(→|↑|->|>|↓|←|<-|<)\s*/);
11257
15011
  if (arrowMatch) {
11258
- const arrow = arrowMatch[1];
15012
+ const arrow2 = arrowMatch[1];
11259
15013
  const idx = arrowMatch.index;
11260
15014
  const left = raw.slice(0, idx).trim();
11261
15015
  const right = raw.slice(idx + arrowMatch[0].length).trim();
11262
- const reversed = arrow === "\u2190" || arrow === "<-" || arrow === "<";
15016
+ const reversed = arrow2 === "\u2190" || arrow2 === "<-" || arrow2 === "<";
11263
15017
  if (reversed) {
11264
15018
  return { low: right, high: left, reversed: true };
11265
15019
  }
@@ -11270,7 +15024,7 @@ function parseAxis(raw) {
11270
15024
  function parseNumberList2(raw) {
11271
15025
  const t = raw.trim();
11272
15026
  const inner = t.startsWith("[") && t.endsWith("]") ? t.slice(1, -1) : t;
11273
- return inner.split(",").map((s) => stripQuotes3(s.trim())).filter((s) => s.length > 0);
15027
+ return inner.split(",").map((s) => stripQuotes4(s.trim())).filter((s) => s.length > 0);
11274
15028
  }
11275
15029
  function parseProperties2(raw, point) {
11276
15030
  let i = 0;
@@ -11385,7 +15139,7 @@ function parseHeader2(line2, ast) {
11385
15139
  ast.cols = Number(heatMatch[1]);
11386
15140
  ast.rows = Number(heatMatch[2]);
11387
15141
  const title2 = heatMatch[3].trim();
11388
- if (title2) ast.title = stripQuotes3(title2);
15142
+ if (title2) ast.title = stripQuotes4(title2);
11389
15143
  return void 0;
11390
15144
  }
11391
15145
  const corrMatch = rest.match(/^correlation\s*(?:(\d+)\s*x\s*(\d+))?\s*(.*)$/i);
@@ -11397,7 +15151,7 @@ function parseHeader2(line2, ast) {
11397
15151
  ast.rows = Number(corrMatch[2]);
11398
15152
  }
11399
15153
  const title2 = corrMatch[3].trim();
11400
- if (title2) ast.title = stripQuotes3(title2);
15154
+ if (title2) ast.title = stripQuotes4(title2);
11401
15155
  return void 0;
11402
15156
  }
11403
15157
  const tokenMatch = rest.match(/^([a-zA-Z0-9_-]+)\s*(.*)$/);
@@ -11405,13 +15159,13 @@ function parseHeader2(line2, ast) {
11405
15159
  const tok = tokenMatch[1].toLowerCase();
11406
15160
  const remainder = tokenMatch[2].trim();
11407
15161
  if (TEMPLATE_NAMES.has(tok)) {
11408
- if (remainder) ast.title = stripQuotes3(remainder);
15162
+ if (remainder) ast.title = stripQuotes4(remainder);
11409
15163
  return tok;
11410
15164
  }
11411
15165
  if (rest.startsWith('"') || rest.startsWith("'")) {
11412
- ast.title = stripQuotes3(rest);
15166
+ ast.title = stripQuotes4(rest);
11413
15167
  } else if (rest.length > 0) {
11414
- ast.title = stripQuotes3(rest);
15168
+ ast.title = stripQuotes4(rest);
11415
15169
  }
11416
15170
  }
11417
15171
  return void 0;
@@ -11447,7 +15201,7 @@ function parseMatrix(text2) {
11447
15201
  inConfig = false;
11448
15202
  }
11449
15203
  if (/^title\s*:/i.test(line2)) {
11450
- st.ast.title = stripQuotes3(line2.replace(/^title\s*:\s*/i, ""));
15204
+ st.ast.title = stripQuotes4(line2.replace(/^title\s*:\s*/i, ""));
11451
15205
  continue;
11452
15206
  }
11453
15207
  if (/^x-axis\s*:/i.test(line2)) {
@@ -11532,7 +15286,7 @@ function parseMatrix(text2) {
11532
15286
  if (qShort) {
11533
15287
  const q = Number(qShort[1]);
11534
15288
  const labelRaw = qShort[2].trim();
11535
- const label = stripQuotes3(labelRaw);
15289
+ const label = stripQuotes4(labelRaw);
11536
15290
  const cell = quadrantToCell(q);
11537
15291
  st.ast.cellLabels.push({ col: cell.col, row: cell.row, label });
11538
15292
  continue;
@@ -11574,11 +15328,11 @@ var CANVAS_W = 720;
11574
15328
  var CANVAS_H = 560;
11575
15329
  var PADDING_X = 110;
11576
15330
  var PADDING_Y = 90;
11577
- var CHAR_W = 6.2;
15331
+ var CHAR_W2 = 6.2;
11578
15332
  var LABEL_H = 14;
11579
15333
  function estimateWidth(text2) {
11580
15334
  const cjk = (text2.match(/[\u3000-\u9fff]/g) ?? []).length;
11581
- return (text2.length - cjk) * CHAR_W + cjk * 12 + 8;
15335
+ return (text2.length - cjk) * CHAR_W2 + cjk * 12 + 8;
11582
15336
  }
11583
15337
  function clamp01(v) {
11584
15338
  return Math.max(0.02, Math.min(0.98, v));
@@ -12629,7 +16383,7 @@ var ErdParseError = class extends Error {
12629
16383
  }
12630
16384
  lineNumber;
12631
16385
  };
12632
- function stripComment2(s) {
16386
+ function stripComment3(s) {
12633
16387
  let out = "";
12634
16388
  let inQuote = false;
12635
16389
  for (let i = 0; i < s.length; i++) {
@@ -12683,7 +16437,7 @@ function parseCardToken(raw, side) {
12683
16437
  }
12684
16438
  function lex(text2) {
12685
16439
  return text2.split(/\r?\n/).map((raw, i) => ({
12686
- text: stripComment2(raw).trim(),
16440
+ text: stripComment3(raw).trim(),
12687
16441
  lineNumber: i + 1
12688
16442
  })).filter((l) => l.text.length > 0);
12689
16443
  }
@@ -13334,7 +17088,7 @@ function sideAnchor(e, side, col) {
13334
17088
  }
13335
17089
 
13336
17090
  // src/diagrams/erd/renderer.ts
13337
- function buildCss5(t) {
17091
+ function buildCss8(t) {
13338
17092
  return `
13339
17093
  .lt-erd { font-family: system-ui, -apple-system, sans-serif; }
13340
17094
  .lt-erd-title { font: bold 16px sans-serif; fill: ${t.text}; }
@@ -13420,10 +17174,10 @@ function renderEntity(e) {
13420
17174
  a.name
13421
17175
  )
13422
17176
  );
13423
- const markers3 = attrMarkers(a);
17177
+ const markers5 = attrMarkers(a);
13424
17178
  const markerW = 26;
13425
17179
  const markerGap = 4;
13426
- const markersBlockW = markers3.length * markerW + (markers3.length - 1) * markerGap;
17180
+ const markersBlockW = markers5.length * markerW + (markers5.length - 1) * markerGap;
13427
17181
  const markersStartX = e.x + e.width - C2.ENTITY_PADDING_X - markersBlockW;
13428
17182
  if (a.type) {
13429
17183
  body.push(
@@ -13438,8 +17192,8 @@ function renderEntity(e) {
13438
17192
  )
13439
17193
  );
13440
17194
  }
13441
- for (let j = 0; j < markers3.length; j++) {
13442
- const m = markers3[j];
17195
+ for (let j = 0; j < markers5.length; j++) {
17196
+ const m = markers5[j];
13443
17197
  const px = markersStartX + j * (markerW + markerGap);
13444
17198
  body.push(
13445
17199
  rect({
@@ -13644,7 +17398,7 @@ function renderEdge4(edge) {
13644
17398
  function renderErdAst(result, themeName = "default") {
13645
17399
  const theme = resolveBaseTheme(themeName);
13646
17400
  const { entities, edges, width, height, ast } = result;
13647
- const cssBlock = el("style", {}, buildCss5(theme));
17401
+ const cssBlock = el("style", {}, buildCss8(theme));
13648
17402
  const titleNode = title(ast.title ?? "Schematex ERD");
13649
17403
  const descNode = desc(
13650
17404
  `Entity-Relationship Diagram with ${entities.length} entities and ${edges.length} relationships.`
@@ -13711,7 +17465,7 @@ var BreadboardParseError = class extends Error {
13711
17465
  }
13712
17466
  lineNumber;
13713
17467
  };
13714
- function stripComment3(s) {
17468
+ function stripComment4(s) {
13715
17469
  let out = "";
13716
17470
  let inQuote = false;
13717
17471
  for (let i = 0; i < s.length; i++) {
@@ -13732,7 +17486,7 @@ function unquote6(v) {
13732
17486
  }
13733
17487
  function lex2(text2) {
13734
17488
  return text2.split(/\r?\n/).map((raw, i) => ({
13735
- text: stripComment3(raw).trimEnd().replace(/^\s+/, ""),
17489
+ text: stripComment4(raw).trimEnd().replace(/^\s+/, ""),
13736
17490
  lineNumber: i + 1
13737
17491
  })).filter((l) => l.text.length > 0);
13738
17492
  }
@@ -13779,7 +17533,7 @@ function parsePlacement(rest, lineNumber) {
13779
17533
  }
13780
17534
  return { kind: "point", at: parseCoord(body, lineNumber) };
13781
17535
  }
13782
- var KIND_ALIASES = {
17536
+ var KIND_ALIASES2 = {
13783
17537
  resistor: "resistor",
13784
17538
  led: "led",
13785
17539
  cap: "cap-elec",
@@ -13844,7 +17598,7 @@ function resolveKind(tokens, lineNumber) {
13844
17598
  if (!ACTUATOR_SUBTYPES[sub]) throw new BreadboardParseError(`Unknown actuator subtype '${sub}'`, lineNumber);
13845
17599
  return { kind: ACTUATOR_SUBTYPES[sub], consumed: 2 };
13846
17600
  }
13847
- if (KIND_ALIASES[head]) return { kind: KIND_ALIASES[head], consumed: 1 };
17601
+ if (KIND_ALIASES2[head]) return { kind: KIND_ALIASES2[head], consumed: 1 };
13848
17602
  throw new BreadboardParseError(`Unknown part kind '${head}'`, lineNumber);
13849
17603
  }
13850
17604
  function parsePart(rawLine, lineNumber) {
@@ -14714,7 +18468,7 @@ var WIRE_COLOR_MAP = {
14714
18468
  brown: "#78350f",
14715
18469
  grey: "#64748b"
14716
18470
  };
14717
- function buildCss6(t) {
18471
+ function buildCss9(t) {
14718
18472
  return `
14719
18473
  .lt-bb { font-family: system-ui, -apple-system, sans-serif; }
14720
18474
  .lt-bb-title { font: 600 16px sans-serif; fill: ${t.text}; }
@@ -14828,12 +18582,12 @@ function renderSubstrate(sub) {
14828
18582
  function renderPart(lp) {
14829
18583
  const spec = partSpec(lp.part.kind, lp.part.args);
14830
18584
  const body = spec.body(lp.part, lp.width, lp.height);
14831
- const labelText = lp.part.label ?? defaultPartLabel(lp);
14832
- const labelEl = labelText ? text({
18585
+ const labelText2 = lp.part.label ?? defaultPartLabel(lp);
18586
+ const labelEl = labelText2 ? text({
14833
18587
  x: lp.x + lp.width / 2,
14834
18588
  y: spec.category === "side" ? lp.y - 6 : lp.y - 4,
14835
18589
  class: "lt-bb-part-label"
14836
- }, labelText) : "";
18590
+ }, labelText2) : "";
14837
18591
  return group(
14838
18592
  { class: `lt-bb-part lt-bb-part-${lp.part.kind}`, transform: `translate(${lp.x.toFixed(2)} ${lp.y.toFixed(2)})` },
14839
18593
  [body]
@@ -14862,7 +18616,7 @@ function renderWire(lw) {
14862
18616
  }
14863
18617
  function renderBreadboardLayout(layout, config) {
14864
18618
  const theme = resolveBaseTheme(config?.theme ?? "default");
14865
- const css = buildCss6(theme);
18619
+ const css = buildCss9(theme);
14866
18620
  const titleStr = layout.ast.title ?? "Breadboard";
14867
18621
  const titleNode = layout.ast.title ? text({ x: BB_CONST.MARGIN, y: 22, class: "lt-bb-title" }, layout.ast.title) : "";
14868
18622
  const substrate = renderSubstrate(layout.substrate);
@@ -17060,13 +20814,13 @@ function parseFbd(text2) {
17060
20814
  parseStatement(state2, stmt);
17061
20815
  i++;
17062
20816
  }
17063
- const network = {
20817
+ const network2 = {
17064
20818
  index: idx,
17065
20819
  blocks: state2.blocks.slice(blocksBefore),
17066
20820
  wires: state2.wires.slice(wiresBefore)
17067
20821
  };
17068
- if (nh.title !== void 0) network.title = nh.title;
17069
- networks.push(network);
20822
+ if (nh.title !== void 0) network2.title = nh.title;
20823
+ networks.push(network2);
17070
20824
  }
17071
20825
  for (const block of state2.blocks) {
17072
20826
  if (!block.instance) continue;
@@ -17205,18 +20959,18 @@ function makePort(p, side, edgeX, x, y) {
17205
20959
  if (p.negated) port.negated = true;
17206
20960
  return port;
17207
20961
  }
17208
- function buildGraph2(network) {
20962
+ function buildGraph2(network2) {
17209
20963
  const incoming = /* @__PURE__ */ new Map();
17210
20964
  const outgoing = /* @__PURE__ */ new Map();
17211
20965
  const inputVars = /* @__PURE__ */ new Map();
17212
20966
  const outputVars = /* @__PURE__ */ new Map();
17213
- for (const b of network.blocks) {
20967
+ for (const b of network2.blocks) {
17214
20968
  incoming.set(b.id, /* @__PURE__ */ new Set());
17215
20969
  outgoing.set(b.id, /* @__PURE__ */ new Set());
17216
20970
  inputVars.set(b.id, /* @__PURE__ */ new Set());
17217
20971
  outputVars.set(b.id, /* @__PURE__ */ new Set());
17218
20972
  }
17219
- for (const w of network.wires) {
20973
+ for (const w of network2.wires) {
17220
20974
  if (w.from.kind === "port" && w.to.kind === "port") {
17221
20975
  incoming.get(w.to.blockId)?.add(w.from.blockId);
17222
20976
  outgoing.get(w.from.blockId)?.add(w.to.blockId);
@@ -17228,7 +20982,7 @@ function buildGraph2(network) {
17228
20982
  }
17229
20983
  return { incoming, outgoing, inputVars, outputVars };
17230
20984
  }
17231
- function assignLayers(network, graph) {
20985
+ function assignLayers(network2, graph) {
17232
20986
  const layer = /* @__PURE__ */ new Map();
17233
20987
  const visiting = /* @__PURE__ */ new Set();
17234
20988
  const visit = (id) => {
@@ -17245,20 +20999,20 @@ function assignLayers(network, graph) {
17245
20999
  layer.set(id, max);
17246
21000
  return max;
17247
21001
  };
17248
- for (const b of network.blocks) visit(b.id);
21002
+ for (const b of network2.blocks) visit(b.id);
17249
21003
  return layer;
17250
21004
  }
17251
- function layoutNetwork2(network, originX, originY) {
17252
- const graph = buildGraph2(network);
21005
+ function layoutNetwork3(network2, originX, originY) {
21006
+ const graph = buildGraph2(network2);
17253
21007
  const sizes = /* @__PURE__ */ new Map();
17254
- for (const b of network.blocks) sizes.set(b.id, computeBlockSize(b));
17255
- const layerOf = assignLayers(network, graph);
21008
+ for (const b of network2.blocks) sizes.set(b.id, computeBlockSize(b));
21009
+ const layerOf = assignLayers(network2, graph);
17256
21010
  const maxLayer = Math.max(0, ...Array.from(layerOf.values()));
17257
21011
  const byLayer = Array.from({ length: maxLayer + 1 }, () => []);
17258
- for (const b of network.blocks) byLayer[layerOf.get(b.id)].push(b.id);
21012
+ for (const b of network2.blocks) byLayer[layerOf.get(b.id)].push(b.id);
17259
21013
  const inputVarsAll = /* @__PURE__ */ new Set();
17260
21014
  const outputVarsAll = /* @__PURE__ */ new Set();
17261
- for (const b of network.blocks) {
21015
+ for (const b of network2.blocks) {
17262
21016
  for (const v of graph.inputVars.get(b.id) ?? []) inputVarsAll.add(v);
17263
21017
  for (const v of graph.outputVars.get(b.id) ?? []) outputVarsAll.add(v);
17264
21018
  }
@@ -17291,7 +21045,7 @@ function layoutNetwork2(network, originX, originY) {
17291
21045
  yMax = Math.max(yMax, y);
17292
21046
  }
17293
21047
  const layoutBlocks = [];
17294
- for (const b of network.blocks) {
21048
+ for (const b of network2.blocks) {
17295
21049
  const bb = blockBboxes.get(b.id);
17296
21050
  layoutBlocks.push({
17297
21051
  block: b,
@@ -17339,7 +21093,7 @@ function layoutNetwork2(network, originX, originY) {
17339
21093
  const wires = [];
17340
21094
  const junctions = [];
17341
21095
  const fanOutCounts = /* @__PURE__ */ new Map();
17342
- for (const w of network.wires) {
21096
+ for (const w of network2.wires) {
17343
21097
  if (w.from.kind === "port") {
17344
21098
  const key = `${w.from.blockId}.${w.from.portName}`;
17345
21099
  fanOutCounts.set(key, (fanOutCounts.get(key) ?? 0) + 1);
@@ -17357,7 +21111,7 @@ function layoutNetwork2(network, originX, originY) {
17357
21111
  const varTermByName = /* @__PURE__ */ new Map();
17358
21112
  for (const t of varTerms) varTermByName.set(t.name, t);
17359
21113
  const colCursor = /* @__PURE__ */ new Map();
17360
- for (const w of network.wires) {
21114
+ for (const w of network2.wires) {
17361
21115
  const path2 = routeWire(w, portByKey, varTermByName, colCursor, layerX, layerWidths);
17362
21116
  if (path2) {
17363
21117
  wires.push({ wire: w, path: path2 });
@@ -17382,7 +21136,7 @@ function layoutNetwork2(network, originX, originY) {
17382
21136
  FBD_CONST.block_min_height + FBD_CONST.network_label_h + FBD_CONST.network_padding_y * 2
17383
21137
  );
17384
21138
  return {
17385
- network,
21139
+ network: network2,
17386
21140
  x: originX,
17387
21141
  y: originY,
17388
21142
  width,
@@ -17443,7 +21197,7 @@ function layoutFbd(ast) {
17443
21197
  let cursorY = 0;
17444
21198
  let maxWidth = 0;
17445
21199
  for (const net of ast.networks) {
17446
- const ln = layoutNetwork2(net, 0, cursorY);
21200
+ const ln = layoutNetwork3(net, 0, cursorY);
17447
21201
  networks.push(ln);
17448
21202
  cursorY = ln.y + ln.height + FBD_CONST.network_gap_y;
17449
21203
  maxWidth = Math.max(maxWidth, ln.width);
@@ -17593,7 +21347,7 @@ function renderBlock(lb) {
17593
21347
  parts
17594
21348
  );
17595
21349
  }
17596
- function renderNetwork(ln) {
21350
+ function renderNetwork2(ln) {
17597
21351
  const parts = [];
17598
21352
  parts.push(rect({
17599
21353
  class: "lt-fbd-network-frame",
@@ -17659,7 +21413,7 @@ function renderFbdLayout(layout) {
17659
21413
  const descEl = desc(`FBD with ${layout.networks.length} network(s).`);
17660
21414
  const root = group(
17661
21415
  { transform: `translate(${margin},${margin})` },
17662
- layout.networks.map(renderNetwork)
21416
+ layout.networks.map(renderNetwork2)
17663
21417
  );
17664
21418
  return svgRoot(
17665
21419
  {
@@ -18644,7 +22398,10 @@ var plugins = [
18644
22398
  sfc,
18645
22399
  prisma,
18646
22400
  usecase,
18647
- pert
22401
+ pert,
22402
+ sequence,
22403
+ petri,
22404
+ network
18648
22405
  ];
18649
22406
  function detectPlugin(text2, config) {
18650
22407
  if (config?.type) {
@@ -18655,7 +22412,7 @@ function detectPlugin(text2, config) {
18655
22412
  if (plugin.detect(text2)) return plugin;
18656
22413
  }
18657
22414
  throw new Error(
18658
- "Cannot detect diagram type. Start your text with 'genogram', 'ecomap', 'pedigree', 'phylo', 'sociogram', 'timing', 'logic', 'circuit', 'blockdiagram', 'ladder', 'sld', 'entity-structure', 'fishbone', 'venn', 'flowchart', 'mindmap', 'matrix', 'orgchart', 'state', 'pid', 'erd', 'breadboard', 'bpmn', 'fbd', 'sfc', 'prisma', 'usecase', or 'pert'."
22415
+ "Cannot detect diagram type. Start your text with 'genogram', 'ecomap', 'pedigree', 'phylo', 'sociogram', 'timing', 'logic', 'circuit', 'blockdiagram', 'ladder', 'sld', 'entity-structure', 'fishbone', 'venn', 'flowchart', 'mindmap', 'matrix', 'orgchart', 'state', 'pid', 'erd', 'breadboard', 'bpmn', 'fbd', 'sfc', 'prisma', 'usecase', 'pert', 'sequence', 'petri', or 'network'."
18659
22416
  );
18660
22417
  }
18661
22418
  function preprocess8(text2) {
@@ -18680,9 +22437,80 @@ function parse(text2, config) {
18680
22437
  `Diagram type '${plugin.type}' does not yet expose a parse() method.`
18681
22438
  );
18682
22439
  }
22440
+ function parseResult(text2, config) {
22441
+ let plugin;
22442
+ try {
22443
+ const prepared = preprocess8(text2);
22444
+ plugin = detectPlugin(prepared, config);
22445
+ if (!plugin.parse) {
22446
+ throw new Error(
22447
+ `Diagram type '${plugin.type}' does not yet expose a parse() method.`
22448
+ );
22449
+ }
22450
+ const ast = plugin.parse(prepared);
22451
+ const diagnostics = runLint(plugin, prepared);
22452
+ return {
22453
+ ok: true,
22454
+ status: diagnostics.length > 0 ? "partial" : "valid",
22455
+ type: plugin.type,
22456
+ ast,
22457
+ diagnostics
22458
+ };
22459
+ } catch (err) {
22460
+ return {
22461
+ ok: false,
22462
+ status: "invalid",
22463
+ type: plugin?.type ?? config?.type ?? null,
22464
+ diagnostics: [diagnosticFromError(err)]
22465
+ };
22466
+ }
22467
+ }
22468
+ function runLint(plugin, prepared) {
22469
+ if (!plugin.lint) return [];
22470
+ try {
22471
+ return plugin.lint(prepared);
22472
+ } catch {
22473
+ return [];
22474
+ }
22475
+ }
18683
22476
  function render(text2, config) {
22477
+ if (config?.mode === "preview") return renderResult(text2, config).svg;
18684
22478
  const prepared = preprocess8(text2);
18685
22479
  const plugin = detectPlugin(prepared, config);
22480
+ return renderWithPlugin(prepared, plugin, config);
22481
+ }
22482
+ function renderResult(text2, config) {
22483
+ let plugin;
22484
+ try {
22485
+ const prepared = preprocess8(text2);
22486
+ plugin = detectPlugin(prepared, config);
22487
+ const svg = renderWithPlugin(prepared, plugin, config);
22488
+ const diagnostics = runLint(plugin, prepared);
22489
+ return {
22490
+ ok: true,
22491
+ status: diagnostics.length > 0 ? "partial" : "valid",
22492
+ type: plugin.type,
22493
+ svg,
22494
+ diagnostics
22495
+ };
22496
+ } catch (err) {
22497
+ const type = plugin?.type ?? config?.type ?? null;
22498
+ const diagnostics = [diagnosticFromError(err)];
22499
+ return {
22500
+ ok: false,
22501
+ status: "invalid",
22502
+ type,
22503
+ svg: renderDiagnosticSvg(diagnostics, type, {
22504
+ fontFamily: config?.fontFamily
22505
+ }),
22506
+ diagnostics
22507
+ };
22508
+ }
22509
+ }
22510
+ function renderPreview(text2, config) {
22511
+ return renderResult(text2, config).svg;
22512
+ }
22513
+ function renderWithPlugin(prepared, plugin, config) {
18686
22514
  const renderConfig = {
18687
22515
  fontFamily: config?.fontFamily ?? "system-ui, -apple-system, sans-serif",
18688
22516
  fontSize: 12,
@@ -18692,6 +22520,6 @@ function render(text2, config) {
18692
22520
  return plugin.render(prepared, renderConfig);
18693
22521
  }
18694
22522
 
18695
- export { decisiontree, parse, pert, pid, prisma, render, state, timeline, usecase };
18696
- //# sourceMappingURL=chunk-GTDQAN2Z.js.map
18697
- //# sourceMappingURL=chunk-GTDQAN2Z.js.map
22523
+ export { GEOMETRY, decisiontree, drawDeviceIcon, iconSize, network, parse, parseResult, pert, petri, pid, prisma, render, renderEquip, renderPreview, renderResult, sequence, state, timeline, usecase };
22524
+ //# sourceMappingURL=chunk-7AFW2J6J.js.map
22525
+ //# sourceMappingURL=chunk-7AFW2J6J.js.map