merslim 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,4292 @@
1
+ import dagre2 from 'dagre';
2
+ import { useRef, useMemo, useEffect, Fragment, useState, Suspense, lazy } from 'react';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __esm = (fn, res) => function __init() {
8
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
9
+ };
10
+ var __export = (target, all) => {
11
+ for (var name in all)
12
+ __defProp(target, name, { get: all[name], enumerable: true });
13
+ };
14
+ function layoutFlowchart(ir, options = {}) {
15
+ const defaultSize = options.defaultNodeSize ?? { width: 180, height: 60 };
16
+ const nodeSizes = options.nodeSizes ?? /* @__PURE__ */ new Map();
17
+ const nodeSep = options.nodeSeparation ?? 60;
18
+ const rankSep = options.rankSeparation ?? 80;
19
+ const margin = options.margin ?? 24;
20
+ const g = new dagre2.graphlib.Graph({ multigraph: true, compound: true });
21
+ g.setGraph({
22
+ rankdir: DAGRE_RANK_DIR[ir.direction],
23
+ nodesep: nodeSep,
24
+ ranksep: rankSep,
25
+ marginx: margin,
26
+ marginy: margin
27
+ });
28
+ g.setDefaultEdgeLabel(() => ({}));
29
+ const subgraphIds = /* @__PURE__ */ new Set();
30
+ if (ir.subgraphs) {
31
+ for (const sg of ir.subgraphs) {
32
+ g.setNode(sg.id, { label: sg.label, clusterLabelPos: "top" });
33
+ subgraphIds.add(sg.id);
34
+ }
35
+ }
36
+ const representativeChild = /* @__PURE__ */ new Map();
37
+ for (const node of ir.nodes) {
38
+ const size = nodeSizes.get(node.id) ?? defaultSize;
39
+ g.setNode(node.id, { ...size, label: node.label });
40
+ if (node.subgraph) {
41
+ g.setParent(node.id, node.subgraph);
42
+ if (!representativeChild.has(node.subgraph)) {
43
+ representativeChild.set(node.subgraph, node.id);
44
+ }
45
+ }
46
+ }
47
+ const resolveEndpoint = (id) => {
48
+ if (!subgraphIds.has(id)) return id;
49
+ return representativeChild.get(id) ?? null;
50
+ };
51
+ for (const edge of ir.edges) {
52
+ const source = resolveEndpoint(edge.source);
53
+ const target = resolveEndpoint(edge.target);
54
+ if (!source || !target) continue;
55
+ if (!g.hasNode(source) || !g.hasNode(target)) continue;
56
+ g.setEdge(source, target, edge.label ? { label: edge.label } : {});
57
+ }
58
+ dagre2.layout(g);
59
+ const nodePositions = /* @__PURE__ */ new Map();
60
+ let maxX = 0;
61
+ let maxY = 0;
62
+ for (const node of ir.nodes) {
63
+ const { x, y } = g.node(node.id);
64
+ const size = nodeSizes.get(node.id) ?? defaultSize;
65
+ const topLeft = { x: x - size.width / 2, y: y - size.height / 2 };
66
+ nodePositions.set(node.id, topLeft);
67
+ maxX = Math.max(maxX, topLeft.x + size.width);
68
+ maxY = Math.max(maxY, topLeft.y + size.height);
69
+ }
70
+ return {
71
+ nodePositions,
72
+ width: maxX + margin,
73
+ height: maxY + margin
74
+ };
75
+ }
76
+ var DAGRE_RANK_DIR;
77
+ var init_dagreLayout = __esm({
78
+ "src/utils/diagrams/layout/dagreLayout.ts"() {
79
+ DAGRE_RANK_DIR = {
80
+ TB: "TB",
81
+ BT: "BT",
82
+ LR: "LR",
83
+ RL: "RL"
84
+ };
85
+ }
86
+ });
87
+
88
+ // src/components/diagrams/shared/theme.ts
89
+ function getDiagramTheme(dark) {
90
+ return dark ? DARK : LIGHT;
91
+ }
92
+ var LIGHT, DARK;
93
+ var init_theme = __esm({
94
+ "src/components/diagrams/shared/theme.ts"() {
95
+ LIGHT = {
96
+ canvasBg: "#f8fafc",
97
+ // slate-50
98
+ edgeColor: "#94a3b8",
99
+ // slate-400
100
+ edgeLabel: "#475569",
101
+ // slate-600
102
+ edgeLabelBg: "#ffffff",
103
+ subgraphBg: "rgba(241, 245, 249, 0.5)",
104
+ // slate-100/50
105
+ subgraphBorder: "#cbd5e1",
106
+ // slate-300
107
+ subgraphLabel: "#475569",
108
+ nodeShadow: "rgba(15, 23, 42, 0.08)",
109
+ byKind: {
110
+ service: {
111
+ border: "#86efac",
112
+ headerBg: "#ecfdf5",
113
+ accent: "#10b981",
114
+ bodyBg: "#ffffff",
115
+ text: "#064e3b",
116
+ iconColor: "#10b981"
117
+ },
118
+ database: {
119
+ border: "#fcd34d",
120
+ headerBg: "#fffbeb",
121
+ accent: "#f59e0b",
122
+ bodyBg: "#ffffff",
123
+ text: "#78350f",
124
+ iconColor: "#f59e0b"
125
+ },
126
+ queue: {
127
+ border: "#fda4af",
128
+ headerBg: "#fff1f2",
129
+ accent: "#f43f5e",
130
+ bodyBg: "#ffffff",
131
+ text: "#881337",
132
+ iconColor: "#f43f5e"
133
+ },
134
+ storage: {
135
+ border: "#67e8f9",
136
+ headerBg: "#ecfeff",
137
+ accent: "#06b6d4",
138
+ bodyBg: "#ffffff",
139
+ text: "#164e63",
140
+ iconColor: "#06b6d4"
141
+ },
142
+ user: {
143
+ border: "#93c5fd",
144
+ headerBg: "#eff6ff",
145
+ accent: "#3b82f6",
146
+ bodyBg: "#ffffff",
147
+ text: "#1e3a8a",
148
+ iconColor: "#3b82f6"
149
+ },
150
+ client: {
151
+ border: "#c4b5fd",
152
+ headerBg: "#f5f3ff",
153
+ accent: "#8b5cf6",
154
+ bodyBg: "#ffffff",
155
+ text: "#4c1d95",
156
+ iconColor: "#8b5cf6"
157
+ },
158
+ external: {
159
+ border: "#cbd5e1",
160
+ headerBg: "#f1f5f9",
161
+ accent: "#64748b",
162
+ bodyBg: "#ffffff",
163
+ text: "#334155",
164
+ iconColor: "#64748b"
165
+ },
166
+ process: {
167
+ border: "#bfdbfe",
168
+ headerBg: "#eff6ff",
169
+ accent: "#60a5fa",
170
+ bodyBg: "#ffffff",
171
+ text: "#1e3a8a",
172
+ iconColor: "#60a5fa"
173
+ },
174
+ decision: {
175
+ border: "#c4b5fd",
176
+ headerBg: "#f5f3ff",
177
+ accent: "#8b5cf6",
178
+ bodyBg: "#faf5ff",
179
+ text: "#4c1d95",
180
+ iconColor: "#8b5cf6"
181
+ },
182
+ start: {
183
+ border: "#86efac",
184
+ headerBg: "#ecfdf5",
185
+ accent: "#10b981",
186
+ bodyBg: "#ffffff",
187
+ text: "#064e3b",
188
+ iconColor: "#10b981"
189
+ },
190
+ end: {
191
+ border: "#fda4af",
192
+ headerBg: "#fff1f2",
193
+ accent: "#f43f5e",
194
+ bodyBg: "#ffffff",
195
+ text: "#881337",
196
+ iconColor: "#f43f5e"
197
+ },
198
+ icon: {
199
+ border: "#cbd5e1",
200
+ headerBg: "#ffffff",
201
+ accent: "#64748b",
202
+ bodyBg: "#ffffff",
203
+ text: "#1e293b",
204
+ iconColor: "#64748b"
205
+ },
206
+ plain: {
207
+ border: "#cbd5e1",
208
+ headerBg: "#f8fafc",
209
+ accent: "#64748b",
210
+ bodyBg: "#ffffff",
211
+ text: "#1e293b",
212
+ iconColor: "#64748b"
213
+ }
214
+ }
215
+ };
216
+ DARK = {
217
+ canvasBg: "#0f172a",
218
+ edgeColor: "#64748b",
219
+ edgeLabel: "#cbd5e1",
220
+ edgeLabelBg: "#1e293b",
221
+ subgraphBg: "rgba(30, 41, 59, 0.5)",
222
+ subgraphBorder: "#475569",
223
+ subgraphLabel: "#94a3b8",
224
+ nodeShadow: "rgba(0, 0, 0, 0.30)",
225
+ byKind: {
226
+ service: {
227
+ border: "#10b981",
228
+ headerBg: "rgba(16, 185, 129, 0.12)",
229
+ accent: "#34d399",
230
+ bodyBg: "#1e293b",
231
+ text: "#a7f3d0",
232
+ iconColor: "#34d399"
233
+ },
234
+ database: {
235
+ border: "#f59e0b",
236
+ headerBg: "rgba(245, 158, 11, 0.12)",
237
+ accent: "#fbbf24",
238
+ bodyBg: "#1e293b",
239
+ text: "#fde68a",
240
+ iconColor: "#fbbf24"
241
+ },
242
+ queue: {
243
+ border: "#f43f5e",
244
+ headerBg: "rgba(244, 63, 94, 0.12)",
245
+ accent: "#fb7185",
246
+ bodyBg: "#1e293b",
247
+ text: "#fecdd3",
248
+ iconColor: "#fb7185"
249
+ },
250
+ storage: {
251
+ border: "#06b6d4",
252
+ headerBg: "rgba(6, 182, 212, 0.12)",
253
+ accent: "#22d3ee",
254
+ bodyBg: "#1e293b",
255
+ text: "#a5f3fc",
256
+ iconColor: "#22d3ee"
257
+ },
258
+ user: {
259
+ border: "#3b82f6",
260
+ headerBg: "rgba(59, 130, 246, 0.12)",
261
+ accent: "#60a5fa",
262
+ bodyBg: "#1e293b",
263
+ text: "#bfdbfe",
264
+ iconColor: "#60a5fa"
265
+ },
266
+ client: {
267
+ border: "#8b5cf6",
268
+ headerBg: "rgba(139, 92, 246, 0.12)",
269
+ accent: "#a78bfa",
270
+ bodyBg: "#1e293b",
271
+ text: "#ddd6fe",
272
+ iconColor: "#a78bfa"
273
+ },
274
+ external: {
275
+ border: "#64748b",
276
+ headerBg: "rgba(100, 116, 139, 0.12)",
277
+ accent: "#94a3b8",
278
+ bodyBg: "#1e293b",
279
+ text: "#cbd5e1",
280
+ iconColor: "#94a3b8"
281
+ },
282
+ process: {
283
+ border: "#60a5fa",
284
+ headerBg: "rgba(96, 165, 250, 0.12)",
285
+ accent: "#93c5fd",
286
+ bodyBg: "#1e293b",
287
+ text: "#bfdbfe",
288
+ iconColor: "#93c5fd"
289
+ },
290
+ decision: {
291
+ border: "#8b5cf6",
292
+ headerBg: "rgba(139, 92, 246, 0.12)",
293
+ accent: "#a78bfa",
294
+ bodyBg: "#1e293b",
295
+ text: "#ddd6fe",
296
+ iconColor: "#a78bfa"
297
+ },
298
+ start: {
299
+ border: "#10b981",
300
+ headerBg: "rgba(16, 185, 129, 0.12)",
301
+ accent: "#34d399",
302
+ bodyBg: "#1e293b",
303
+ text: "#a7f3d0",
304
+ iconColor: "#34d399"
305
+ },
306
+ end: {
307
+ border: "#f43f5e",
308
+ headerBg: "rgba(244, 63, 94, 0.12)",
309
+ accent: "#fb7185",
310
+ bodyBg: "#1e293b",
311
+ text: "#fecdd3",
312
+ iconColor: "#fb7185"
313
+ },
314
+ icon: {
315
+ border: "#475569",
316
+ headerBg: "#1e293b",
317
+ accent: "#94a3b8",
318
+ bodyBg: "#1e293b",
319
+ text: "#e2e8f0",
320
+ iconColor: "#94a3b8"
321
+ },
322
+ plain: {
323
+ border: "#475569",
324
+ headerBg: "#1e293b",
325
+ accent: "#94a3b8",
326
+ bodyBg: "#1e293b",
327
+ text: "#e2e8f0",
328
+ iconColor: "#94a3b8"
329
+ }
330
+ }
331
+ };
332
+ }
333
+ });
334
+ function escXml(s) {
335
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
336
+ }
337
+ function palette(dark) {
338
+ return {
339
+ common: dark ? DARK_COMMON : LIGHT_COMMON,
340
+ byKind: dark ? DARK_KIND : LIGHT_KIND
341
+ };
342
+ }
343
+ function flowchartNodeSize(node) {
344
+ if (node.kind === "user" || node.kind === "start" || node.kind === "end") {
345
+ return { width: 84, height: 84 };
346
+ }
347
+ if (node.kind === "icon") return { width: 100, height: 96 };
348
+ if (node.kind === "decision") {
349
+ const len2 = node.label.length;
350
+ return {
351
+ width: Math.max(140, Math.min(280, len2 * 11 + 60)),
352
+ height: Math.max(96, Math.min(160, Math.ceil(len2 / 16) * 32 + 64))
353
+ };
354
+ }
355
+ if (node.kind === "queue") return { width: 220, height: 64 };
356
+ const len = node.label.length;
357
+ const lines = Math.max(1, Math.ceil(len / 24));
358
+ return {
359
+ width: Math.max(160, Math.min(320, len * 8 + 40)),
360
+ height: 48 + (lines - 1) * 18
361
+ };
362
+ }
363
+ function wrapText(text, maxChars) {
364
+ if (text.length <= maxChars) return [text];
365
+ const words = text.split(/\s+/);
366
+ const lines = [];
367
+ let line = "";
368
+ for (const w of words) {
369
+ if ((line + " " + w).trim().length > maxChars && line) {
370
+ lines.push(line);
371
+ line = w;
372
+ } else {
373
+ line = (line + " " + w).trim();
374
+ }
375
+ }
376
+ if (line) lines.push(line);
377
+ return lines;
378
+ }
379
+ function tspans(lines, x, lineHeight) {
380
+ return lines.map((l, i) => `<tspan x="${x}" dy="${i === 0 ? 0 : lineHeight}">${escXml(l)}</tspan>`).join("");
381
+ }
382
+ function attachPoint(box, towards) {
383
+ const cx = box.x + box.width / 2;
384
+ const cy = box.y + box.height / 2;
385
+ const dx = towards.x - cx;
386
+ const dy = towards.y - cy;
387
+ if (dx === 0 && dy === 0) return { x: cx, y: cy };
388
+ const halfW = box.width / 2;
389
+ const halfH = box.height / 2;
390
+ const tx = Math.abs(dx) > 0 ? halfW / Math.abs(dx) : Infinity;
391
+ const ty = Math.abs(dy) > 0 ? halfH / Math.abs(dy) : Infinity;
392
+ const t = Math.min(tx, ty);
393
+ return { x: cx + dx * t, y: cy + dy * t };
394
+ }
395
+ function arrowDef(id, color) {
396
+ return `<marker id="${id}" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="${color}"/></marker>`;
397
+ }
398
+ function svgOpen(viewX, viewY, w, h, bg, title) {
399
+ const label = (title ?? "Diagram").trim() || "Diagram";
400
+ const titleEl = `<title>${escXml(label)}</title>`;
401
+ return `<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="${viewX} ${viewY} ${w} ${h}" width="${w}" height="${h}" font-family='${FONT_FAMILY}' role="img" aria-label="${escXml(label)}">${titleEl}<rect x="${viewX}" y="${viewY}" width="${w}" height="${h}" fill="${bg}"/>`;
402
+ }
403
+ function buildFlowchartSvg(ir, positions, options = {}) {
404
+ const { common, byKind } = palette(options.dark ?? false);
405
+ const padding = options.padding ?? 40;
406
+ const boxes = /* @__PURE__ */ new Map();
407
+ for (const node of ir.nodes) {
408
+ const pos = positions.get(node.id);
409
+ if (!pos) continue;
410
+ const size = flowchartNodeSize(node);
411
+ boxes.set(node.id, { x: pos.x, y: pos.y, width: size.width, height: size.height });
412
+ }
413
+ if (boxes.size === 0) return svgOpen(0, 0, 100, 100, common.canvasBg) + "</svg>";
414
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
415
+ for (const b of boxes.values()) {
416
+ minX = Math.min(minX, b.x);
417
+ minY = Math.min(minY, b.y);
418
+ maxX = Math.max(maxX, b.x + b.width);
419
+ maxY = Math.max(maxY, b.y + b.height);
420
+ }
421
+ minX -= padding;
422
+ minY -= padding;
423
+ maxX += padding;
424
+ maxY += padding;
425
+ const width = Math.round(maxX - minX);
426
+ const height = Math.round(maxY - minY);
427
+ const parts = [];
428
+ parts.push(svgOpen(minX, minY, width, height, common.canvasBg, "Flowchart diagram"));
429
+ parts.push(`<defs>${arrowDef("arr", common.edgeColor)}</defs>`);
430
+ for (const e of ir.edges) {
431
+ const a = boxes.get(e.source);
432
+ const b = boxes.get(e.target);
433
+ if (!a || !b) continue;
434
+ parts.push(buildEdgePath(a, b, e, common));
435
+ }
436
+ for (const node of ir.nodes) {
437
+ const box = boxes.get(node.id);
438
+ if (!box) continue;
439
+ parts.push(buildFlowNode(node, box, byKind[node.kind]));
440
+ }
441
+ parts.push("</svg>");
442
+ return parts.join("");
443
+ }
444
+ function buildEdgePath(a, b, edge, common) {
445
+ const ac = { x: a.x + a.width / 2, y: a.y + a.height / 2 };
446
+ const bc = { x: b.x + b.width / 2, y: b.y + b.height / 2 };
447
+ const p1 = attachPoint(a, bc);
448
+ const p2 = attachPoint(b, ac);
449
+ const dx = Math.abs(p2.x - p1.x);
450
+ const dy = Math.abs(p2.y - p1.y);
451
+ const horizontal = dx > dy;
452
+ const cp = Math.max(30, (horizontal ? dx : dy) * 0.45);
453
+ const c1 = horizontal ? { x: p1.x + Math.sign(p2.x - p1.x) * cp, y: p1.y } : { x: p1.x, y: p1.y + Math.sign(p2.y - p1.y) * cp };
454
+ const c2 = horizontal ? { x: p2.x - Math.sign(p2.x - p1.x) * cp, y: p2.y } : { x: p2.x, y: p2.y - Math.sign(p2.y - p1.y) * cp };
455
+ const dash = edge.kind === "dashed" ? ' stroke-dasharray="6 4"' : edge.kind === "dotted" ? ' stroke-dasharray="2 3"' : "";
456
+ const sw = edge.kind === "thick" ? 2.5 : 1.5;
457
+ const path = `<path d="M ${p1.x} ${p1.y} C ${c1.x} ${c1.y}, ${c2.x} ${c2.y}, ${p2.x} ${p2.y}" stroke="${common.edgeColor}" stroke-width="${sw}" fill="none"${dash} marker-end="url(#arr)"/>`;
458
+ if (!edge.label) return path;
459
+ const lx = 0.125 * p1.x + 0.375 * c1.x + 0.375 * c2.x + 0.125 * p2.x;
460
+ const ly = 0.125 * p1.y + 0.375 * c1.y + 0.375 * c2.y + 0.125 * p2.y;
461
+ const text = escXml(edge.label);
462
+ const w = text.length * 6.5 + 12;
463
+ return path + `<rect x="${lx - w / 2}" y="${ly - 9}" width="${w}" height="16" fill="${common.edgeLabelBg}" rx="3"/><text x="${lx}" y="${ly + 3}" text-anchor="middle" font-size="11" font-weight="500" fill="${common.edgeLabel}">${text}</text>`;
464
+ }
465
+ function buildFlowNode(node, box, c) {
466
+ const cx = box.x + box.width / 2;
467
+ const cy = box.y + box.height / 2;
468
+ if (node.kind === "decision") {
469
+ const left = box.x, right = box.x + box.width, top = box.y, bottom = box.y + box.height;
470
+ const points = `${cx},${top} ${right},${cy} ${cx},${bottom} ${left},${cy}`;
471
+ const lines2 = wrapText(node.label, 18);
472
+ return `<polygon points="${points}" fill="${c.bodyBg}" stroke="${c.accent}" stroke-width="1.5"/><text x="${cx}" y="${cy - (lines2.length - 1) * 7}" text-anchor="middle" font-size="12" font-weight="600" fill="${c.text}">${tspans(lines2, cx, 14)}</text>`;
473
+ }
474
+ if (node.kind === "database") {
475
+ const rx = box.width / 2;
476
+ const ry = 8;
477
+ const top = box.y;
478
+ const bottom = box.y + box.height;
479
+ const path = `M ${box.x} ${top + ry} A ${rx} ${ry} 0 0 0 ${box.x + box.width} ${top + ry} L ${box.x + box.width} ${bottom - ry} A ${rx} ${ry} 0 0 1 ${box.x} ${bottom - ry} Z`;
480
+ const ellipse = `<ellipse cx="${cx}" cy="${top + ry}" rx="${rx}" ry="${ry}" fill="none" stroke="${c.accent}" stroke-width="2"/>`;
481
+ return `<path d="${path}" fill="${c.bodyBg}" stroke="${c.border}" stroke-width="1"/>` + ellipse + `<text x="${cx}" y="${cy + 6}" text-anchor="middle" font-size="13" font-weight="500" fill="${c.text}">${escXml(node.label)}</text>`;
482
+ }
483
+ if (node.kind === "user" || node.kind === "start" || node.kind === "end") {
484
+ const r = Math.min(box.width, box.height) / 2;
485
+ const lines2 = wrapText(node.label, 12);
486
+ return `<circle cx="${cx}" cy="${cy}" r="${r}" fill="${c.bodyBg}" stroke="${c.accent}" stroke-width="2"/><text x="${cx}" y="${cy + 4 - (lines2.length - 1) * 7}" text-anchor="middle" font-size="12" font-weight="600" fill="${c.text}">${tspans(lines2, cx, 14)}</text>`;
487
+ }
488
+ if (node.kind === "queue") {
489
+ return `<rect x="${box.x}" y="${box.y}" width="${box.width}" height="${box.height}" rx="8" fill="${c.bodyBg}" stroke="${c.accent}" stroke-width="1"/><rect x="${box.x + 4}" y="${box.y + 4}" width="${box.width - 8}" height="${box.height - 8}" rx="5" fill="none" stroke="${c.border}" stroke-width="1"/><text x="${cx}" y="${cy + 5}" text-anchor="middle" font-size="13" font-weight="500" fill="${c.text}">${escXml(node.label)}</text>`;
490
+ }
491
+ const lines = wrapText(node.label, 24);
492
+ return `<rect x="${box.x}" y="${box.y}" width="${box.width}" height="${box.height}" rx="10" fill="${c.bodyBg}" stroke="${c.border}" stroke-width="1"/><rect x="${box.x}" y="${box.y}" width="4" height="${box.height}" fill="${c.accent}"/><text x="${cx}" y="${cy + 4 - (lines.length - 1) * 8}" text-anchor="middle" font-size="13" font-weight="500" fill="${c.text}">${tspans(lines, cx, 16)}</text>`;
493
+ }
494
+ function buildStateSvg(ir, positions, options = {}) {
495
+ const { common } = palette(options.dark ?? false);
496
+ const dark = options.dark ?? false;
497
+ const padding = options.padding ?? 40;
498
+ const boxes = /* @__PURE__ */ new Map();
499
+ for (const [id, p] of positions.topLevel) {
500
+ boxes.set(id, p);
501
+ }
502
+ for (const [id, p] of positions.children) {
503
+ const parent = boxes.get(p.parent);
504
+ if (!parent) continue;
505
+ boxes.set(id, { x: parent.x + p.x, y: parent.y + p.y, width: p.width, height: p.height });
506
+ }
507
+ if (boxes.size === 0) return svgOpen(0, 0, 100, 100, common.canvasBg) + "</svg>";
508
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
509
+ for (const b of boxes.values()) {
510
+ minX = Math.min(minX, b.x);
511
+ minY = Math.min(minY, b.y);
512
+ maxX = Math.max(maxX, b.x + b.width);
513
+ maxY = Math.max(maxY, b.y + b.height);
514
+ }
515
+ minX -= padding;
516
+ minY -= padding;
517
+ maxX += padding;
518
+ maxY += padding;
519
+ const width = Math.round(maxX - minX);
520
+ const height = Math.round(maxY - minY);
521
+ const parts = [];
522
+ parts.push(svgOpen(minX, minY, width, height, common.canvasBg, "State diagram"));
523
+ parts.push(`<defs>${arrowDef("arr", common.edgeColor)}</defs>`);
524
+ for (const s of ir.states) {
525
+ if (s.kind !== "composite") continue;
526
+ const box = boxes.get(s.id);
527
+ if (!box) continue;
528
+ parts.push(
529
+ `<rect x="${box.x}" y="${box.y}" width="${box.width}" height="${box.height}" rx="14" fill="${dark ? "rgba(15,23,42,0.5)" : "#ffffff"}" stroke="${common.border}" stroke-width="1.5"/><rect x="${box.x}" y="${box.y}" width="${box.width}" height="32" rx="14" fill="${dark ? "#1e293b" : "#f1f5f9"}"/><rect x="${box.x}" y="${box.y + 18}" width="${box.width}" height="14" fill="${dark ? "#1e293b" : "#f1f5f9"}"/><line x1="${box.x}" y1="${box.y + 32}" x2="${box.x + box.width}" y2="${box.y + 32}" stroke="${common.border}" stroke-width="1"/><text x="${box.x + box.width / 2}" y="${box.y + 21}" text-anchor="middle" font-size="13" font-weight="600" fill="${common.text}">${escXml(s.label || s.id)}</text>`
530
+ );
531
+ }
532
+ for (const t of ir.transitions) {
533
+ const a = boxes.get(t.source);
534
+ const b = boxes.get(t.target);
535
+ if (!a || !b) continue;
536
+ parts.push(buildEdgePath(a, b, { source: t.source, target: t.target, label: t.label, kind: "solid" }, common));
537
+ }
538
+ for (const s of ir.states) {
539
+ if (s.kind === "composite") continue;
540
+ const box = boxes.get(s.id);
541
+ if (!box) continue;
542
+ parts.push(buildStateBox(s, box, common, dark));
543
+ }
544
+ parts.push("</svg>");
545
+ return parts.join("");
546
+ }
547
+ function buildStateBox(s, box, common, dark) {
548
+ if (s.kind === "start" || s.kind === "end") {
549
+ const cx2 = box.x + box.width / 2;
550
+ const cy2 = box.y + box.height / 2;
551
+ const r = 12;
552
+ const fill = s.kind === "start" ? dark ? "#e2e8f0" : "#0f172a" : dark ? "#0f172a" : "#ffffff";
553
+ const stroke = dark ? "#e2e8f0" : "#0f172a";
554
+ const inner = s.kind === "end" ? `<circle cx="${cx2}" cy="${cy2}" r="${r - 4}" fill="${dark ? "#e2e8f0" : "#0f172a"}"/>` : "";
555
+ return `<circle cx="${cx2}" cy="${cy2}" r="${r}" fill="${fill}" stroke="${stroke}" stroke-width="2"/>${inner}`;
556
+ }
557
+ const cx = box.x + box.width / 2;
558
+ const cy = box.y + box.height / 2;
559
+ return `<rect x="${box.x}" y="${box.y}" width="${box.width}" height="${box.height}" rx="14" fill="${dark ? "#1e293b" : "#ffffff"}" stroke="${common.border}" stroke-width="1.5"/><text x="${cx}" y="${cy + 5}" text-anchor="middle" font-size="13" font-weight="500" fill="${common.text}">${escXml(s.label || s.id)}</text>`;
560
+ }
561
+ function buildClassSvg(ir, positions, options = {}) {
562
+ const { common } = palette(options.dark ?? false);
563
+ const dark = options.dark ?? false;
564
+ const padding = options.padding ?? 40;
565
+ if (positions.size === 0) return svgOpen(0, 0, 100, 100, common.canvasBg) + "</svg>";
566
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
567
+ for (const p of positions.values()) {
568
+ minX = Math.min(minX, p.x);
569
+ minY = Math.min(minY, p.y);
570
+ maxX = Math.max(maxX, p.x + p.width);
571
+ maxY = Math.max(maxY, p.y + p.height);
572
+ }
573
+ minX -= padding;
574
+ minY -= padding;
575
+ maxX += padding;
576
+ maxY += padding;
577
+ const width = Math.round(maxX - minX);
578
+ const height = Math.round(maxY - minY);
579
+ const parts = [];
580
+ parts.push(svgOpen(minX, minY, width, height, common.canvasBg, "Class diagram"));
581
+ parts.push(`<defs>${arrowDef("arr", common.edgeColor)}</defs>`);
582
+ for (const rel of ir.relations) {
583
+ const a = positions.get(rel.source);
584
+ const b = positions.get(rel.target);
585
+ if (!a || !b) continue;
586
+ const aBox = { x: a.x, y: a.y, width: a.width, height: a.height };
587
+ const bBox = { x: b.x, y: b.y, width: b.width, height: b.height };
588
+ const dashed = rel.kind === "dependency" || rel.kind === "realization";
589
+ parts.push(buildEdgePath(aBox, bBox, { source: rel.source, target: rel.target, label: rel.label, kind: dashed ? "dashed" : "solid" }, common));
590
+ }
591
+ for (const cls of ir.classes) {
592
+ const p = positions.get(cls.id);
593
+ if (!p) continue;
594
+ parts.push(buildClassNode(cls, p, common, dark));
595
+ }
596
+ parts.push("</svg>");
597
+ return parts.join("");
598
+ }
599
+ function buildClassNode(cls, p, common, dark) {
600
+ const headerH = 30;
601
+ const rowH = 18;
602
+ const attrs = cls.members.filter((m) => m.kind === "attribute");
603
+ const methods = cls.members.filter((m) => m.kind === "method");
604
+ const parts = [];
605
+ parts.push(`<g transform="translate(${p.x},${p.y})">`);
606
+ parts.push(`<rect width="${p.width}" height="${p.height}" rx="8" fill="${dark ? "#0f172a" : "#ffffff"}" stroke="${common.border}" stroke-width="1"/>`);
607
+ parts.push(`<rect width="${p.width}" height="${headerH}" rx="8" fill="${dark ? "#1e293b" : "#f1f5f9"}"/>`);
608
+ parts.push(`<rect y="${headerH - 8}" width="${p.width}" height="8" fill="${dark ? "#1e293b" : "#f1f5f9"}"/>`);
609
+ parts.push(`<line x1="0" y1="${headerH}" x2="${p.width}" y2="${headerH}" stroke="${common.border}"/>`);
610
+ parts.push(`<text x="${p.width / 2}" y="${headerH / 2 + 5}" text-anchor="middle" font-size="13" font-weight="600" fill="${common.text}">${escXml(cls.label)}</text>`);
611
+ let y = headerH + 14;
612
+ for (const m of attrs) {
613
+ parts.push(buildClassMember(m, y, p.width, common));
614
+ y += rowH;
615
+ }
616
+ if (attrs.length > 0 && methods.length > 0) {
617
+ parts.push(`<line x1="6" y1="${y - 6}" x2="${p.width - 6}" y2="${y - 6}" stroke="${common.border}" stroke-dasharray="3 2"/>`);
618
+ y += 4;
619
+ }
620
+ for (const m of methods) {
621
+ parts.push(buildClassMember(m, y, p.width, common));
622
+ y += rowH;
623
+ }
624
+ parts.push("</g>");
625
+ return parts.join("");
626
+ }
627
+ function buildClassMember(m, y, width, common) {
628
+ const sym = m.visibility ? VIS_SYMBOL[m.visibility] ?? "" : "";
629
+ const sig = m.kind === "method" ? `${m.name}(${m.parameters ?? ""})${m.returnType ? `: ${m.returnType}` : ""}` : `${m.name}${m.returnType ? `: ${m.returnType}` : ""}`;
630
+ return `<text x="10" y="${y}" font-family='${MONO_FAMILY}' font-size="11" fill="${common.subtle}">${escXml(sym)}</text><text x="22" y="${y}" font-family='${MONO_FAMILY}' font-size="11" fill="${common.text}">${escXml(sig.length > 32 ? sig.slice(0, 31) + "\u2026" : sig)}</text>`;
631
+ }
632
+ function buildErSvg(ir, positions, options = {}) {
633
+ const { common } = palette(options.dark ?? false);
634
+ const dark = options.dark ?? false;
635
+ const padding = options.padding ?? 40;
636
+ const HEADER_H = 34;
637
+ const ROW_H = 26;
638
+ if (positions.size === 0) return svgOpen(0, 0, 100, 100, common.canvasBg) + "</svg>";
639
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
640
+ for (const p of positions.values()) {
641
+ minX = Math.min(minX, p.x);
642
+ minY = Math.min(minY, p.y);
643
+ maxX = Math.max(maxX, p.x + p.width);
644
+ maxY = Math.max(maxY, p.y + p.height);
645
+ }
646
+ minX -= padding;
647
+ minY -= padding;
648
+ maxX += padding;
649
+ maxY += padding;
650
+ const width = Math.round(maxX - minX);
651
+ const height = Math.round(maxY - minY);
652
+ const parts = [];
653
+ parts.push(svgOpen(minX, minY, width, height, common.canvasBg, "Entity-relationship diagram"));
654
+ parts.push(`<defs>${arrowDef("arr", common.edgeColor)}</defs>`);
655
+ for (const rel of ir.schema.relations) {
656
+ const fromTable = ir.schema.tables.find((t) => t.name === rel.fromTable);
657
+ const toTable = ir.schema.tables.find((t) => t.name === rel.toTable);
658
+ if (!fromTable || !toTable) continue;
659
+ const fromPos = positions.get(rel.fromTable);
660
+ const toPos = positions.get(rel.toTable);
661
+ if (!fromPos || !toPos) continue;
662
+ const fromColIdx = fromTable.columns.findIndex((c) => c.name === rel.fromCol);
663
+ const toColIdx = toTable.columns.findIndex((c) => c.name === rel.toCol);
664
+ const fy = fromColIdx >= 0 ? fromPos.y + HEADER_H + fromColIdx * ROW_H + ROW_H / 2 : fromPos.y + fromPos.height / 2;
665
+ const ty = toColIdx >= 0 ? toPos.y + HEADER_H + toColIdx * ROW_H + ROW_H / 2 : toPos.y + toPos.height / 2;
666
+ const goRight = toPos.x + toPos.width / 2 > fromPos.x + fromPos.width / 2;
667
+ const fx = goRight ? fromPos.x + fromPos.width : fromPos.x;
668
+ const tx = goRight ? toPos.x : toPos.x + toPos.width;
669
+ const cp = Math.max(40, Math.abs(tx - fx) * 0.55);
670
+ const c1x = goRight ? fx + cp : fx - cp;
671
+ const c2x = goRight ? tx - cp : tx + cp;
672
+ parts.push(`<path d="M ${fx} ${fy} C ${c1x} ${fy}, ${c2x} ${ty}, ${tx} ${ty}" stroke="${common.edgeColor}" stroke-width="1.4" fill="none" stroke-dasharray="5 4" marker-end="url(#arr)"/>`);
673
+ const lx = (fx + tx) / 2;
674
+ const ly = (fy + ty) / 2 - 5;
675
+ parts.push(`<text x="${lx}" y="${ly}" font-size="9" font-style="italic" fill="${common.subtle}" text-anchor="middle">${escXml(fromColIdx >= 0 ? "FK" : rel.fromCol)}</text>`);
676
+ }
677
+ for (const table of ir.schema.tables) {
678
+ const p = positions.get(table.name);
679
+ if (!p) continue;
680
+ parts.push(`<g transform="translate(${p.x},${p.y})">`);
681
+ parts.push(`<rect width="${p.width}" height="${p.height}" rx="8" fill="${dark ? "#0f172a" : "#ffffff"}" stroke="${common.border}" stroke-width="1"/>`);
682
+ parts.push(`<rect width="${p.width}" height="${HEADER_H}" rx="8" fill="${dark ? "#1e293b" : "#f8fafc"}"/>`);
683
+ parts.push(`<rect y="${HEADER_H - 8}" width="${p.width}" height="8" fill="${dark ? "#1e293b" : "#f8fafc"}"/>`);
684
+ parts.push(`<line x1="0" y1="${HEADER_H}" x2="${p.width}" y2="${HEADER_H}" stroke="${common.border}"/>`);
685
+ parts.push(`<text x="12" y="${HEADER_H / 2 + 5}" font-family='${MONO_FAMILY}' font-size="12" font-weight="600" fill="${common.text}">${escXml(table.name)}</text>`);
686
+ for (let i = 0; i < table.columns.length; i++) {
687
+ const col = table.columns[i];
688
+ const rowY = HEADER_H + i * ROW_H;
689
+ const textY = rowY + ROW_H / 2 + 3;
690
+ if (i > 0) {
691
+ parts.push(`<line x1="8" y1="${rowY}" x2="${p.width - 8}" y2="${rowY}" stroke="${dark ? "#1e293b" : "#f1f5f9"}"/>`);
692
+ }
693
+ const nameColor = col.isPK ? "#d97706" : col.isFK ? "#0284c7" : dark ? "#cbd5e1" : "#475569";
694
+ const marker = col.isPK ? "\u{1F511}" : col.isFK ? "\u2197" : "\xB7";
695
+ parts.push(`<text x="14" y="${textY}" font-size="10" fill="${nameColor}">${marker}</text>`);
696
+ parts.push(`<text x="28" y="${textY}" font-family='${MONO_FAMILY}' font-size="11" font-weight="${col.isPK ? "600" : "400"}" fill="${nameColor}">${escXml(col.name)}</text>`);
697
+ parts.push(`<text x="${p.width - 12}" y="${textY}" text-anchor="end" font-family='${MONO_FAMILY}' font-size="10" fill="${common.subtle}">${escXml(col.type)}</text>`);
698
+ }
699
+ parts.push("</g>");
700
+ }
701
+ parts.push("</svg>");
702
+ return parts.join("");
703
+ }
704
+ function buildMindmapSvg(ir, positions, options = {}) {
705
+ const { common } = palette(options.dark ?? false);
706
+ const dark = options.dark ?? false;
707
+ const padding = options.padding ?? 60;
708
+ if (positions.size === 0) return svgOpen(0, 0, 100, 100, common.canvasBg) + "</svg>";
709
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
710
+ for (const p of positions.values()) {
711
+ minX = Math.min(minX, p.x);
712
+ minY = Math.min(minY, p.y);
713
+ maxX = Math.max(maxX, p.x + p.width);
714
+ maxY = Math.max(maxY, p.y + p.height);
715
+ }
716
+ minX -= padding;
717
+ minY -= padding;
718
+ maxX += padding;
719
+ maxY += padding;
720
+ const width = Math.round(maxX - minX);
721
+ const height = Math.round(maxY - minY);
722
+ const parts = [];
723
+ parts.push(svgOpen(minX, minY, width, height, common.canvasBg, ir.root.label || "Mindmap"));
724
+ const collect = (node) => {
725
+ const p = positions.get(node.id);
726
+ if (!p) return;
727
+ for (const c of node.children) {
728
+ const cp = positions.get(c.id);
729
+ if (!cp) continue;
730
+ const fx = p.x + p.width / 2;
731
+ const fy = p.y + p.height / 2;
732
+ const tx = cp.x + cp.width / 2;
733
+ const ty = cp.y + cp.height / 2;
734
+ const c1x = fx + (tx - fx) * 0.4;
735
+ const c2x = fx + (tx - fx) * 0.6;
736
+ parts.push(`<path d="M ${fx} ${fy} C ${c1x} ${fy}, ${c2x} ${ty}, ${tx} ${ty}" stroke="${common.edgeColor}" stroke-width="1.5" fill="none"/>`);
737
+ collect(c);
738
+ }
739
+ };
740
+ collect(ir.root);
741
+ const palettes = [
742
+ dark ? { bg: "#e2e8f0", border: "#f8fafc", text: "#0f172a" } : { bg: "#1e293b", border: "#0f172a", text: "#f8fafc" },
743
+ dark ? { bg: "rgba(59,130,246,0.20)", border: "#60a5fa", text: "#bfdbfe" } : { bg: "#dbeafe", border: "#3b82f6", text: "#1e3a8a" },
744
+ dark ? { bg: "rgba(16,185,129,0.20)", border: "#34d399", text: "#a7f3d0" } : { bg: "#dcfce7", border: "#10b981", text: "#064e3b" },
745
+ dark ? { bg: "rgba(245,158,11,0.20)", border: "#fbbf24", text: "#fde68a" } : { bg: "#fef3c7", border: "#f59e0b", text: "#78350f" },
746
+ dark ? { bg: "rgba(244,63,94,0.20)", border: "#fb7185", text: "#fecdd3" } : { bg: "#fee2e2", border: "#f43f5e", text: "#881337" },
747
+ dark ? { bg: "rgba(139,92,246,0.20)", border: "#a78bfa", text: "#ddd6fe" } : { bg: "#ede9fe", border: "#8b5cf6", text: "#4c1d95" }
748
+ ];
749
+ const renderNode = (node) => {
750
+ const p = positions.get(node.id);
751
+ if (!p) return;
752
+ const c = palettes[Math.min(p.depth, palettes.length - 1)];
753
+ const isRoot = p.depth === 0;
754
+ const radius = node.shape === "circle" ? Math.min(p.width, p.height) / 2 : node.shape === "rounded" ? 999 : node.shape === "square" ? 4 : 12;
755
+ parts.push(
756
+ `<rect x="${p.x}" y="${p.y}" width="${p.width}" height="${p.height}" rx="${radius}" ry="${radius}" fill="${c.bg}" stroke="${c.border}" stroke-width="2"/>`
757
+ );
758
+ parts.push(
759
+ `<text x="${p.x + p.width / 2}" y="${p.y + p.height / 2 + 4}" text-anchor="middle" font-size="${isRoot ? 14 : 12}" font-weight="${isRoot ? 700 : 500}" fill="${c.text}">${escXml(node.label)}</text>`
760
+ );
761
+ for (const child of node.children) renderNode(child);
762
+ };
763
+ renderNode(ir.root);
764
+ parts.push("</svg>");
765
+ return parts.join("");
766
+ }
767
+ function buildGanttSvg(ir, options = {}) {
768
+ const { common } = palette(options.dark ?? false);
769
+ const dark = options.dark ?? false;
770
+ const padding = options.padding ?? 24;
771
+ if (ir.tasks.length === 0) return svgOpen(0, 0, 200, 60, common.canvasBg) + "</svg>";
772
+ const sections = /* @__PURE__ */ new Map();
773
+ for (const t of ir.tasks) {
774
+ const s = t.section ?? "Tasks";
775
+ if (!sections.has(s)) sections.set(s, []);
776
+ sections.get(s).push(t);
777
+ }
778
+ const rowH = 30;
779
+ const sectionHeaderH = 24;
780
+ const labelW = 160;
781
+ const chartW = 760;
782
+ const headerH = 36;
783
+ const totalRows = ir.tasks.length;
784
+ const totalSections = sections.size;
785
+ const titleH = ir.title ? 32 : 0;
786
+ const bodyH = headerH + totalSections * sectionHeaderH + totalRows * rowH;
787
+ const width = padding * 2 + labelW + chartW;
788
+ const height = padding * 2 + titleH + bodyH;
789
+ let minTime = Infinity, maxTime = -Infinity;
790
+ for (const t of ir.tasks) {
791
+ minTime = Math.min(minTime, new Date(t.start).getTime());
792
+ maxTime = Math.max(maxTime, new Date(t.end).getTime());
793
+ }
794
+ if (!isFinite(minTime) || !isFinite(maxTime) || maxTime === minTime) maxTime = minTime + 864e5;
795
+ const timeToX = (t) => padding + labelW + (t - minTime) / (maxTime - minTime) * chartW;
796
+ const colors = dark ? {
797
+ default: { fill: "rgba(59,130,246,0.30)", stroke: "#60a5fa", text: "#bfdbfe" },
798
+ active: { fill: "rgba(245,158,11,0.30)", stroke: "#fbbf24", text: "#fde68a" },
799
+ done: { fill: "rgba(16,185,129,0.30)", stroke: "#34d399", text: "#a7f3d0" },
800
+ crit: { fill: "rgba(244,63,94,0.30)", stroke: "#fb7185", text: "#fecdd3" },
801
+ milestone: { fill: "rgba(139,92,246,0.40)", stroke: "#a78bfa", text: "#ddd6fe" }
802
+ } : {
803
+ default: { fill: "#bfdbfe", stroke: "#3b82f6", text: "#1e3a8a" },
804
+ active: { fill: "#fde68a", stroke: "#f59e0b", text: "#78350f" },
805
+ done: { fill: "#a7f3d0", stroke: "#10b981", text: "#064e3b" },
806
+ crit: { fill: "#fecaca", stroke: "#f43f5e", text: "#881337" },
807
+ milestone: { fill: "#ddd6fe", stroke: "#8b5cf6", text: "#4c1d95" }
808
+ };
809
+ const parts = [];
810
+ parts.push(svgOpen(0, 0, width, height, common.canvasBg, ir.title || "Gantt chart"));
811
+ if (ir.title) {
812
+ parts.push(`<text x="${width / 2}" y="22" text-anchor="middle" font-size="15" font-weight="600" fill="${common.text}">${escXml(ir.title)}</text>`);
813
+ }
814
+ const axisY = padding + titleH + headerH - 4;
815
+ for (const ratio of [0, 0.5, 1]) {
816
+ const x = padding + labelW + ratio * chartW;
817
+ const time = minTime + (maxTime - minTime) * ratio;
818
+ const date = new Date(time);
819
+ const label = `${date.getMonth() + 1}/${date.getDate()}/${String(date.getFullYear()).slice(2)}`;
820
+ parts.push(`<text x="${x}" y="${axisY}" text-anchor="middle" font-size="10" fill="${common.subtle}">${escXml(label)}</text>`);
821
+ parts.push(`<line x1="${x}" y1="${axisY + 4}" x2="${x}" y2="${padding + titleH + bodyH}" stroke="${common.border}" stroke-dasharray="2 3"/>`);
822
+ }
823
+ let cy = padding + titleH + headerH;
824
+ for (const [section, tasks] of sections) {
825
+ parts.push(`<rect x="${padding}" y="${cy}" width="${labelW + chartW}" height="${sectionHeaderH}" fill="${dark ? "#1e293b" : "#f8fafc"}"/>`);
826
+ parts.push(`<text x="${padding + 8}" y="${cy + 16}" font-size="11" font-weight="600" fill="${common.text}">${escXml(section)}</text>`);
827
+ cy += sectionHeaderH;
828
+ for (const t of tasks) {
829
+ const x1 = timeToX(new Date(t.start).getTime());
830
+ const x2 = timeToX(new Date(t.end).getTime());
831
+ const c = colors[t.status] ?? colors.default;
832
+ parts.push(`<text x="${padding + 8}" y="${cy + rowH / 2 + 4}" font-size="11" fill="${common.text}">${escXml(t.label)}</text>`);
833
+ if (t.status === "milestone") {
834
+ const cx = x1;
835
+ const my = cy + rowH / 2;
836
+ parts.push(`<polygon points="${cx},${my - 7} ${cx + 7},${my} ${cx},${my + 7} ${cx - 7},${my}" fill="${c.fill}" stroke="${c.stroke}" stroke-width="1.5"/>`);
837
+ } else {
838
+ const w = Math.max(4, x2 - x1);
839
+ parts.push(`<rect x="${x1}" y="${cy + 6}" width="${w}" height="${rowH - 12}" rx="4" fill="${c.fill}" stroke="${c.stroke}" stroke-width="1"/>`);
840
+ }
841
+ cy += rowH;
842
+ }
843
+ }
844
+ parts.push("</svg>");
845
+ return parts.join("");
846
+ }
847
+ function buildTimelineSvg(ir, options = {}) {
848
+ const { common } = palette(options.dark ?? false);
849
+ const dark = options.dark ?? false;
850
+ const padding = options.padding ?? 24;
851
+ if (ir.events.length === 0) return svgOpen(0, 0, 200, 60, common.canvasBg) + "</svg>";
852
+ const sectionsMap = /* @__PURE__ */ new Map();
853
+ for (const e of ir.events) {
854
+ const s = e.section ?? "";
855
+ if (!sectionsMap.has(s)) sectionsMap.set(s, []);
856
+ sectionsMap.get(s).push(e);
857
+ }
858
+ const titleH = ir.title ? 32 : 0;
859
+ const sectionHeaderH = 28;
860
+ const eventH = 56;
861
+ const rows = [];
862
+ for (const [section, events] of sectionsMap) {
863
+ if (section) rows.push({ type: "section", data: section });
864
+ for (const e of events) rows.push({ type: "event", data: e });
865
+ }
866
+ const lineX = padding + 80;
867
+ const eventBoxX = lineX + 24;
868
+ const eventBoxW = 480;
869
+ const width = padding * 2 + 80 + 24 + eventBoxW;
870
+ let height = padding * 2 + titleH;
871
+ for (const r of rows) height += r.type === "section" ? sectionHeaderH : eventH;
872
+ height += 20;
873
+ const sectionColors = ["#3b82f6", "#10b981", "#f59e0b", "#f43f5e", "#8b5cf6", "#06b6d4"];
874
+ const parts = [];
875
+ parts.push(svgOpen(0, 0, width, height, common.canvasBg, ir.title || "Timeline"));
876
+ if (ir.title) {
877
+ parts.push(`<text x="${width / 2}" y="22" text-anchor="middle" font-size="15" font-weight="600" fill="${common.text}">${escXml(ir.title)}</text>`);
878
+ }
879
+ parts.push(`<line x1="${lineX}" y1="${padding + titleH}" x2="${lineX}" y2="${height - padding}" stroke="${common.border}" stroke-width="2"/>`);
880
+ let cy = padding + titleH + 8;
881
+ let sectionIndex = -1;
882
+ for (const r of rows) {
883
+ if (r.type === "section") {
884
+ sectionIndex++;
885
+ parts.push(`<text x="${padding}" y="${cy + 16}" font-size="11" font-weight="700" fill="${common.subtle}">${escXml(r.data)}</text>`);
886
+ cy += sectionHeaderH;
887
+ } else {
888
+ const e = r.data;
889
+ const color = sectionColors[Math.max(0, sectionIndex) % sectionColors.length];
890
+ parts.push(`<circle cx="${lineX}" cy="${cy + eventH / 2}" r="6" fill="${color}" stroke="${common.canvasBg}" stroke-width="2"/>`);
891
+ parts.push(`<text x="${lineX - 12}" y="${cy + eventH / 2 + 4}" text-anchor="end" font-size="11" font-weight="600" fill="${common.text}">${escXml(e.period)}</text>`);
892
+ parts.push(`<rect x="${eventBoxX}" y="${cy + 4}" width="${eventBoxW}" height="${eventH - 12}" rx="8" fill="${dark ? "rgba(30,41,59,0.6)" : "#f8fafc"}" stroke="${color}" stroke-width="1"/>`);
893
+ const lines = wrapText(e.text, 60);
894
+ for (let i = 0; i < Math.min(lines.length, 3); i++) {
895
+ parts.push(`<text x="${eventBoxX + 12}" y="${cy + 22 + i * 14}" font-size="11" fill="${common.text}">${escXml(lines[i])}</text>`);
896
+ }
897
+ cy += eventH;
898
+ }
899
+ }
900
+ parts.push("</svg>");
901
+ return parts.join("");
902
+ }
903
+ function buildPieSvg(ir, options = {}) {
904
+ const { common } = palette(options.dark ?? false);
905
+ const padding = options.padding ?? 20;
906
+ const titleH = ir.title ? 32 : 0;
907
+ const legendW = 200;
908
+ const radius = 160;
909
+ const cx = padding + radius + 16;
910
+ const cy = padding + titleH + radius + 16;
911
+ const width = padding * 2 + radius * 2 + 32 + legendW;
912
+ const height = padding * 2 + titleH + radius * 2 + 32;
913
+ const total = ir.slices.reduce((s, sl) => s + sl.value, 0);
914
+ const parts = [];
915
+ parts.push(svgOpen(0, 0, width, height, common.canvasBg, ir.title || "Pie chart"));
916
+ if (ir.title) {
917
+ parts.push(`<text x="${width / 2}" y="22" text-anchor="middle" font-size="16" font-weight="600" fill="${common.text}">${escXml(ir.title)}</text>`);
918
+ }
919
+ if (total === 0 || ir.slices.length === 0) {
920
+ parts.push(`<circle cx="${cx}" cy="${cy}" r="${radius}" fill="${common.border}"/>`);
921
+ parts.push("</svg>");
922
+ return parts.join("");
923
+ }
924
+ let startAngle = -Math.PI / 2;
925
+ ir.slices.forEach((slice, i) => {
926
+ const fraction = slice.value / total;
927
+ const endAngle = startAngle + fraction * Math.PI * 2;
928
+ const fill = PIE_PALETTE[i % PIE_PALETTE.length];
929
+ if (fraction >= 0.999) {
930
+ parts.push(`<circle cx="${cx}" cy="${cy}" r="${radius}" fill="${fill}" stroke="${common.canvasBg}" stroke-width="2"/>`);
931
+ } else {
932
+ const x1 = cx + Math.cos(startAngle) * radius;
933
+ const y1 = cy + Math.sin(startAngle) * radius;
934
+ const x2 = cx + Math.cos(endAngle) * radius;
935
+ const y2 = cy + Math.sin(endAngle) * radius;
936
+ const largeArc = fraction > 0.5 ? 1 : 0;
937
+ parts.push(
938
+ `<path d="M ${cx} ${cy} L ${x1} ${y1} A ${radius} ${radius} 0 ${largeArc} 1 ${x2} ${y2} Z" fill="${fill}" stroke="${common.canvasBg}" stroke-width="2"/>`
939
+ );
940
+ }
941
+ if (ir.showData && fraction >= 0.04) {
942
+ const mid = (startAngle + endAngle) / 2;
943
+ const lx = cx + Math.cos(mid) * radius * 0.65;
944
+ const ly = cy + Math.sin(mid) * radius * 0.65;
945
+ parts.push(
946
+ `<text x="${lx}" y="${ly + 4}" text-anchor="middle" font-size="12" font-weight="600" fill="#ffffff">${(fraction * 100).toFixed(1)}%</text>`
947
+ );
948
+ }
949
+ startAngle = endAngle;
950
+ });
951
+ const legendX = cx + radius + 24;
952
+ let legendY = padding + titleH + 16;
953
+ ir.slices.forEach((slice, i) => {
954
+ const fill = PIE_PALETTE[i % PIE_PALETTE.length];
955
+ const pct = (slice.value / total * 100).toFixed(1);
956
+ parts.push(`<rect x="${legendX}" y="${legendY - 10}" width="14" height="14" rx="2" fill="${fill}"/>`);
957
+ parts.push(`<text x="${legendX + 22}" y="${legendY + 1}" font-size="12" fill="${common.text}">${escXml(slice.label)}</text>`);
958
+ parts.push(`<text x="${legendX + 22}" y="${legendY + 16}" font-size="10" fill="${common.subtle}">${slice.value} \xB7 ${pct}%</text>`);
959
+ legendY += 32;
960
+ });
961
+ parts.push("</svg>");
962
+ return parts.join("");
963
+ }
964
+ function buildQuadrantSvg(ir, options = {}) {
965
+ const { common } = palette(options.dark ?? false);
966
+ const dark = options.dark ?? false;
967
+ const padding = options.padding ?? 60;
968
+ const titleH = ir.title ? 32 : 0;
969
+ const chartW = 700;
970
+ const chartH = 500;
971
+ const yAxis = ir.yAxisLabel ?? { low: "Low", high: "High" };
972
+ const xAxis = ir.xAxisLabel ?? { low: "Low", high: "High" };
973
+ const longestYLabel = Math.max(yAxis.low.length, yAxis.high.length);
974
+ const leftPadding = Math.max(padding, longestYLabel * 7 + 24);
975
+ const width = leftPadding + chartW + padding;
976
+ const height = padding * 2 + titleH + chartH;
977
+ const x0 = leftPadding;
978
+ const y0 = padding + titleH;
979
+ const tints = dark ? { q1: "rgba(16,185,129,0.18)", q2: "rgba(245,158,11,0.18)", q3: "rgba(244,63,94,0.18)", q4: "rgba(59,130,246,0.18)" } : { q1: "#10b98120", q2: "#f59e0b20", q3: "#ef444420", q4: "#3b82f620" };
980
+ const parts = [];
981
+ parts.push(svgOpen(0, 0, width, height, common.canvasBg, ir.title || "Quadrant chart"));
982
+ if (ir.title) {
983
+ parts.push(`<text x="${width / 2}" y="22" text-anchor="middle" font-size="16" font-weight="600" fill="${common.text}">${escXml(ir.title)}</text>`);
984
+ }
985
+ const halfW = chartW / 2;
986
+ const halfH = chartH / 2;
987
+ parts.push(`<rect x="${x0}" y="${y0 + halfH}" width="${halfW}" height="${halfH}" fill="${tints.q3}"/>`);
988
+ parts.push(`<rect x="${x0 + halfW}" y="${y0 + halfH}" width="${halfW}" height="${halfH}" fill="${tints.q4}"/>`);
989
+ parts.push(`<rect x="${x0}" y="${y0}" width="${halfW}" height="${halfH}" fill="${tints.q2}"/>`);
990
+ parts.push(`<rect x="${x0 + halfW}" y="${y0}" width="${halfW}" height="${halfH}" fill="${tints.q1}"/>`);
991
+ parts.push(`<line x1="${x0}" y1="${y0 + halfH}" x2="${x0 + chartW}" y2="${y0 + halfH}" stroke="${common.border}" stroke-width="1"/>`);
992
+ parts.push(`<line x1="${x0 + halfW}" y1="${y0}" x2="${x0 + halfW}" y2="${y0 + chartH}" stroke="${common.border}" stroke-width="1"/>`);
993
+ const labels = ir.quadrantLabels ?? {};
994
+ if (labels.q1) parts.push(`<text x="${x0 + halfW + halfW / 2}" y="${y0 + halfH / 2}" text-anchor="middle" font-size="13" font-weight="500" fill="${common.text}">${escXml(labels.q1)}</text>`);
995
+ if (labels.q2) parts.push(`<text x="${x0 + halfW / 2}" y="${y0 + halfH / 2}" text-anchor="middle" font-size="13" font-weight="500" fill="${common.text}">${escXml(labels.q2)}</text>`);
996
+ if (labels.q3) parts.push(`<text x="${x0 + halfW / 2}" y="${y0 + halfH + halfH / 2}" text-anchor="middle" font-size="13" font-weight="500" fill="${common.text}">${escXml(labels.q3)}</text>`);
997
+ if (labels.q4) parts.push(`<text x="${x0 + halfW + halfW / 2}" y="${y0 + halfH + halfH / 2}" text-anchor="middle" font-size="13" font-weight="500" fill="${common.text}">${escXml(labels.q4)}</text>`);
998
+ parts.push(`<text x="${x0}" y="${y0 + chartH + 24}" text-anchor="start" font-size="12" fill="${common.text}">${escXml(xAxis.low)}</text>`);
999
+ parts.push(`<text x="${x0 + chartW}" y="${y0 + chartH + 24}" text-anchor="end" font-size="12" fill="${common.text}">${escXml(xAxis.high)}</text>`);
1000
+ parts.push(`<text x="${x0 - 12}" y="${y0 + chartH}" text-anchor="end" font-size="12" fill="${common.text}">${escXml(yAxis.low)}</text>`);
1001
+ parts.push(`<text x="${x0 - 12}" y="${y0 + 12}" text-anchor="end" font-size="12" fill="${common.text}">${escXml(yAxis.high)}</text>`);
1002
+ for (const p of ir.points) {
1003
+ const px = x0 + p.x * chartW;
1004
+ const py = y0 + (1 - p.y) * chartH;
1005
+ parts.push(`<circle cx="${px}" cy="${py}" r="6" fill="#3b82f6" stroke="${common.canvasBg}" stroke-width="2"/>`);
1006
+ parts.push(`<text x="${px}" y="${py - 12}" text-anchor="middle" font-size="11" font-weight="500" fill="${common.text}">${escXml(p.label)}</text>`);
1007
+ }
1008
+ parts.push("</svg>");
1009
+ return parts.join("");
1010
+ }
1011
+ function buildJourneySvg(ir, options = {}) {
1012
+ const { common } = palette(options.dark ?? false);
1013
+ const padding = options.padding ?? 40;
1014
+ const titleH = ir.title ? 32 : 0;
1015
+ const SECTION_HEADER_H = 32;
1016
+ const TASK_H = 36;
1017
+ const labelW = 200;
1018
+ const chartW = 600;
1019
+ const SCORE_MAX = 7;
1020
+ const allTasks = [];
1021
+ ir.sections.forEach((s, idx) => {
1022
+ s.tasks.forEach((t) => allTasks.push({ sectionTitle: s.title, sectionIdx: idx, label: t.label, score: t.score, actors: t.actors }));
1023
+ });
1024
+ const totalH = padding * 2 + titleH + ir.sections.length * SECTION_HEADER_H + allTasks.length * TASK_H + 40;
1025
+ const width = padding * 2 + labelW + chartW;
1026
+ const height = totalH;
1027
+ const sectionColors = ["#3b82f6", "#10b981", "#f59e0b", "#f43f5e", "#8b5cf6", "#06b6d4"];
1028
+ const parts = [];
1029
+ parts.push(svgOpen(0, 0, width, height, common.canvasBg, ir.title || "User journey"));
1030
+ if (ir.title) {
1031
+ parts.push(`<text x="${width / 2}" y="22" text-anchor="middle" font-size="16" font-weight="600" fill="${common.text}">${escXml(ir.title)}</text>`);
1032
+ }
1033
+ const chartX = padding + labelW;
1034
+ const axisY = padding + titleH + 14;
1035
+ for (let s = 1; s <= SCORE_MAX; s++) {
1036
+ const x = chartX + (s - 1) / (SCORE_MAX - 1) * chartW;
1037
+ parts.push(`<text x="${x}" y="${axisY}" text-anchor="middle" font-size="10" fill="${common.subtle}">${s}</text>`);
1038
+ }
1039
+ let cy = padding + titleH + SECTION_HEADER_H;
1040
+ ir.sections.forEach((section, idx) => {
1041
+ const color = sectionColors[idx % sectionColors.length];
1042
+ parts.push(`<rect x="${padding}" y="${cy - SECTION_HEADER_H + 4}" width="${labelW + chartW}" height="${SECTION_HEADER_H - 4}" fill="${color}20" rx="6"/>`);
1043
+ parts.push(`<text x="${padding + 10}" y="${cy - 12}" font-size="12" font-weight="600" fill="${common.text}">${escXml(section.title)}</text>`);
1044
+ section.tasks.forEach((task) => {
1045
+ parts.push(`<text x="${padding + 10}" y="${cy + TASK_H / 2 + 4}" font-size="12" fill="${common.text}">${escXml(task.label)}</text>`);
1046
+ const scoreClamped = Math.max(1, Math.min(SCORE_MAX, task.score));
1047
+ const dotX = chartX + (scoreClamped - 1) / (SCORE_MAX - 1) * chartW;
1048
+ const dotY = cy + TASK_H / 2;
1049
+ parts.push(`<line x1="${chartX}" y1="${dotY}" x2="${chartX + chartW}" y2="${dotY}" stroke="${common.border}" stroke-dasharray="2 3"/>`);
1050
+ parts.push(`<circle cx="${dotX}" cy="${dotY}" r="8" fill="${color}" stroke="${common.canvasBg}" stroke-width="2"/>`);
1051
+ parts.push(`<text x="${dotX}" y="${dotY + 3}" text-anchor="middle" font-size="10" font-weight="700" fill="#ffffff">${task.score}</text>`);
1052
+ if (task.actors.length > 0) {
1053
+ const actorsText = task.actors.join(", ");
1054
+ parts.push(`<text x="${dotX + 14}" y="${dotY + 4}" font-size="10" fill="${common.subtle}">${escXml(actorsText)}</text>`);
1055
+ }
1056
+ cy += TASK_H;
1057
+ });
1058
+ cy += SECTION_HEADER_H;
1059
+ });
1060
+ parts.push("</svg>");
1061
+ return parts.join("");
1062
+ }
1063
+ function archTint(icon, dark) {
1064
+ if (!icon) return dark ? { fill: "#1e293b", border: "#475569" } : { fill: "#ffffff", border: "#cbd5e1" };
1065
+ const key = Object.keys(ARCH_ICON_TINT).find((k) => icon.toLowerCase().includes(k));
1066
+ const base = key ? ARCH_ICON_TINT[key] : { fill: "#ffffff", border: "#cbd5e1" };
1067
+ if (!dark) return base;
1068
+ return { fill: `${base.border}25`, border: base.border };
1069
+ }
1070
+ function buildArchitectureSvg(ir, options = {}) {
1071
+ const { common } = palette(options.dark ?? false);
1072
+ const dark = options.dark ?? false;
1073
+ const padding = options.padding ?? 40;
1074
+ const SERVICE_W = 130;
1075
+ const SERVICE_H = 80;
1076
+ const byParent = /* @__PURE__ */ new Map();
1077
+ for (const n of ir.nodes) {
1078
+ const k = n.parent;
1079
+ if (!byParent.has(k)) byParent.set(k, []);
1080
+ byParent.get(k).push(n);
1081
+ }
1082
+ const groupBounds = /* @__PURE__ */ new Map();
1083
+ for (const node of ir.nodes) {
1084
+ if (node.kind !== "group") continue;
1085
+ const children = byParent.get(node.id) ?? [];
1086
+ const g = new dagre2.graphlib.Graph();
1087
+ g.setGraph({ rankdir: "LR", nodesep: 30, ranksep: 50, marginx: 16, marginy: 16 });
1088
+ g.setDefaultEdgeLabel(() => ({}));
1089
+ for (const c of children) {
1090
+ g.setNode(c.id, { width: SERVICE_W, height: SERVICE_H });
1091
+ }
1092
+ const childIds = new Set(children.map((c) => c.id));
1093
+ for (const e of ir.edges) {
1094
+ if (childIds.has(e.source) && childIds.has(e.target) && g.hasNode(e.source) && g.hasNode(e.target)) {
1095
+ g.setEdge(e.source, e.target);
1096
+ }
1097
+ }
1098
+ if (children.length > 0) dagre2.layout(g);
1099
+ const positions = /* @__PURE__ */ new Map();
1100
+ let minLeft = Infinity, minTop = Infinity, maxRight = 0, maxBottom = 0;
1101
+ for (const c of children) {
1102
+ const { x, y } = g.node(c.id);
1103
+ const left = x - SERVICE_W / 2;
1104
+ const top = y - SERVICE_H / 2;
1105
+ positions.set(c.id, { x: left, y: top });
1106
+ minLeft = Math.min(minLeft, left);
1107
+ minTop = Math.min(minTop, top);
1108
+ maxRight = Math.max(maxRight, left + SERVICE_W);
1109
+ maxBottom = Math.max(maxBottom, top + SERVICE_H);
1110
+ }
1111
+ const HEADER = 28;
1112
+ const PAD = 16;
1113
+ const dx = PAD - (isFinite(minLeft) ? minLeft : 0);
1114
+ const dy = HEADER + PAD - (isFinite(minTop) ? minTop : 0);
1115
+ const offset = /* @__PURE__ */ new Map();
1116
+ for (const [id, p] of positions) offset.set(id, { x: p.x + dx, y: p.y + dy });
1117
+ groupBounds.set(node.id, {
1118
+ width: Math.max(220, (isFinite(maxRight - minLeft) ? maxRight - minLeft : 0) + PAD * 2),
1119
+ height: Math.max(120, (isFinite(maxBottom - minTop) ? maxBottom - minTop : 0) + HEADER + PAD * 2),
1120
+ positions: offset
1121
+ });
1122
+ }
1123
+ const topLevel = ir.nodes.filter((n) => !n.parent);
1124
+ const outer = new dagre2.graphlib.Graph();
1125
+ outer.setGraph({ rankdir: "LR", nodesep: 50, ranksep: 80, marginx: 32, marginy: 32 });
1126
+ outer.setDefaultEdgeLabel(() => ({}));
1127
+ for (const n of topLevel) {
1128
+ if (n.kind === "group") {
1129
+ const b = groupBounds.get(n.id);
1130
+ outer.setNode(n.id, { width: b.width, height: b.height });
1131
+ } else {
1132
+ outer.setNode(n.id, { width: SERVICE_W, height: SERVICE_H });
1133
+ }
1134
+ }
1135
+ for (const e of ir.edges) {
1136
+ if (outer.hasNode(e.source) && outer.hasNode(e.target)) outer.setEdge(e.source, e.target);
1137
+ }
1138
+ dagre2.layout(outer);
1139
+ const abs = /* @__PURE__ */ new Map();
1140
+ for (const n of topLevel) {
1141
+ const { x, y } = outer.node(n.id);
1142
+ const w = n.kind === "group" ? groupBounds.get(n.id).width : SERVICE_W;
1143
+ const h = n.kind === "group" ? groupBounds.get(n.id).height : SERVICE_H;
1144
+ abs.set(n.id, { x: x - w / 2, y: y - h / 2, width: w, height: h, kind: n.kind, node: n });
1145
+ }
1146
+ for (const n of ir.nodes) {
1147
+ if (!n.parent) continue;
1148
+ const parentBox = abs.get(n.parent);
1149
+ if (!parentBox) continue;
1150
+ const pos = groupBounds.get(n.parent)?.positions.get(n.id);
1151
+ if (!pos) continue;
1152
+ abs.set(n.id, { x: parentBox.x + pos.x, y: parentBox.y + pos.y, width: SERVICE_W, height: SERVICE_H, kind: n.kind, node: n });
1153
+ }
1154
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1155
+ for (const b of abs.values()) {
1156
+ minX = Math.min(minX, b.x);
1157
+ minY = Math.min(minY, b.y);
1158
+ maxX = Math.max(maxX, b.x + b.width);
1159
+ maxY = Math.max(maxY, b.y + b.height);
1160
+ }
1161
+ minX -= padding;
1162
+ minY -= padding;
1163
+ maxX += padding;
1164
+ maxY += padding;
1165
+ const width = Math.round(maxX - minX);
1166
+ const height = Math.round(maxY - minY);
1167
+ const parts = [];
1168
+ parts.push(svgOpen(minX, minY, width, height, common.canvasBg, "Architecture diagram"));
1169
+ parts.push(`<defs>${arrowDef("arr", common.edgeColor)}</defs>`);
1170
+ for (const n of ir.nodes) {
1171
+ if (n.kind !== "group") continue;
1172
+ const b = abs.get(n.id);
1173
+ if (!b) continue;
1174
+ const tint = archTint(n.icon, dark);
1175
+ parts.push(
1176
+ `<rect x="${b.x}" y="${b.y}" width="${b.width}" height="${b.height}" rx="12" fill="${tint.fill}" stroke="${tint.border}" stroke-width="1.5" stroke-dasharray="6 4"/>`
1177
+ );
1178
+ parts.push(`<text x="${b.x + 14}" y="${b.y + 18}" font-size="12" font-weight="700" fill="${common.text}">${escXml(n.label)}</text>`);
1179
+ if (n.icon) {
1180
+ parts.push(`<text x="${b.x + b.width - 14}" y="${b.y + 18}" text-anchor="end" font-size="9" fill="${common.subtle}">${escXml(n.icon)}</text>`);
1181
+ }
1182
+ }
1183
+ for (const e of ir.edges) {
1184
+ const a = abs.get(e.source);
1185
+ const b = abs.get(e.target);
1186
+ if (!a || !b) continue;
1187
+ parts.push(buildEdgePath(a, b, { source: e.source, target: e.target, label: e.label, kind: "solid" }, common));
1188
+ }
1189
+ for (const n of ir.nodes) {
1190
+ if (n.kind !== "service") continue;
1191
+ const b = abs.get(n.id);
1192
+ if (!b) continue;
1193
+ const tint = archTint(n.icon, dark);
1194
+ parts.push(
1195
+ `<rect x="${b.x}" y="${b.y}" width="${b.width}" height="${b.height}" rx="10" fill="${tint.fill}" stroke="${tint.border}" stroke-width="1.5"/>`
1196
+ );
1197
+ parts.push(`<circle cx="${b.x + b.width / 2}" cy="${b.y + 22}" r="14" fill="${tint.border}" opacity="0.85"/>`);
1198
+ if (n.icon) {
1199
+ const short = n.icon.split(":").pop().slice(0, 3).toUpperCase();
1200
+ parts.push(`<text x="${b.x + b.width / 2}" y="${b.y + 26}" text-anchor="middle" font-size="9" font-weight="700" fill="#ffffff">${escXml(short)}</text>`);
1201
+ }
1202
+ parts.push(`<text x="${b.x + b.width / 2}" y="${b.y + 56}" text-anchor="middle" font-size="12" font-weight="600" fill="${common.text}">${escXml(n.label)}</text>`);
1203
+ }
1204
+ parts.push("</svg>");
1205
+ return parts.join("");
1206
+ }
1207
+ function c4StyleFor(kind, dark) {
1208
+ const isExternal = kind.endsWith("-external");
1209
+ const base = kind.startsWith("person") ? { fill: dark ? "rgba(8, 80, 134, 0.4)" : "#08427b", text: "#ffffff" } : kind.startsWith("system") ? { fill: dark ? "rgba(17, 102, 187, 0.4)" : "#1168bd", text: "#ffffff" } : kind.startsWith("container") ? { fill: dark ? "rgba(67, 130, 245, 0.4)" : "#438dd5", text: "#ffffff" } : kind.startsWith("component") ? { fill: dark ? "rgba(133, 187, 245, 0.4)" : "#85bbf0", text: "#0f172a" } : { fill: dark ? "rgba(148, 163, 184, 0.3)" : "#9ca3af", text: "#0f172a" };
1210
+ let shape = "rect";
1211
+ if (kind.startsWith("person")) shape = "person";
1212
+ else if (kind.endsWith("-db")) shape = "cylinder";
1213
+ else if (kind.endsWith("-queue")) shape = "queue";
1214
+ else if (kind.endsWith("boundary")) shape = "boundary";
1215
+ else if (kind === "node") shape = "node";
1216
+ return {
1217
+ fill: isExternal ? dark ? "rgba(100, 116, 139, 0.4)" : "#999999" : base.fill,
1218
+ border: isExternal ? "#64748b" : "#073b6f",
1219
+ text: base.text,
1220
+ badge: "#0f172a40",
1221
+ badgeText: "#ffffff",
1222
+ shape,
1223
+ dashed: shape === "boundary" || shape === "node"
1224
+ };
1225
+ }
1226
+ function c4BadgeLabel(kind) {
1227
+ if (kind.startsWith("person")) return "Person";
1228
+ if (kind.endsWith("-external")) {
1229
+ if (kind.startsWith("system")) return "External System";
1230
+ if (kind.startsWith("container")) return "External Container";
1231
+ if (kind.startsWith("component")) return "External Component";
1232
+ }
1233
+ if (kind.endsWith("-db")) return kind.startsWith("system") ? "System" : kind.startsWith("container") ? "Container" : "Component";
1234
+ if (kind.endsWith("-queue")) return kind.startsWith("system") ? "System" : kind.startsWith("container") ? "Container" : "Component";
1235
+ if (kind === "system") return "System";
1236
+ if (kind === "container") return "Container";
1237
+ if (kind === "component") return "Component";
1238
+ if (kind === "node") return "Deployment Node";
1239
+ return "Boundary";
1240
+ }
1241
+ function buildC4Svg(ir, options = {}) {
1242
+ const { common } = palette(options.dark ?? false);
1243
+ const dark = options.dark ?? false;
1244
+ const padding = options.padding ?? 40;
1245
+ const titleH = ir.title ? 32 : 0;
1246
+ const ELEM_W = 200;
1247
+ const ELEM_H = 110;
1248
+ const nonBoundary = ir.elements.filter((e) => !c4StyleFor(e.kind, dark).shape.includes("boundary") && c4StyleFor(e.kind, dark).shape !== "node");
1249
+ const boundaries = ir.elements.filter((e) => {
1250
+ const s = c4StyleFor(e.kind, dark);
1251
+ return s.shape === "boundary" || s.shape === "node";
1252
+ });
1253
+ const g = new dagre2.graphlib.Graph();
1254
+ g.setGraph({ rankdir: "TB", nodesep: 60, ranksep: 80, marginx: 32, marginy: 32 });
1255
+ g.setDefaultEdgeLabel(() => ({}));
1256
+ for (const el of nonBoundary) g.setNode(el.id, { width: ELEM_W, height: ELEM_H });
1257
+ for (const rel of ir.relations) {
1258
+ if (g.hasNode(rel.source) && g.hasNode(rel.target)) g.setEdge(rel.source, rel.target);
1259
+ }
1260
+ dagre2.layout(g);
1261
+ const positions = /* @__PURE__ */ new Map();
1262
+ for (const el of nonBoundary) {
1263
+ const { x, y } = g.node(el.id);
1264
+ positions.set(el.id, { x: x - ELEM_W / 2, y: y - ELEM_H / 2, width: ELEM_W, height: ELEM_H });
1265
+ }
1266
+ for (const b of boundaries) {
1267
+ const children = ir.elements.filter((e) => e.parent === b.id);
1268
+ const childPositions = children.map((c) => positions.get(c.id)).filter((p) => !!p);
1269
+ if (childPositions.length === 0) continue;
1270
+ let minX2 = Infinity, minY2 = Infinity, maxX2 = -Infinity, maxY2 = -Infinity;
1271
+ for (const p of childPositions) {
1272
+ minX2 = Math.min(minX2, p.x);
1273
+ minY2 = Math.min(minY2, p.y);
1274
+ maxX2 = Math.max(maxX2, p.x + p.width);
1275
+ maxY2 = Math.max(maxY2, p.y + p.height);
1276
+ }
1277
+ positions.set(b.id, { x: minX2 - 20, y: minY2 - 28, width: maxX2 - minX2 + 40, height: maxY2 - minY2 + 48 });
1278
+ }
1279
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
1280
+ for (const p of positions.values()) {
1281
+ minX = Math.min(minX, p.x);
1282
+ minY = Math.min(minY, p.y);
1283
+ maxX = Math.max(maxX, p.x + p.width);
1284
+ maxY = Math.max(maxY, p.y + p.height);
1285
+ }
1286
+ minX -= padding;
1287
+ minY -= padding - titleH;
1288
+ maxX += padding;
1289
+ maxY += padding;
1290
+ const width = Math.round(maxX - minX);
1291
+ const height = Math.round(maxY - minY) + titleH;
1292
+ const viewMinY = minY - titleH;
1293
+ const parts = [];
1294
+ parts.push(svgOpen(minX, viewMinY, width, height, common.canvasBg, ir.title || `C4 ${ir.variant} diagram`));
1295
+ parts.push(`<defs>${arrowDef("arr", common.edgeColor)}</defs>`);
1296
+ if (ir.title) {
1297
+ parts.push(`<text x="${minX + width / 2}" y="${viewMinY + 22}" text-anchor="middle" font-size="16" font-weight="700" fill="${common.text}">${escXml(ir.title)}</text>`);
1298
+ }
1299
+ for (const b of boundaries) {
1300
+ const p = positions.get(b.id);
1301
+ if (!p) continue;
1302
+ const style = c4StyleFor(b.kind, dark);
1303
+ parts.push(
1304
+ `<rect x="${p.x}" y="${p.y}" width="${p.width}" height="${p.height}" rx="8" fill="none" stroke="${style.border}" stroke-width="2" stroke-dasharray="8 4"/>`
1305
+ );
1306
+ parts.push(`<text x="${p.x + 12}" y="${p.y + 18}" font-size="11" font-weight="700" fill="${common.text}">${escXml(b.label)}</text>`);
1307
+ parts.push(`<text x="${p.x + 12}" y="${p.y + 32}" font-size="9" fill="${common.subtle}" font-style="italic">[${escXml(c4BadgeLabel(b.kind))}]</text>`);
1308
+ }
1309
+ for (const rel of ir.relations) {
1310
+ const a = positions.get(rel.source);
1311
+ const b = positions.get(rel.target);
1312
+ if (!a || !b) continue;
1313
+ const labelLine = rel.label ?? "";
1314
+ const techLine = rel.technology ? `[${rel.technology}]` : "";
1315
+ parts.push(
1316
+ buildEdgePath(a, b, { source: rel.source, target: rel.target, label: [labelLine, techLine].filter(Boolean).join(" "), kind: "solid" }, common)
1317
+ );
1318
+ }
1319
+ for (const el of nonBoundary) {
1320
+ const p = positions.get(el.id);
1321
+ if (!p) continue;
1322
+ parts.push(buildC4Element(el, p, dark));
1323
+ }
1324
+ parts.push("</svg>");
1325
+ return parts.join("");
1326
+ }
1327
+ function buildC4Element(el, p, dark) {
1328
+ const style = c4StyleFor(el.kind, dark);
1329
+ const cx = p.x + p.width / 2;
1330
+ const parts = [];
1331
+ if (style.shape === "person") {
1332
+ const headR = 14;
1333
+ parts.push(
1334
+ `<rect x="${p.x}" y="${p.y + headR + 8}" width="${p.width}" height="${p.height - headR - 8}" rx="10" fill="${style.fill}" stroke="${style.border}" stroke-width="1.5"/>`
1335
+ );
1336
+ parts.push(`<circle cx="${cx}" cy="${p.y + headR + 4}" r="${headR}" fill="${style.fill}" stroke="${style.border}" stroke-width="1.5"/>`);
1337
+ } else if (style.shape === "cylinder") {
1338
+ const ry = 8;
1339
+ parts.push(
1340
+ `<path d="M ${p.x} ${p.y + ry} A ${p.width / 2} ${ry} 0 0 0 ${p.x + p.width} ${p.y + ry} L ${p.x + p.width} ${p.y + p.height - ry} A ${p.width / 2} ${ry} 0 0 1 ${p.x} ${p.y + p.height - ry} Z" fill="${style.fill}" stroke="${style.border}" stroke-width="1.5"/>`
1341
+ );
1342
+ parts.push(`<ellipse cx="${cx}" cy="${p.y + ry}" rx="${p.width / 2}" ry="${ry}" fill="none" stroke="${style.border}" stroke-width="1.5"/>`);
1343
+ } else if (style.shape === "queue") {
1344
+ parts.push(
1345
+ `<rect x="${p.x}" y="${p.y}" width="${p.width}" height="${p.height}" rx="${p.height / 2}" fill="${style.fill}" stroke="${style.border}" stroke-width="1.5"/>`
1346
+ );
1347
+ } else {
1348
+ parts.push(
1349
+ `<rect x="${p.x}" y="${p.y}" width="${p.width}" height="${p.height}" rx="8" fill="${style.fill}" stroke="${style.border}" stroke-width="1.5"/>`
1350
+ );
1351
+ }
1352
+ const badgeY = p.y + (style.shape === "person" ? 36 : 18);
1353
+ parts.push(`<text x="${cx}" y="${badgeY}" text-anchor="middle" font-size="9" font-style="italic" font-weight="600" fill="${style.text}" opacity="0.85">[${escXml(c4BadgeLabel(el.kind))}]</text>`);
1354
+ parts.push(`<text x="${cx}" y="${badgeY + 18}" text-anchor="middle" font-size="13" font-weight="700" fill="${style.text}">${escXml(el.label)}</text>`);
1355
+ if (el.technology) {
1356
+ parts.push(`<text x="${cx}" y="${badgeY + 32}" text-anchor="middle" font-size="10" font-style="italic" fill="${style.text}" opacity="0.85">[${escXml(el.technology)}]</text>`);
1357
+ }
1358
+ if (el.description) {
1359
+ const lines = wrapText(el.description, 28);
1360
+ const startY = badgeY + (el.technology ? 48 : 36);
1361
+ for (let i = 0; i < Math.min(lines.length, 2); i++) {
1362
+ parts.push(`<text x="${cx}" y="${startY + i * 12}" text-anchor="middle" font-size="10" fill="${style.text}" opacity="0.92">${escXml(lines[i])}</text>`);
1363
+ }
1364
+ }
1365
+ return parts.join("");
1366
+ }
1367
+ function buildGitGraphSvg(ir, options = {}) {
1368
+ const { common } = palette(options.dark ?? false);
1369
+ const dark = options.dark ?? false;
1370
+ const padding = options.padding ?? 40;
1371
+ const titleH = ir.title ? 32 : 0;
1372
+ const commits = [];
1373
+ const branchOrder = ["main"];
1374
+ const branchHead = /* @__PURE__ */ new Map();
1375
+ branchHead.set("main", null);
1376
+ let currentBranch = "main";
1377
+ let counter = 0;
1378
+ for (const op of ir.ops) {
1379
+ if (op.kind === "branch") {
1380
+ if (!branchOrder.includes(op.name)) branchOrder.push(op.name);
1381
+ branchHead.set(op.name, branchHead.get(currentBranch) ?? null);
1382
+ currentBranch = op.name;
1383
+ } else if (op.kind === "checkout") {
1384
+ currentBranch = op.name;
1385
+ if (!branchOrder.includes(op.name)) branchOrder.push(op.name);
1386
+ if (!branchHead.has(op.name)) branchHead.set(op.name, null);
1387
+ } else if (op.kind === "commit") {
1388
+ const id = op.id ?? `c${++counter}`;
1389
+ const parent = branchHead.get(currentBranch);
1390
+ const commit = {
1391
+ id,
1392
+ branch: currentBranch,
1393
+ parents: parent ? [parent] : [],
1394
+ tag: op.tag,
1395
+ type: op.type ?? "NORMAL"
1396
+ };
1397
+ commits.push(commit);
1398
+ branchHead.set(currentBranch, id);
1399
+ } else if (op.kind === "merge") {
1400
+ const id = `merge-${++counter}`;
1401
+ const a = branchHead.get(currentBranch);
1402
+ const b = branchHead.get(op.from);
1403
+ const commit = {
1404
+ id,
1405
+ branch: currentBranch,
1406
+ parents: [a, b].filter((p) => !!p),
1407
+ tag: op.tag,
1408
+ type: "NORMAL",
1409
+ isMerge: true
1410
+ };
1411
+ commits.push(commit);
1412
+ branchHead.set(currentBranch, id);
1413
+ } else if (op.kind === "cherry-pick") {
1414
+ const id = `cherry-${++counter}`;
1415
+ const parent = branchHead.get(currentBranch);
1416
+ commits.push({
1417
+ id,
1418
+ branch: currentBranch,
1419
+ parents: parent ? [parent] : [],
1420
+ type: "HIGHLIGHT"
1421
+ });
1422
+ branchHead.set(currentBranch, id);
1423
+ }
1424
+ }
1425
+ const BRANCH_GAP = 60;
1426
+ const COMMIT_GAP = 70;
1427
+ const branchIdx = new Map(branchOrder.map((b, i) => [b, i]));
1428
+ const branchX = (b) => padding + 90 + (branchIdx.get(b) ?? 0) * BRANCH_GAP;
1429
+ const commitPositions = /* @__PURE__ */ new Map();
1430
+ commits.forEach((c, i) => {
1431
+ commitPositions.set(c.id, { x: branchX(c.branch), y: padding + titleH + 40 + i * COMMIT_GAP });
1432
+ });
1433
+ const width = padding * 2 + 90 + branchOrder.length * BRANCH_GAP + 200;
1434
+ const height = padding * 2 + titleH + 60 + commits.length * COMMIT_GAP;
1435
+ const branchColors = ["#3b82f6", "#10b981", "#f59e0b", "#f43f5e", "#8b5cf6", "#06b6d4", "#ec4899"];
1436
+ const branchColor = (b) => branchColors[(branchIdx.get(b) ?? 0) % branchColors.length];
1437
+ const parts = [];
1438
+ parts.push(svgOpen(0, 0, width, height, common.canvasBg, ir.title || "Git graph"));
1439
+ if (ir.title) {
1440
+ parts.push(`<text x="${width / 2}" y="22" text-anchor="middle" font-size="15" font-weight="600" fill="${common.text}">${escXml(ir.title)}</text>`);
1441
+ }
1442
+ for (const b of branchOrder) {
1443
+ const x = branchX(b);
1444
+ const y = padding + titleH + 16;
1445
+ parts.push(`<text x="${x}" y="${y}" text-anchor="middle" font-size="11" font-weight="700" fill="${branchColor(b)}">${escXml(b)}</text>`);
1446
+ parts.push(`<line x1="${x}" y1="${y + 8}" x2="${x}" y2="${height - padding}" stroke="${branchColor(b)}" stroke-width="2" opacity="0.3"/>`);
1447
+ }
1448
+ for (const c of commits) {
1449
+ const cp = commitPositions.get(c.id);
1450
+ if (!cp) continue;
1451
+ for (const pid of c.parents) {
1452
+ const pp = commitPositions.get(pid);
1453
+ if (!pp) continue;
1454
+ const sameLane = pp.x === cp.x;
1455
+ const stroke = branchColor(c.branch);
1456
+ if (sameLane) {
1457
+ parts.push(`<line x1="${pp.x}" y1="${pp.y}" x2="${cp.x}" y2="${cp.y}" stroke="${stroke}" stroke-width="2"/>`);
1458
+ } else {
1459
+ const midY = (pp.y + cp.y) / 2;
1460
+ parts.push(`<path d="M ${pp.x} ${pp.y} C ${pp.x} ${midY}, ${cp.x} ${midY}, ${cp.x} ${cp.y}" stroke="${stroke}" stroke-width="2" fill="none"/>`);
1461
+ }
1462
+ }
1463
+ }
1464
+ for (const c of commits) {
1465
+ const cp = commitPositions.get(c.id);
1466
+ if (!cp) continue;
1467
+ const color = branchColor(c.branch);
1468
+ const r = 9;
1469
+ if (c.type === "REVERSE") {
1470
+ parts.push(`<rect x="${cp.x - r}" y="${cp.y - r}" width="${r * 2}" height="${r * 2}" fill="${dark ? "#0f172a" : "#ffffff"}" stroke="${color}" stroke-width="2"/>`);
1471
+ } else if (c.type === "HIGHLIGHT") {
1472
+ parts.push(`<rect x="${cp.x - r}" y="${cp.y - r}" width="${r * 2}" height="${r * 2}" rx="3" fill="${color}" stroke="${color}" stroke-width="2"/>`);
1473
+ } else if (c.isMerge) {
1474
+ parts.push(`<circle cx="${cp.x}" cy="${cp.y}" r="${r}" fill="${dark ? "#0f172a" : "#ffffff"}" stroke="${color}" stroke-width="2.5"/>`);
1475
+ parts.push(`<circle cx="${cp.x}" cy="${cp.y}" r="${r - 4}" fill="${color}"/>`);
1476
+ } else {
1477
+ parts.push(`<circle cx="${cp.x}" cy="${cp.y}" r="${r}" fill="${color}" stroke="${dark ? "#0f172a" : "#ffffff"}" stroke-width="2"/>`);
1478
+ }
1479
+ const labelX = padding + 90 + branchOrder.length * BRANCH_GAP + 24;
1480
+ parts.push(`<text x="${labelX}" y="${cp.y + 4}" font-family='${MONO_FAMILY}' font-size="11" fill="${common.text}">${escXml(c.id)}</text>`);
1481
+ if (c.tag) {
1482
+ const tagX = labelX + c.id.length * 7 + 12;
1483
+ parts.push(`<rect x="${tagX}" y="${cp.y - 8}" width="${c.tag.length * 6.5 + 12}" height="16" rx="3" fill="${color}" opacity="0.85"/>`);
1484
+ parts.push(`<text x="${tagX + 6}" y="${cp.y + 4}" font-size="10" font-weight="700" fill="#ffffff">${escXml(c.tag)}</text>`);
1485
+ }
1486
+ }
1487
+ parts.push("</svg>");
1488
+ return parts.join("");
1489
+ }
1490
+ function svgStringToElement(svg) {
1491
+ if (typeof window === "undefined" || !window.DOMParser) return null;
1492
+ const doc = new DOMParser().parseFromString(svg, "image/svg+xml");
1493
+ const root = doc.documentElement;
1494
+ if (root.tagName !== "svg") return null;
1495
+ const imported = document.importNode(root, true);
1496
+ return imported;
1497
+ }
1498
+ var LIGHT_KIND, DARK_KIND, LIGHT_COMMON, DARK_COMMON, FONT_FAMILY, MONO_FAMILY, VIS_SYMBOL, PIE_PALETTE, ARCH_ICON_TINT;
1499
+ var init_svgBuilders = __esm({
1500
+ "src/utils/diagrams/svgBuilders.ts"() {
1501
+ LIGHT_KIND = {
1502
+ service: { border: "#86efac", headerBg: "#ecfdf5", accent: "#10b981", bodyBg: "#ffffff", text: "#064e3b" },
1503
+ database: { border: "#fcd34d", headerBg: "#fffbeb", accent: "#f59e0b", bodyBg: "#ffffff", text: "#78350f" },
1504
+ queue: { border: "#fda4af", headerBg: "#fff1f2", accent: "#f43f5e", bodyBg: "#ffffff", text: "#881337" },
1505
+ storage: { border: "#67e8f9", headerBg: "#ecfeff", accent: "#06b6d4", bodyBg: "#ffffff", text: "#164e63" },
1506
+ user: { border: "#93c5fd", headerBg: "#eff6ff", accent: "#3b82f6", bodyBg: "#ffffff", text: "#1e3a8a" },
1507
+ client: { border: "#c4b5fd", headerBg: "#f5f3ff", accent: "#8b5cf6", bodyBg: "#ffffff", text: "#4c1d95" },
1508
+ external: { border: "#cbd5e1", headerBg: "#f1f5f9", accent: "#64748b", bodyBg: "#ffffff", text: "#334155" },
1509
+ process: { border: "#bfdbfe", headerBg: "#eff6ff", accent: "#60a5fa", bodyBg: "#ffffff", text: "#1e3a8a" },
1510
+ decision: { border: "#c4b5fd", headerBg: "#f5f3ff", accent: "#8b5cf6", bodyBg: "#faf5ff", text: "#4c1d95" },
1511
+ start: { border: "#86efac", headerBg: "#ecfdf5", accent: "#10b981", bodyBg: "#ffffff", text: "#064e3b" },
1512
+ end: { border: "#fda4af", headerBg: "#fff1f2", accent: "#f43f5e", bodyBg: "#ffffff", text: "#881337" },
1513
+ icon: { border: "#cbd5e1", headerBg: "#ffffff", accent: "#64748b", bodyBg: "#ffffff", text: "#1e293b" },
1514
+ plain: { border: "#cbd5e1", headerBg: "#f8fafc", accent: "#64748b", bodyBg: "#ffffff", text: "#1e293b" }
1515
+ };
1516
+ DARK_KIND = {
1517
+ service: { border: "#10b981", headerBg: "rgba(16,185,129,0.12)", accent: "#34d399", bodyBg: "#1e293b", text: "#a7f3d0" },
1518
+ database: { border: "#f59e0b", headerBg: "rgba(245,158,11,0.12)", accent: "#fbbf24", bodyBg: "#1e293b", text: "#fde68a" },
1519
+ queue: { border: "#f43f5e", headerBg: "rgba(244,63,94,0.12)", accent: "#fb7185", bodyBg: "#1e293b", text: "#fecdd3" },
1520
+ storage: { border: "#06b6d4", headerBg: "rgba(6,182,212,0.12)", accent: "#22d3ee", bodyBg: "#1e293b", text: "#a5f3fc" },
1521
+ user: { border: "#3b82f6", headerBg: "rgba(59,130,246,0.12)", accent: "#60a5fa", bodyBg: "#1e293b", text: "#bfdbfe" },
1522
+ client: { border: "#8b5cf6", headerBg: "rgba(139,92,246,0.12)", accent: "#a78bfa", bodyBg: "#1e293b", text: "#ddd6fe" },
1523
+ external: { border: "#64748b", headerBg: "rgba(100,116,139,0.12)", accent: "#94a3b8", bodyBg: "#1e293b", text: "#cbd5e1" },
1524
+ process: { border: "#60a5fa", headerBg: "rgba(96,165,250,0.12)", accent: "#93c5fd", bodyBg: "#1e293b", text: "#bfdbfe" },
1525
+ decision: { border: "#8b5cf6", headerBg: "rgba(139,92,246,0.12)", accent: "#a78bfa", bodyBg: "#1e293b", text: "#ddd6fe" },
1526
+ start: { border: "#10b981", headerBg: "rgba(16,185,129,0.12)", accent: "#34d399", bodyBg: "#1e293b", text: "#a7f3d0" },
1527
+ end: { border: "#f43f5e", headerBg: "rgba(244,63,94,0.12)", accent: "#fb7185", bodyBg: "#1e293b", text: "#fecdd3" },
1528
+ icon: { border: "#475569", headerBg: "#1e293b", accent: "#94a3b8", bodyBg: "#1e293b", text: "#e2e8f0" },
1529
+ plain: { border: "#475569", headerBg: "#1e293b", accent: "#94a3b8", bodyBg: "#1e293b", text: "#e2e8f0" }
1530
+ };
1531
+ LIGHT_COMMON = {
1532
+ canvasBg: "#ffffff",
1533
+ edgeColor: "#94a3b8",
1534
+ edgeLabel: "#475569",
1535
+ edgeLabelBg: "#ffffff",
1536
+ text: "#1e293b",
1537
+ subtle: "#64748b",
1538
+ border: "#cbd5e1"
1539
+ };
1540
+ DARK_COMMON = {
1541
+ canvasBg: "#0f172a",
1542
+ edgeColor: "#64748b",
1543
+ edgeLabel: "#cbd5e1",
1544
+ edgeLabelBg: "#1e293b",
1545
+ text: "#e2e8f0",
1546
+ subtle: "#94a3b8",
1547
+ border: "#475569"
1548
+ };
1549
+ FONT_FAMILY = '"Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif';
1550
+ MONO_FAMILY = '"Fira Code", ui-monospace, SFMono-Regular, Menlo, monospace';
1551
+ VIS_SYMBOL = { public: "+", private: "-", protected: "#", package: "~" };
1552
+ PIE_PALETTE = [
1553
+ "#3b82f6",
1554
+ "#10b981",
1555
+ "#f59e0b",
1556
+ "#ef4444",
1557
+ "#8b5cf6",
1558
+ "#06b6d4",
1559
+ "#ec4899",
1560
+ "#84cc16",
1561
+ "#f97316",
1562
+ "#6366f1",
1563
+ "#14b8a6",
1564
+ "#d946ef"
1565
+ ];
1566
+ ARCH_ICON_TINT = {
1567
+ aws: { fill: "#fff5e6", border: "#ff9900" },
1568
+ google: { fill: "#e8f0fe", border: "#4285f4" },
1569
+ azure: { fill: "#e3f2fd", border: "#0078d4" },
1570
+ cloudflare: { fill: "#fff4e0", border: "#f48120" },
1571
+ docker: { fill: "#e7f3ff", border: "#2496ed" },
1572
+ kubernetes: { fill: "#eaf1ff", border: "#326ce5" },
1573
+ redis: { fill: "#fde7e3", border: "#dc382d" },
1574
+ postgresql: { fill: "#e3f2fa", border: "#336791" },
1575
+ mongodb: { fill: "#e8f5e9", border: "#47a248" },
1576
+ cloud: { fill: "#f0f4ff", border: "#6366f1" },
1577
+ database: { fill: "#fff7ed", border: "#f59e0b" },
1578
+ disk: { fill: "#ecfeff", border: "#06b6d4" },
1579
+ server: { fill: "#ecfdf5", border: "#10b981" },
1580
+ internet: { fill: "#eff6ff", border: "#3b82f6" }
1581
+ };
1582
+ }
1583
+ });
1584
+
1585
+ // src/components/diagrams/FlowchartRenderer.tsx
1586
+ var FlowchartRenderer_exports = {};
1587
+ __export(FlowchartRenderer_exports, {
1588
+ default: () => FlowchartRenderer
1589
+ });
1590
+ function rectSize(label) {
1591
+ const width = Math.max(160, Math.min(320, label.length * 8 + 40));
1592
+ const lines = Math.max(1, Math.ceil(label.length / 24));
1593
+ return { width, height: 48 + (lines - 1) * 18 };
1594
+ }
1595
+ function diamondSize(label) {
1596
+ return {
1597
+ width: Math.max(140, Math.min(280, label.length * 11 + 60)),
1598
+ height: Math.max(96, Math.min(160, Math.ceil(label.length / 16) * 32 + 64))
1599
+ };
1600
+ }
1601
+ function FlowchartRenderer({ ir, dark = false, handleRef }) {
1602
+ const theme = getDiagramTheme(dark);
1603
+ const containerRef = useRef(null);
1604
+ const svg = useMemo(() => {
1605
+ const sizes = /* @__PURE__ */ new Map();
1606
+ for (const n of ir.nodes) {
1607
+ if (n.kind === "user" || n.kind === "start" || n.kind === "end") sizes.set(n.id, NODE_SIZE.circle);
1608
+ else if (n.kind === "icon") sizes.set(n.id, NODE_SIZE.icon);
1609
+ else if (n.kind === "decision") sizes.set(n.id, diamondSize(n.label));
1610
+ else if (n.kind === "queue") sizes.set(n.id, NODE_SIZE.subroutine);
1611
+ else sizes.set(n.id, rectSize(n.label));
1612
+ }
1613
+ const layout = layoutFlowchart(ir, { nodeSizes: sizes });
1614
+ return buildFlowchartSvg(ir, layout.nodePositions, { dark });
1615
+ }, [ir, dark]);
1616
+ useEffect(() => {
1617
+ if (!handleRef) return;
1618
+ const handle = {
1619
+ getSvgElement: () => svgStringToElement(svg)
1620
+ };
1621
+ handleRef.current = handle;
1622
+ return () => {
1623
+ if (handleRef.current === handle) handleRef.current = null;
1624
+ };
1625
+ }, [handleRef, svg]);
1626
+ return /* @__PURE__ */ jsx(
1627
+ "div",
1628
+ {
1629
+ ref: containerRef,
1630
+ className: "flowchart-renderer",
1631
+ style: { width: "100%", background: theme.canvasBg, borderRadius: 12, padding: 12, overflow: "auto" },
1632
+ dangerouslySetInnerHTML: { __html: svg }
1633
+ }
1634
+ );
1635
+ }
1636
+ var NODE_SIZE;
1637
+ var init_FlowchartRenderer = __esm({
1638
+ "src/components/diagrams/FlowchartRenderer.tsx"() {
1639
+ init_dagreLayout();
1640
+ init_theme();
1641
+ init_svgBuilders();
1642
+ NODE_SIZE = {
1643
+ circle: { width: 84, height: 84 },
1644
+ icon: { width: 100, height: 96 },
1645
+ subroutine: { width: 220, height: 64 }
1646
+ };
1647
+ }
1648
+ });
1649
+
1650
+ // src/components/diagrams/ERRenderer.tsx
1651
+ var ERRenderer_exports = {};
1652
+ __export(ERRenderer_exports, {
1653
+ default: () => ERRenderer
1654
+ });
1655
+ function ERRenderer({ ir, dark = false, handleRef }) {
1656
+ const theme = getDiagramTheme(dark);
1657
+ const containerRef = useRef(null);
1658
+ const svg = useMemo(() => {
1659
+ const g = new dagre2.graphlib.Graph();
1660
+ g.setGraph({ rankdir: "LR", nodesep: 60, ranksep: 100, marginx: 24, marginy: 24 });
1661
+ g.setDefaultEdgeLabel(() => ({}));
1662
+ const tableHeight = (cols) => TABLE_HEADER_HEIGHT + cols * TABLE_ROW_HEIGHT;
1663
+ for (const table of ir.schema.tables) {
1664
+ g.setNode(table.name, { width: TABLE_NODE_WIDTH, height: tableHeight(table.columns.length) });
1665
+ }
1666
+ for (const rel of ir.schema.relations) {
1667
+ if (g.hasNode(rel.fromTable) && g.hasNode(rel.toTable)) g.setEdge(rel.fromTable, rel.toTable);
1668
+ }
1669
+ dagre2.layout(g);
1670
+ const positions = /* @__PURE__ */ new Map();
1671
+ for (const table of ir.schema.tables) {
1672
+ const { x, y } = g.node(table.name);
1673
+ const h = tableHeight(table.columns.length);
1674
+ positions.set(table.name, { x: x - TABLE_NODE_WIDTH / 2, y: y - h / 2, width: TABLE_NODE_WIDTH, height: h });
1675
+ }
1676
+ return buildErSvg(ir, positions, { dark });
1677
+ }, [ir, dark]);
1678
+ useEffect(() => {
1679
+ if (!handleRef) return;
1680
+ const handle = {
1681
+ getSvgElement: () => svgStringToElement(svg)
1682
+ };
1683
+ handleRef.current = handle;
1684
+ return () => {
1685
+ if (handleRef.current === handle) handleRef.current = null;
1686
+ };
1687
+ }, [handleRef, svg]);
1688
+ return /* @__PURE__ */ jsx(
1689
+ "div",
1690
+ {
1691
+ ref: containerRef,
1692
+ className: "er-renderer",
1693
+ style: { width: "100%", background: theme.canvasBg, borderRadius: 12, padding: 12, overflow: "auto" },
1694
+ dangerouslySetInnerHTML: { __html: svg }
1695
+ }
1696
+ );
1697
+ }
1698
+ var TABLE_NODE_WIDTH, TABLE_HEADER_HEIGHT, TABLE_ROW_HEIGHT;
1699
+ var init_ERRenderer = __esm({
1700
+ "src/components/diagrams/ERRenderer.tsx"() {
1701
+ init_theme();
1702
+ init_svgBuilders();
1703
+ TABLE_NODE_WIDTH = 240;
1704
+ TABLE_HEADER_HEIGHT = 34;
1705
+ TABLE_ROW_HEIGHT = 26;
1706
+ }
1707
+ });
1708
+
1709
+ // src/components/diagrams/PieRenderer.tsx
1710
+ var PieRenderer_exports = {};
1711
+ __export(PieRenderer_exports, {
1712
+ default: () => PieRenderer
1713
+ });
1714
+ function PieRenderer({ ir, dark = false, handleRef }) {
1715
+ const theme = getDiagramTheme(dark);
1716
+ const containerRef = useRef(null);
1717
+ const svg = useMemo(() => buildPieSvg(ir, { dark }), [ir, dark]);
1718
+ useEffect(() => {
1719
+ if (!handleRef) return;
1720
+ const handle = {
1721
+ getSvgElement: () => svgStringToElement(svg)
1722
+ };
1723
+ handleRef.current = handle;
1724
+ return () => {
1725
+ if (handleRef.current === handle) handleRef.current = null;
1726
+ };
1727
+ }, [handleRef, svg]);
1728
+ return /* @__PURE__ */ jsx(
1729
+ "div",
1730
+ {
1731
+ ref: containerRef,
1732
+ className: "pie-renderer",
1733
+ style: { width: "100%", background: theme.canvasBg, borderRadius: 12, padding: 12, overflow: "auto" },
1734
+ dangerouslySetInnerHTML: { __html: svg }
1735
+ }
1736
+ );
1737
+ }
1738
+ var init_PieRenderer = __esm({
1739
+ "src/components/diagrams/PieRenderer.tsx"() {
1740
+ init_theme();
1741
+ init_svgBuilders();
1742
+ }
1743
+ });
1744
+
1745
+ // src/components/diagrams/QuadrantRenderer.tsx
1746
+ var QuadrantRenderer_exports = {};
1747
+ __export(QuadrantRenderer_exports, {
1748
+ default: () => QuadrantRenderer
1749
+ });
1750
+ function QuadrantRenderer({ ir, dark = false, handleRef }) {
1751
+ const theme = getDiagramTheme(dark);
1752
+ const containerRef = useRef(null);
1753
+ const svg = useMemo(() => buildQuadrantSvg(ir, { dark }), [ir, dark]);
1754
+ useEffect(() => {
1755
+ if (!handleRef) return;
1756
+ const handle = {
1757
+ getSvgElement: () => svgStringToElement(svg)
1758
+ };
1759
+ handleRef.current = handle;
1760
+ return () => {
1761
+ if (handleRef.current === handle) handleRef.current = null;
1762
+ };
1763
+ }, [handleRef, svg]);
1764
+ return /* @__PURE__ */ jsx(
1765
+ "div",
1766
+ {
1767
+ ref: containerRef,
1768
+ className: "quadrant-renderer",
1769
+ style: { width: "100%", background: theme.canvasBg, borderRadius: 12, padding: 12, overflow: "auto" },
1770
+ dangerouslySetInnerHTML: { __html: svg }
1771
+ }
1772
+ );
1773
+ }
1774
+ var init_QuadrantRenderer = __esm({
1775
+ "src/components/diagrams/QuadrantRenderer.tsx"() {
1776
+ init_theme();
1777
+ init_svgBuilders();
1778
+ }
1779
+ });
1780
+
1781
+ // src/components/diagrams/JourneyRenderer.tsx
1782
+ var JourneyRenderer_exports = {};
1783
+ __export(JourneyRenderer_exports, {
1784
+ default: () => JourneyRenderer
1785
+ });
1786
+ function JourneyRenderer({ ir, dark = false, handleRef }) {
1787
+ const theme = getDiagramTheme(dark);
1788
+ const containerRef = useRef(null);
1789
+ const svg = useMemo(() => buildJourneySvg(ir, { dark }), [ir, dark]);
1790
+ useEffect(() => {
1791
+ if (!handleRef) return;
1792
+ const handle = {
1793
+ getSvgElement: () => svgStringToElement(svg)
1794
+ };
1795
+ handleRef.current = handle;
1796
+ return () => {
1797
+ if (handleRef.current === handle) handleRef.current = null;
1798
+ };
1799
+ }, [handleRef, svg]);
1800
+ return /* @__PURE__ */ jsx(
1801
+ "div",
1802
+ {
1803
+ ref: containerRef,
1804
+ className: "journey-renderer",
1805
+ style: { width: "100%", background: theme.canvasBg, borderRadius: 12, padding: 12, overflow: "auto" },
1806
+ dangerouslySetInnerHTML: { __html: svg }
1807
+ }
1808
+ );
1809
+ }
1810
+ var init_JourneyRenderer = __esm({
1811
+ "src/components/diagrams/JourneyRenderer.tsx"() {
1812
+ init_theme();
1813
+ init_svgBuilders();
1814
+ }
1815
+ });
1816
+
1817
+ // src/components/diagrams/SequenceRenderer.tsx
1818
+ var SequenceRenderer_exports = {};
1819
+ __export(SequenceRenderer_exports, {
1820
+ default: () => SequenceRenderer
1821
+ });
1822
+ function SequenceRenderer({ ir, dark = false, handleRef }) {
1823
+ const theme = getDiagramTheme(dark);
1824
+ const containerRef = useRef(null);
1825
+ const layout = useMemo(() => computeLayout(ir.participants, ir.steps), [ir.participants, ir.steps]);
1826
+ useEffect(() => {
1827
+ if (!handleRef) return;
1828
+ const handle = {
1829
+ getSvgElement: () => containerRef.current?.querySelector("svg.sequence-svg") ?? null
1830
+ };
1831
+ handleRef.current = handle;
1832
+ return () => {
1833
+ if (handleRef.current === handle) handleRef.current = null;
1834
+ };
1835
+ }, [handleRef, layout]);
1836
+ const lifelineColor = dark ? "#475569" : "#cbd5e1";
1837
+ const headFill = dark ? "#1e293b" : "#f1f5f9";
1838
+ const headBorder = dark ? "#475569" : "#cbd5e1";
1839
+ const headText = dark ? "#e2e8f0" : "#1e293b";
1840
+ const arrowColor = dark ? "#94a3b8" : "#64748b";
1841
+ const labelColor = dark ? "#cbd5e1" : "#334155";
1842
+ const noteFill = dark ? "#451a03" : "#fef3c7";
1843
+ const noteBorder = dark ? "#92400e" : "#fcd34d";
1844
+ const noteText = dark ? "#fde68a" : "#78350f";
1845
+ const titleY = ir.title ? TITLE_H : 0;
1846
+ const headTopY = titleY + HEAD_PAD_Y;
1847
+ return /* @__PURE__ */ jsx(
1848
+ "div",
1849
+ {
1850
+ ref: containerRef,
1851
+ className: "sequence-renderer",
1852
+ style: {
1853
+ width: "100%",
1854
+ background: theme.canvasBg,
1855
+ borderRadius: 12,
1856
+ padding: 16,
1857
+ overflow: "auto"
1858
+ },
1859
+ children: /* @__PURE__ */ jsxs(
1860
+ "svg",
1861
+ {
1862
+ className: "sequence-svg",
1863
+ xmlns: "http://www.w3.org/2000/svg",
1864
+ width: layout.width,
1865
+ height: layout.height + titleY,
1866
+ viewBox: `0 0 ${layout.width} ${layout.height + titleY}`,
1867
+ style: { display: "block", minWidth: "100%", fontFamily: '"Inter", ui-sans-serif, system-ui, sans-serif' },
1868
+ children: [
1869
+ ir.title && /* @__PURE__ */ jsx(
1870
+ "text",
1871
+ {
1872
+ x: layout.width / 2,
1873
+ y: 20,
1874
+ textAnchor: "middle",
1875
+ fontSize: 15,
1876
+ fontWeight: 600,
1877
+ fill: headText,
1878
+ children: ir.title
1879
+ }
1880
+ ),
1881
+ ir.participants.map((p) => {
1882
+ const x = layout.participantX.get(p.id);
1883
+ if (x === void 0) return null;
1884
+ return /* @__PURE__ */ jsx(
1885
+ "line",
1886
+ {
1887
+ x1: x,
1888
+ x2: x,
1889
+ y1: headTopY + PART_BOX_H,
1890
+ y2: headTopY + PART_BOX_H + layout.contentH + STEP_GAP,
1891
+ stroke: lifelineColor,
1892
+ strokeWidth: 1,
1893
+ strokeDasharray: "4 4"
1894
+ },
1895
+ `life-${p.id}`
1896
+ );
1897
+ }),
1898
+ ir.participants.map((p) => {
1899
+ const x = layout.participantX.get(p.id);
1900
+ if (x === void 0) return null;
1901
+ return /* @__PURE__ */ jsxs("g", { children: [
1902
+ /* @__PURE__ */ jsx(
1903
+ "rect",
1904
+ {
1905
+ x: x - PART_BOX_W / 2,
1906
+ y: headTopY,
1907
+ width: PART_BOX_W,
1908
+ height: PART_BOX_H,
1909
+ rx: 6,
1910
+ fill: headFill,
1911
+ stroke: headBorder,
1912
+ strokeWidth: 1
1913
+ }
1914
+ ),
1915
+ /* @__PURE__ */ jsx(
1916
+ "text",
1917
+ {
1918
+ x,
1919
+ y: headTopY + PART_BOX_H / 2 + 4,
1920
+ textAnchor: "middle",
1921
+ fontSize: 12,
1922
+ fontWeight: 600,
1923
+ fill: headText,
1924
+ children: truncate(p.label, 18)
1925
+ }
1926
+ )
1927
+ ] }, `head-${p.id}`);
1928
+ }),
1929
+ layout.placedSteps.map((ps, i) => {
1930
+ if (ps.step.kind === "message") {
1931
+ const fromX = layout.participantX.get(ps.step.from);
1932
+ const isSelf = ps.step.from === ps.step.to;
1933
+ const isRightmost = isSelf && fromX === Math.max(...layout.participantX.values());
1934
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
1935
+ MessageStep,
1936
+ {
1937
+ msg: ps.step,
1938
+ fromX,
1939
+ toX: layout.participantX.get(ps.step.to),
1940
+ y: headTopY + PART_BOX_H + ps.y,
1941
+ arrowColor,
1942
+ labelColor,
1943
+ selfDirection: isRightmost ? "left" : "right",
1944
+ canvasWidth: layout.width
1945
+ }
1946
+ ) }, i);
1947
+ }
1948
+ return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(
1949
+ NoteStep,
1950
+ {
1951
+ note: ps.step,
1952
+ participantXs: ps.step.participants.map((id) => layout.participantX.get(id)).filter((x) => x !== void 0),
1953
+ y: headTopY + PART_BOX_H + ps.y,
1954
+ fill: noteFill,
1955
+ border: noteBorder,
1956
+ textColor: noteText
1957
+ }
1958
+ ) }, i);
1959
+ }),
1960
+ ir.participants.map((p) => {
1961
+ const x = layout.participantX.get(p.id);
1962
+ if (x === void 0) return null;
1963
+ const footY = headTopY + PART_BOX_H + layout.contentH + STEP_GAP - PART_BOX_H / 2;
1964
+ return /* @__PURE__ */ jsxs("g", { children: [
1965
+ /* @__PURE__ */ jsx(
1966
+ "rect",
1967
+ {
1968
+ x: x - PART_BOX_W / 2,
1969
+ y: footY,
1970
+ width: PART_BOX_W,
1971
+ height: PART_BOX_H,
1972
+ rx: 6,
1973
+ fill: headFill,
1974
+ stroke: headBorder,
1975
+ strokeWidth: 1
1976
+ }
1977
+ ),
1978
+ /* @__PURE__ */ jsx(
1979
+ "text",
1980
+ {
1981
+ x,
1982
+ y: footY + PART_BOX_H / 2 + 4,
1983
+ textAnchor: "middle",
1984
+ fontSize: 12,
1985
+ fontWeight: 600,
1986
+ fill: headText,
1987
+ children: truncate(p.label, 18)
1988
+ }
1989
+ )
1990
+ ] }, `foot-${p.id}`);
1991
+ }),
1992
+ /* @__PURE__ */ jsx(ArrowDefs, { arrowColor })
1993
+ ]
1994
+ }
1995
+ )
1996
+ }
1997
+ );
1998
+ }
1999
+ function MessageStep({
2000
+ msg,
2001
+ fromX,
2002
+ toX,
2003
+ y,
2004
+ arrowColor,
2005
+ labelColor,
2006
+ selfDirection = "right",
2007
+ canvasWidth
2008
+ }) {
2009
+ const isReply = msg.arrow === "reply";
2010
+ const isAsync = msg.arrow === "async";
2011
+ const isCross = msg.arrow === "cross";
2012
+ const dasharray = isReply ? "5 4" : isAsync ? "8 3" : void 0;
2013
+ const markerEnd = isCross ? "url(#arr-cross)" : isAsync ? "url(#arr-open)" : "url(#arr-solid)";
2014
+ const labelX = (fromX + toX) / 2;
2015
+ const labelY = y - 8;
2016
+ const showSelf = fromX === toX;
2017
+ if (showSelf) {
2018
+ const dir = selfDirection === "left" ? -1 : 1;
2019
+ const loopEndX = fromX + dir * SELF_LOOP_W;
2020
+ const labelText = truncate(msg.label, 28);
2021
+ const charW = 6.2;
2022
+ const approxLabelW = labelText.length * charW;
2023
+ const labelXPos = dir === 1 ? (
2024
+ // loop opens right; label sits to the right of the loop's edge
2025
+ Math.min(loopEndX + 8, (canvasWidth ?? Infinity) - approxLabelW - 4)
2026
+ ) : (
2027
+ // loop opens left; label sits to the left of the loop's edge
2028
+ Math.max(loopEndX - approxLabelW - 8, 4)
2029
+ );
2030
+ return /* @__PURE__ */ jsxs("g", { children: [
2031
+ /* @__PURE__ */ jsx(
2032
+ "path",
2033
+ {
2034
+ d: `M ${fromX} ${y} h ${dir * SELF_LOOP_W} v 22 h ${-dir * SELF_LOOP_W}`,
2035
+ fill: "none",
2036
+ stroke: arrowColor,
2037
+ strokeWidth: 1.4,
2038
+ strokeDasharray: dasharray,
2039
+ markerEnd
2040
+ }
2041
+ ),
2042
+ /* @__PURE__ */ jsx(
2043
+ "text",
2044
+ {
2045
+ x: labelXPos,
2046
+ y: y + 14,
2047
+ fontSize: 11,
2048
+ fill: labelColor,
2049
+ textAnchor: dir === 1 ? "start" : "start",
2050
+ children: labelText
2051
+ }
2052
+ )
2053
+ ] });
2054
+ }
2055
+ return /* @__PURE__ */ jsxs("g", { children: [
2056
+ /* @__PURE__ */ jsx(
2057
+ "text",
2058
+ {
2059
+ x: labelX,
2060
+ y: labelY,
2061
+ textAnchor: "middle",
2062
+ fontSize: 11,
2063
+ fill: labelColor,
2064
+ fontWeight: 500,
2065
+ children: truncate(msg.label, 60)
2066
+ }
2067
+ ),
2068
+ /* @__PURE__ */ jsx(
2069
+ "line",
2070
+ {
2071
+ x1: fromX,
2072
+ x2: toX,
2073
+ y1: y,
2074
+ y2: y,
2075
+ stroke: arrowColor,
2076
+ strokeWidth: 1.4,
2077
+ strokeDasharray: dasharray,
2078
+ markerEnd
2079
+ }
2080
+ )
2081
+ ] });
2082
+ }
2083
+ function NoteStep({
2084
+ note,
2085
+ participantXs,
2086
+ y,
2087
+ fill,
2088
+ border,
2089
+ textColor
2090
+ }) {
2091
+ if (participantXs.length === 0) return null;
2092
+ let x = 0;
2093
+ let w = 160;
2094
+ if (note.side === "over") {
2095
+ const min = Math.min(...participantXs);
2096
+ const max = Math.max(...participantXs);
2097
+ w = Math.max(160, max - min + 80);
2098
+ x = min - (w - (max - min)) / 2;
2099
+ } else if (note.side === "left") {
2100
+ x = participantXs[0] - 90 - 60;
2101
+ w = 140;
2102
+ } else {
2103
+ x = participantXs[0] + 60;
2104
+ w = 140;
2105
+ }
2106
+ return /* @__PURE__ */ jsxs("g", { children: [
2107
+ /* @__PURE__ */ jsx(
2108
+ "rect",
2109
+ {
2110
+ x,
2111
+ y: y - 6,
2112
+ width: w,
2113
+ height: NOTE_H,
2114
+ rx: 4,
2115
+ fill,
2116
+ stroke: border,
2117
+ strokeWidth: 1
2118
+ }
2119
+ ),
2120
+ /* @__PURE__ */ jsx("text", { x: x + w / 2, y: y + NOTE_H / 2 - 1, textAnchor: "middle", fontSize: 11, fill: textColor, children: truncate(note.text, 50) })
2121
+ ] });
2122
+ }
2123
+ function ArrowDefs({ arrowColor }) {
2124
+ return /* @__PURE__ */ jsxs("defs", { children: [
2125
+ /* @__PURE__ */ jsx("marker", { id: "arr-solid", viewBox: "0 0 10 10", refX: "9", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto-start-reverse", children: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 10 5 L 0 10 z", fill: arrowColor }) }),
2126
+ /* @__PURE__ */ jsx("marker", { id: "arr-open", viewBox: "0 0 10 10", refX: "9", refY: "5", markerWidth: "9", markerHeight: "9", orient: "auto-start-reverse", children: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 10 5 L 0 10", fill: "none", stroke: arrowColor, strokeWidth: "1.5" }) }),
2127
+ /* @__PURE__ */ jsx("marker", { id: "arr-cross", viewBox: "0 0 10 10", refX: "5", refY: "5", markerWidth: "9", markerHeight: "9", orient: "auto", children: /* @__PURE__ */ jsx("path", { d: "M 0 0 L 10 10 M 10 0 L 0 10", stroke: arrowColor, strokeWidth: "1.5" }) })
2128
+ ] });
2129
+ }
2130
+ function computeLayout(participants, steps) {
2131
+ const participantX = /* @__PURE__ */ new Map();
2132
+ participants.forEach((p, i) => {
2133
+ participantX.set(p.id, SIDE_PAD + PART_BOX_W / 2 + i * PARTICIPANT_GAP);
2134
+ });
2135
+ const lastX = (participants.length - 1) * PARTICIPANT_GAP + SIDE_PAD * 2 + PART_BOX_W;
2136
+ const placedSteps = [];
2137
+ let y = 36;
2138
+ for (const step of steps) {
2139
+ placedSteps.push({ step, y });
2140
+ y += step.kind === "note" ? NOTE_H + 16 : STEP_GAP;
2141
+ }
2142
+ return {
2143
+ width: lastX,
2144
+ height: HEAD_PAD_Y + PART_BOX_H + y + STEP_GAP + PART_BOX_H + FOOT_HEIGHT,
2145
+ contentH: y,
2146
+ participantX,
2147
+ placedSteps
2148
+ };
2149
+ }
2150
+ function truncate(s, max) {
2151
+ return s.length <= max ? s : s.slice(0, max - 1) + "\u2026";
2152
+ }
2153
+ var PARTICIPANT_GAP, STEP_GAP, HEAD_PAD_Y, FOOT_HEIGHT, SIDE_PAD, PART_BOX_W, PART_BOX_H, NOTE_H, TITLE_H, SELF_LOOP_W;
2154
+ var init_SequenceRenderer = __esm({
2155
+ "src/components/diagrams/SequenceRenderer.tsx"() {
2156
+ init_theme();
2157
+ PARTICIPANT_GAP = 200;
2158
+ STEP_GAP = 56;
2159
+ HEAD_PAD_Y = 20;
2160
+ FOOT_HEIGHT = 36;
2161
+ SIDE_PAD = 40;
2162
+ PART_BOX_W = 150;
2163
+ PART_BOX_H = 36;
2164
+ NOTE_H = 38;
2165
+ TITLE_H = 28;
2166
+ SELF_LOOP_W = 60;
2167
+ }
2168
+ });
2169
+
2170
+ // src/components/diagrams/ClassRenderer.tsx
2171
+ var ClassRenderer_exports = {};
2172
+ __export(ClassRenderer_exports, {
2173
+ default: () => ClassRenderer
2174
+ });
2175
+ function classBoxSize(memberCount) {
2176
+ return { width: 220, height: Math.max(64, 40 + memberCount * 18) };
2177
+ }
2178
+ function ClassRenderer({ ir, dark = false, handleRef }) {
2179
+ const theme = getDiagramTheme(dark);
2180
+ const containerRef = useRef(null);
2181
+ const svg = useMemo(() => {
2182
+ const g = new dagre2.graphlib.Graph();
2183
+ g.setGraph({ rankdir: "TB", nodesep: 50, ranksep: 80, marginx: 24, marginy: 24 });
2184
+ g.setDefaultEdgeLabel(() => ({}));
2185
+ for (const cls of ir.classes) g.setNode(cls.id, classBoxSize(cls.members.length));
2186
+ for (const rel of ir.relations) {
2187
+ if (g.hasNode(rel.source) && g.hasNode(rel.target)) g.setEdge(rel.source, rel.target);
2188
+ }
2189
+ dagre2.layout(g);
2190
+ const positions = /* @__PURE__ */ new Map();
2191
+ for (const cls of ir.classes) {
2192
+ const { x, y } = g.node(cls.id);
2193
+ const size = classBoxSize(cls.members.length);
2194
+ positions.set(cls.id, { x: x - size.width / 2, y: y - size.height / 2, width: size.width, height: size.height });
2195
+ }
2196
+ return buildClassSvg(ir, positions, { dark });
2197
+ }, [ir, dark]);
2198
+ useEffect(() => {
2199
+ if (!handleRef) return;
2200
+ const handle = {
2201
+ getSvgElement: () => svgStringToElement(svg)
2202
+ };
2203
+ handleRef.current = handle;
2204
+ return () => {
2205
+ if (handleRef.current === handle) handleRef.current = null;
2206
+ };
2207
+ }, [handleRef, svg]);
2208
+ return /* @__PURE__ */ jsx(
2209
+ "div",
2210
+ {
2211
+ ref: containerRef,
2212
+ className: "class-renderer",
2213
+ style: { width: "100%", background: theme.canvasBg, borderRadius: 12, padding: 12, overflow: "auto" },
2214
+ dangerouslySetInnerHTML: { __html: svg }
2215
+ }
2216
+ );
2217
+ }
2218
+ var init_ClassRenderer = __esm({
2219
+ "src/components/diagrams/ClassRenderer.tsx"() {
2220
+ init_theme();
2221
+ init_svgBuilders();
2222
+ }
2223
+ });
2224
+
2225
+ // src/components/diagrams/StateRenderer.tsx
2226
+ var StateRenderer_exports = {};
2227
+ __export(StateRenderer_exports, {
2228
+ default: () => StateRenderer
2229
+ });
2230
+ function stateNodeSize(s) {
2231
+ if (s.kind === "start" || s.kind === "end") return { width: 80, height: 32 };
2232
+ const len = (s.label || s.id).length;
2233
+ return { width: Math.max(96, len * 9 + 40), height: 44 };
2234
+ }
2235
+ function layoutComposite(parentId, ir) {
2236
+ const children = ir.states.filter((s) => s.parent === parentId);
2237
+ if (children.length === 0) {
2238
+ return { width: 200, height: COMPOSITE_HEADER_HEIGHT + 60, positions: /* @__PURE__ */ new Map() };
2239
+ }
2240
+ const g = new dagre2.graphlib.Graph();
2241
+ g.setGraph({ rankdir: "TB", nodesep: 60, ranksep: 70, marginx: 16, marginy: 16 });
2242
+ g.setDefaultEdgeLabel(() => ({}));
2243
+ for (const c of children) g.setNode(c.id, stateNodeSize(c));
2244
+ for (const t of ir.transitions) {
2245
+ if (t.parent !== parentId) continue;
2246
+ if (g.hasNode(t.source) && g.hasNode(t.target)) g.setEdge(t.source, t.target);
2247
+ }
2248
+ dagre2.layout(g);
2249
+ let minLeft = Infinity, minTop = Infinity, maxRight = 0, maxBottom = 0;
2250
+ const tmp = /* @__PURE__ */ new Map();
2251
+ for (const c of children) {
2252
+ const { x, y } = g.node(c.id);
2253
+ const sz = stateNodeSize(c);
2254
+ const left = x - sz.width / 2;
2255
+ const top = y - sz.height / 2;
2256
+ tmp.set(c.id, { x: left, y: top });
2257
+ minLeft = Math.min(minLeft, left);
2258
+ minTop = Math.min(minTop, top);
2259
+ maxRight = Math.max(maxRight, left + sz.width);
2260
+ maxBottom = Math.max(maxBottom, top + sz.height);
2261
+ }
2262
+ const dx = COMPOSITE_PAD - minLeft;
2263
+ const dy = COMPOSITE_HEADER_HEIGHT + COMPOSITE_PAD - minTop;
2264
+ const positions = /* @__PURE__ */ new Map();
2265
+ for (const [id, p] of tmp) positions.set(id, { x: p.x + dx, y: p.y + dy });
2266
+ return {
2267
+ width: maxRight - minLeft + COMPOSITE_PAD * 2,
2268
+ height: maxBottom - minTop + COMPOSITE_HEADER_HEIGHT + COMPOSITE_PAD * 2,
2269
+ positions
2270
+ };
2271
+ }
2272
+ function StateRenderer({ ir, dark = false, handleRef }) {
2273
+ const theme = getDiagramTheme(dark);
2274
+ const containerRef = useRef(null);
2275
+ const svg = useMemo(() => {
2276
+ const compositeIds = ir.states.filter((s) => s.kind === "composite").map((s) => s.id);
2277
+ const innerLayouts = /* @__PURE__ */ new Map();
2278
+ for (const id of compositeIds) innerLayouts.set(id, layoutComposite(id, ir));
2279
+ const buildPositions = { topLevel: /* @__PURE__ */ new Map(), children: /* @__PURE__ */ new Map() };
2280
+ const topLevel = ir.states.filter((s) => !s.parent);
2281
+ const g = new dagre2.graphlib.Graph();
2282
+ g.setGraph({ rankdir: "TB", nodesep: 80, ranksep: 90, marginx: 32, marginy: 32 });
2283
+ g.setDefaultEdgeLabel(() => ({}));
2284
+ for (const s of topLevel) {
2285
+ if (s.kind === "composite") {
2286
+ const inner = innerLayouts.get(s.id);
2287
+ g.setNode(s.id, { width: inner.width, height: inner.height });
2288
+ } else {
2289
+ g.setNode(s.id, stateNodeSize(s));
2290
+ }
2291
+ }
2292
+ for (const t of ir.transitions) {
2293
+ if (t.parent) continue;
2294
+ if (g.hasNode(t.source) && g.hasNode(t.target)) g.setEdge(t.source, t.target);
2295
+ }
2296
+ dagre2.layout(g);
2297
+ for (const s of topLevel) {
2298
+ const { x, y } = g.node(s.id);
2299
+ if (s.kind === "composite") {
2300
+ const inner = innerLayouts.get(s.id);
2301
+ buildPositions.topLevel.set(s.id, {
2302
+ x: x - inner.width / 2,
2303
+ y: y - inner.height / 2,
2304
+ width: inner.width,
2305
+ height: inner.height
2306
+ });
2307
+ } else {
2308
+ const size = stateNodeSize(s);
2309
+ buildPositions.topLevel.set(s.id, {
2310
+ x: x - size.width / 2,
2311
+ y: y - size.height / 2,
2312
+ width: size.width,
2313
+ height: size.height
2314
+ });
2315
+ }
2316
+ }
2317
+ for (const compId of compositeIds) {
2318
+ const inner = innerLayouts.get(compId);
2319
+ const children = ir.states.filter((s) => s.parent === compId);
2320
+ for (const c of children) {
2321
+ const pos = inner.positions.get(c.id);
2322
+ if (!pos) continue;
2323
+ const size = stateNodeSize(c);
2324
+ buildPositions.children.set(c.id, {
2325
+ x: pos.x,
2326
+ y: pos.y,
2327
+ width: size.width,
2328
+ height: size.height,
2329
+ parent: compId
2330
+ });
2331
+ }
2332
+ }
2333
+ return buildStateSvg(ir, buildPositions, { dark });
2334
+ }, [ir, dark]);
2335
+ useEffect(() => {
2336
+ if (!handleRef) return;
2337
+ const handle = {
2338
+ getSvgElement: () => svgStringToElement(svg)
2339
+ };
2340
+ handleRef.current = handle;
2341
+ return () => {
2342
+ if (handleRef.current === handle) handleRef.current = null;
2343
+ };
2344
+ }, [handleRef, svg]);
2345
+ return /* @__PURE__ */ jsx(
2346
+ "div",
2347
+ {
2348
+ ref: containerRef,
2349
+ className: "state-renderer",
2350
+ style: { width: "100%", background: theme.canvasBg, borderRadius: 12, padding: 12, overflow: "auto" },
2351
+ dangerouslySetInnerHTML: { __html: svg }
2352
+ }
2353
+ );
2354
+ }
2355
+ var COMPOSITE_HEADER_HEIGHT, COMPOSITE_PAD;
2356
+ var init_StateRenderer = __esm({
2357
+ "src/components/diagrams/StateRenderer.tsx"() {
2358
+ init_theme();
2359
+ init_svgBuilders();
2360
+ COMPOSITE_HEADER_HEIGHT = 32;
2361
+ COMPOSITE_PAD = 18;
2362
+ }
2363
+ });
2364
+
2365
+ // src/components/diagrams/GanttRenderer.tsx
2366
+ var GanttRenderer_exports = {};
2367
+ __export(GanttRenderer_exports, {
2368
+ default: () => GanttRenderer
2369
+ });
2370
+ function GanttRenderer({ ir, dark = false, handleRef }) {
2371
+ const theme = getDiagramTheme(dark);
2372
+ const containerRef = useRef(null);
2373
+ const svg = useMemo(() => buildGanttSvg(ir, { dark }), [ir, dark]);
2374
+ useEffect(() => {
2375
+ if (!handleRef) return;
2376
+ const handle = {
2377
+ getSvgElement: () => svgStringToElement(svg)
2378
+ };
2379
+ handleRef.current = handle;
2380
+ return () => {
2381
+ if (handleRef.current === handle) handleRef.current = null;
2382
+ };
2383
+ }, [handleRef, svg]);
2384
+ return /* @__PURE__ */ jsx(
2385
+ "div",
2386
+ {
2387
+ ref: containerRef,
2388
+ className: "gantt-renderer",
2389
+ style: { width: "100%", background: theme.canvasBg, borderRadius: 12, padding: 12, overflowX: "auto" },
2390
+ dangerouslySetInnerHTML: { __html: svg }
2391
+ }
2392
+ );
2393
+ }
2394
+ var init_GanttRenderer = __esm({
2395
+ "src/components/diagrams/GanttRenderer.tsx"() {
2396
+ init_theme();
2397
+ init_svgBuilders();
2398
+ }
2399
+ });
2400
+
2401
+ // src/components/diagrams/TimelineRenderer.tsx
2402
+ var TimelineRenderer_exports = {};
2403
+ __export(TimelineRenderer_exports, {
2404
+ default: () => TimelineRenderer
2405
+ });
2406
+ function TimelineRenderer({ ir, dark = false, handleRef }) {
2407
+ const theme = getDiagramTheme(dark);
2408
+ const containerRef = useRef(null);
2409
+ const svg = useMemo(() => buildTimelineSvg(ir, { dark }), [ir, dark]);
2410
+ useEffect(() => {
2411
+ if (!handleRef) return;
2412
+ const handle = {
2413
+ getSvgElement: () => svgStringToElement(svg)
2414
+ };
2415
+ handleRef.current = handle;
2416
+ return () => {
2417
+ if (handleRef.current === handle) handleRef.current = null;
2418
+ };
2419
+ }, [handleRef, svg]);
2420
+ return /* @__PURE__ */ jsx(
2421
+ "div",
2422
+ {
2423
+ ref: containerRef,
2424
+ className: "timeline-renderer",
2425
+ style: { width: "100%", background: theme.canvasBg, borderRadius: 12, padding: 12, overflowX: "auto" },
2426
+ dangerouslySetInnerHTML: { __html: svg }
2427
+ }
2428
+ );
2429
+ }
2430
+ var init_TimelineRenderer = __esm({
2431
+ "src/components/diagrams/TimelineRenderer.tsx"() {
2432
+ init_theme();
2433
+ init_svgBuilders();
2434
+ }
2435
+ });
2436
+
2437
+ // src/components/diagrams/MindmapRenderer.tsx
2438
+ var MindmapRenderer_exports = {};
2439
+ __export(MindmapRenderer_exports, {
2440
+ default: () => MindmapRenderer
2441
+ });
2442
+ function radialLayout(root) {
2443
+ const positioned = [];
2444
+ const RING_DISTANCE = 180;
2445
+ const place = (node, depth, startAngle, endAngle) => {
2446
+ const angle = (startAngle + endAngle) / 2;
2447
+ const x = depth === 0 ? 0 : Math.cos(angle) * RING_DISTANCE * depth;
2448
+ const y = depth === 0 ? 0 : Math.sin(angle) * RING_DISTANCE * depth;
2449
+ positioned.push({ id: node.id, node, x, y, depth });
2450
+ if (node.children.length === 0) return;
2451
+ const span = endAngle - startAngle;
2452
+ const slice = span / node.children.length;
2453
+ for (let i = 0; i < node.children.length; i++) {
2454
+ place(node.children[i], depth + 1, startAngle + i * slice, startAngle + (i + 1) * slice);
2455
+ }
2456
+ };
2457
+ place(root, 0, 0, Math.PI * 2);
2458
+ let minX = 0, minY = 0, maxX = 0, maxY = 0;
2459
+ for (const p of positioned) {
2460
+ if (p.x < minX) minX = p.x;
2461
+ if (p.y < minY) minY = p.y;
2462
+ if (p.x > maxX) maxX = p.x;
2463
+ if (p.y > maxY) maxY = p.y;
2464
+ }
2465
+ for (const p of positioned) {
2466
+ p.x = p.x - minX + 200;
2467
+ p.y = p.y - minY + 100;
2468
+ }
2469
+ return { positioned, bounds: { w: maxX - minX + 400, h: maxY - minY + 200 } };
2470
+ }
2471
+ function MindmapRenderer({ ir, dark = false, handleRef }) {
2472
+ const theme = getDiagramTheme(dark);
2473
+ const containerRef = useRef(null);
2474
+ const svg = useMemo(() => {
2475
+ const { positioned } = radialLayout(ir.root);
2476
+ const positions = /* @__PURE__ */ new Map();
2477
+ for (const p of positioned) {
2478
+ const isRoot = p.depth === 0;
2479
+ const labelLen = p.node.label.length;
2480
+ const w = Math.max(isRoot ? 100 : 70, labelLen * (isRoot ? 9 : 7) + 40);
2481
+ const h = isRoot ? 44 : 32;
2482
+ positions.set(p.id, { x: p.x, y: p.y, width: w, height: h, depth: p.depth });
2483
+ }
2484
+ return buildMindmapSvg(ir, positions, { dark });
2485
+ }, [ir, dark]);
2486
+ useEffect(() => {
2487
+ if (!handleRef) return;
2488
+ const handle = {
2489
+ getSvgElement: () => svgStringToElement(svg)
2490
+ };
2491
+ handleRef.current = handle;
2492
+ return () => {
2493
+ if (handleRef.current === handle) handleRef.current = null;
2494
+ };
2495
+ }, [handleRef, svg]);
2496
+ return /* @__PURE__ */ jsx(
2497
+ "div",
2498
+ {
2499
+ ref: containerRef,
2500
+ className: "mindmap-renderer",
2501
+ style: { width: "100%", background: theme.canvasBg, borderRadius: 12, padding: 12, overflow: "auto" },
2502
+ dangerouslySetInnerHTML: { __html: svg }
2503
+ }
2504
+ );
2505
+ }
2506
+ var init_MindmapRenderer = __esm({
2507
+ "src/components/diagrams/MindmapRenderer.tsx"() {
2508
+ init_theme();
2509
+ init_svgBuilders();
2510
+ }
2511
+ });
2512
+
2513
+ // src/components/diagrams/ArchitectureRenderer.tsx
2514
+ var ArchitectureRenderer_exports = {};
2515
+ __export(ArchitectureRenderer_exports, {
2516
+ default: () => ArchitectureRenderer
2517
+ });
2518
+ function ArchitectureRenderer({ ir, dark = false, handleRef }) {
2519
+ const theme = getDiagramTheme(dark);
2520
+ const containerRef = useRef(null);
2521
+ const svg = useMemo(() => buildArchitectureSvg(ir, { dark }), [ir, dark]);
2522
+ useEffect(() => {
2523
+ if (!handleRef) return;
2524
+ const handle = {
2525
+ getSvgElement: () => svgStringToElement(svg)
2526
+ };
2527
+ handleRef.current = handle;
2528
+ return () => {
2529
+ if (handleRef.current === handle) handleRef.current = null;
2530
+ };
2531
+ }, [handleRef, svg]);
2532
+ return /* @__PURE__ */ jsx(
2533
+ "div",
2534
+ {
2535
+ ref: containerRef,
2536
+ className: "architecture-renderer",
2537
+ style: { width: "100%", background: theme.canvasBg, borderRadius: 12, padding: 12, overflow: "auto" },
2538
+ dangerouslySetInnerHTML: { __html: svg }
2539
+ }
2540
+ );
2541
+ }
2542
+ var init_ArchitectureRenderer = __esm({
2543
+ "src/components/diagrams/ArchitectureRenderer.tsx"() {
2544
+ init_theme();
2545
+ init_svgBuilders();
2546
+ }
2547
+ });
2548
+
2549
+ // src/components/diagrams/C4Renderer.tsx
2550
+ var C4Renderer_exports = {};
2551
+ __export(C4Renderer_exports, {
2552
+ default: () => C4Renderer
2553
+ });
2554
+ function C4Renderer({ ir, dark = false, handleRef }) {
2555
+ const theme = getDiagramTheme(dark);
2556
+ const containerRef = useRef(null);
2557
+ const svg = useMemo(() => buildC4Svg(ir, { dark }), [ir, dark]);
2558
+ useEffect(() => {
2559
+ if (!handleRef) return;
2560
+ const handle = {
2561
+ getSvgElement: () => svgStringToElement(svg)
2562
+ };
2563
+ handleRef.current = handle;
2564
+ return () => {
2565
+ if (handleRef.current === handle) handleRef.current = null;
2566
+ };
2567
+ }, [handleRef, svg]);
2568
+ return /* @__PURE__ */ jsx(
2569
+ "div",
2570
+ {
2571
+ ref: containerRef,
2572
+ className: "c4-renderer",
2573
+ style: { width: "100%", background: theme.canvasBg, borderRadius: 12, padding: 12, overflow: "auto" },
2574
+ dangerouslySetInnerHTML: { __html: svg }
2575
+ }
2576
+ );
2577
+ }
2578
+ var init_C4Renderer = __esm({
2579
+ "src/components/diagrams/C4Renderer.tsx"() {
2580
+ init_theme();
2581
+ init_svgBuilders();
2582
+ }
2583
+ });
2584
+
2585
+ // src/components/diagrams/GitGraphRenderer.tsx
2586
+ var GitGraphRenderer_exports = {};
2587
+ __export(GitGraphRenderer_exports, {
2588
+ default: () => GitGraphRenderer
2589
+ });
2590
+ function GitGraphRenderer({ ir, dark = false, handleRef }) {
2591
+ const theme = getDiagramTheme(dark);
2592
+ const containerRef = useRef(null);
2593
+ const svg = useMemo(() => buildGitGraphSvg(ir, { dark }), [ir, dark]);
2594
+ useEffect(() => {
2595
+ if (!handleRef) return;
2596
+ const handle = {
2597
+ getSvgElement: () => svgStringToElement(svg)
2598
+ };
2599
+ handleRef.current = handle;
2600
+ return () => {
2601
+ if (handleRef.current === handle) handleRef.current = null;
2602
+ };
2603
+ }, [handleRef, svg]);
2604
+ return /* @__PURE__ */ jsx(
2605
+ "div",
2606
+ {
2607
+ ref: containerRef,
2608
+ className: "gitgraph-renderer",
2609
+ style: { width: "100%", background: theme.canvasBg, borderRadius: 12, padding: 12, overflow: "auto" },
2610
+ dangerouslySetInnerHTML: { __html: svg }
2611
+ }
2612
+ );
2613
+ }
2614
+ var init_GitGraphRenderer = __esm({
2615
+ "src/components/diagrams/GitGraphRenderer.tsx"() {
2616
+ init_theme();
2617
+ init_svgBuilders();
2618
+ }
2619
+ });
2620
+
2621
+ // src/utils/diagrams/parser.ts
2622
+ var HEADER_KEYWORDS = [
2623
+ { re: /^(flowchart|graph)\b/i, type: "flowchart" },
2624
+ { re: /^erdiagram\b/i, type: "er" },
2625
+ { re: /^sequencediagram\b/i, type: "sequence" },
2626
+ { re: /^classdiagram\b/i, type: "class" },
2627
+ { re: /^statediagram(-v2)?\b/i, type: "state" },
2628
+ { re: /^gantt\b/i, type: "gantt" },
2629
+ { re: /^pie\b/i, type: "pie" },
2630
+ { re: /^quadrantchart\b/i, type: "quadrant" },
2631
+ { re: /^mindmap\b/i, type: "mindmap" },
2632
+ { re: /^gitgraph\b/i, type: "gitgraph" },
2633
+ { re: /^timeline\b/i, type: "timeline" },
2634
+ { re: /^journey\b/i, type: "journey" },
2635
+ { re: /^c4(context|container|component|deployment)/i, type: "c4" },
2636
+ { re: /^architecture(-beta)?\b/i, type: "architecture" }
2637
+ ];
2638
+ async function detectDiagramType(source) {
2639
+ const clean = source.replace(/^/, "");
2640
+ const firstLine = clean.split("\n").map((l) => l.trim()).find((l) => l.length > 0 && !l.startsWith("%%"));
2641
+ if (!firstLine) return null;
2642
+ for (const { re, type } of HEADER_KEYWORDS) {
2643
+ if (re.test(firstLine)) return type;
2644
+ }
2645
+ return "unsupported";
2646
+ }
2647
+ async function parseToIR(source) {
2648
+ const type = await detectDiagramType(source);
2649
+ if (type === null) {
2650
+ return { ok: false, source, error: "Empty or whitespace-only source" };
2651
+ }
2652
+ if (type === "unsupported") {
2653
+ return { ok: false, source, error: "Unrecognized diagram type" };
2654
+ }
2655
+ try {
2656
+ switch (type) {
2657
+ case "flowchart":
2658
+ return { ok: true, type, ir: parseFlowchart(source) };
2659
+ case "pie":
2660
+ return { ok: true, type, ir: parsePieChart(source) };
2661
+ case "quadrant":
2662
+ return { ok: true, type, ir: parseQuadrantChart(source) };
2663
+ case "journey":
2664
+ return { ok: true, type, ir: parseJourney(source) };
2665
+ case "sequence":
2666
+ return { ok: true, type, ir: parseSequence(source) };
2667
+ case "class":
2668
+ return { ok: true, type, ir: parseClassDiagram(source) };
2669
+ case "state":
2670
+ return { ok: true, type, ir: parseStateDiagram(source) };
2671
+ case "er":
2672
+ return { ok: true, type, ir: parseMermaidERDiagram(source) };
2673
+ case "gantt":
2674
+ return { ok: true, type, ir: parseGantt(source) };
2675
+ case "timeline":
2676
+ return { ok: true, type, ir: parseTimeline(source) };
2677
+ case "mindmap":
2678
+ return { ok: true, type, ir: parseMindmap(source) };
2679
+ case "architecture":
2680
+ return { ok: true, type, ir: parseArchitecture(source) };
2681
+ case "c4":
2682
+ return { ok: true, type, ir: parseC4(source) };
2683
+ case "gitgraph":
2684
+ return { ok: true, type, ir: parseGitGraph(source) };
2685
+ }
2686
+ return { ok: false, source, error: `Unhandled diagram type: ${String(type)}` };
2687
+ } catch (err) {
2688
+ return {
2689
+ ok: false,
2690
+ source,
2691
+ error: err instanceof Error ? err.message : String(err)
2692
+ };
2693
+ }
2694
+ }
2695
+ var HEADER_RE = /^(?:flowchart|graph)\s+(TB|TD|BT|LR|RL)\b/i;
2696
+ var SUBGRAPH_OPEN_RE = /^subgraph\s+([\w-]+)(?:\s*\[(.+?)\])?/i;
2697
+ var EDGE_LINE_REGEX = /-{2,}>|-{2,}|-\.-+>|\.-+>|={2,}>|-{2,}-|=={2,}|~~~/;
2698
+ function parseFlowchart(source) {
2699
+ const rawLines = source.split("\n");
2700
+ if (rawLines.length === 0) throw new Error("Empty flowchart source");
2701
+ let direction = "TB";
2702
+ const nodes = /* @__PURE__ */ new Map();
2703
+ const edges = [];
2704
+ const subgraphs = [];
2705
+ const subgraphStack = [];
2706
+ const ensureNode = (decl) => {
2707
+ const existing = nodes.get(decl.id);
2708
+ if (existing) {
2709
+ if (decl.label && (decl.label !== decl.id || !existing.label)) existing.label = decl.label;
2710
+ if (decl.kind !== "plain" && existing.kind === "plain") existing.kind = decl.kind;
2711
+ if (decl.icon) {
2712
+ existing.icon = decl.icon;
2713
+ existing.kind = "icon";
2714
+ }
2715
+ return existing;
2716
+ }
2717
+ const node = {
2718
+ id: decl.id,
2719
+ label: decl.label,
2720
+ kind: decl.icon ? "icon" : decl.kind
2721
+ };
2722
+ if (decl.icon) node.icon = decl.icon;
2723
+ if (subgraphStack.length > 0) node.subgraph = subgraphStack[subgraphStack.length - 1];
2724
+ nodes.set(decl.id, node);
2725
+ return node;
2726
+ };
2727
+ for (let i = 0; i < rawLines.length; i++) {
2728
+ const line = rawLines[i].trim();
2729
+ if (!line || line.startsWith("%%")) continue;
2730
+ if (i === 0 || nodes.size === 0 && edges.length === 0 && subgraphs.length === 0) {
2731
+ const m = line.match(HEADER_RE);
2732
+ if (m) {
2733
+ const dir = m[1].toUpperCase();
2734
+ direction = dir === "TD" ? "TB" : dir;
2735
+ continue;
2736
+ }
2737
+ }
2738
+ const subOpen = line.match(SUBGRAPH_OPEN_RE);
2739
+ if (subOpen) {
2740
+ const id = subOpen[1];
2741
+ const label = subOpen[2] ?? id;
2742
+ subgraphs.push({ id, label });
2743
+ subgraphStack.push(id);
2744
+ continue;
2745
+ }
2746
+ if (/^end\b/i.test(line)) {
2747
+ subgraphStack.pop();
2748
+ continue;
2749
+ }
2750
+ if (/^(direction|class|classDef|style|linkStyle|click)\b/i.test(line)) continue;
2751
+ if (EDGE_LINE_REGEX.test(line)) {
2752
+ const edge = parseEdge(line);
2753
+ if (edge) {
2754
+ const fromTokens = edge.from.split(/\s*&\s*/).filter(Boolean);
2755
+ const toTokens = edge.to.split(/\s*&\s*/).filter(Boolean);
2756
+ for (const f of fromTokens) {
2757
+ const fromDecl = parseNodeDecl(f);
2758
+ if (!fromDecl.id) continue;
2759
+ ensureNode(fromDecl);
2760
+ for (const t of toTokens) {
2761
+ const toDecl = parseNodeDecl(t);
2762
+ if (!toDecl.id) continue;
2763
+ ensureNode(toDecl);
2764
+ const e = { source: fromDecl.id, target: toDecl.id, kind: edge.kind };
2765
+ if (edge.label) e.label = edge.label;
2766
+ edges.push(e);
2767
+ }
2768
+ }
2769
+ continue;
2770
+ }
2771
+ }
2772
+ const decl = parseNodeDecl(line);
2773
+ if (decl.id) ensureNode(decl);
2774
+ }
2775
+ for (const sg of subgraphs) nodes.delete(sg.id);
2776
+ return {
2777
+ type: "flowchart",
2778
+ direction,
2779
+ nodes: [...nodes.values()],
2780
+ edges,
2781
+ subgraphs
2782
+ };
2783
+ }
2784
+ function splitClassDirective(text) {
2785
+ const idx = text.indexOf(":::");
2786
+ if (idx === -1) return { core: text };
2787
+ const core = text.slice(0, idx).trim();
2788
+ const rest = text.slice(idx + 3).trim();
2789
+ const iconMatch = rest.match(/(?:^|[\s,])icon\s*=\s*([\w:_-]+)/);
2790
+ return { core, icon: iconMatch?.[1] };
2791
+ }
2792
+ function parseNodeDecl(text) {
2793
+ const trimmed = text.trim();
2794
+ const { core, icon } = splitClassDirective(trimmed);
2795
+ const shapes = [
2796
+ { re: /^([\w-]+)\(\(([^)]*)\)\)$/, kind: "user" },
2797
+ // ((circle))
2798
+ { re: /^([\w-]+)\[\(([^)]*)\)\]$/, kind: "database" },
2799
+ // [(cylinder)]
2800
+ { re: /^([\w-]+)\[\[([^\]]*)\]\]$/, kind: "queue" },
2801
+ // [[subroutine]]
2802
+ { re: /^([\w-]+)\[\/([^/]*)\/\]$/, kind: "process" },
2803
+ // [/parallelogram/]
2804
+ { re: /^([\w-]+)\[\\([^\\]*)\\\]$/, kind: "process" },
2805
+ // [\..\]
2806
+ { re: /^([\w-]+)\{([^}]*)\}$/, kind: "decision" },
2807
+ // {decision}
2808
+ { re: /^([\w-]+)>([^\]]*)\]$/, kind: "plain" },
2809
+ // >tag]
2810
+ { re: /^([\w-]+)\(([^)]*)\)$/, kind: "service" },
2811
+ // (rounded)
2812
+ { re: /^([\w-]+)\[([^\]]*)\]$/, kind: "process" }
2813
+ // [rect]
2814
+ ];
2815
+ for (const { re, kind } of shapes) {
2816
+ const m = core.match(re);
2817
+ if (m) {
2818
+ return {
2819
+ id: m[1],
2820
+ label: stripQuotes(m[2]) || m[1],
2821
+ kind,
2822
+ icon
2823
+ };
2824
+ }
2825
+ }
2826
+ const bare = core.match(/^([\w-]+)$/);
2827
+ if (bare) {
2828
+ return { id: bare[1], label: bare[1], kind: "plain", icon };
2829
+ }
2830
+ return { id: "", label: "", kind: "plain" };
2831
+ }
2832
+ function stripQuotes(s) {
2833
+ if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) {
2834
+ return s.slice(1, -1);
2835
+ }
2836
+ return s;
2837
+ }
2838
+ function parseEdge(line) {
2839
+ const labeled = [
2840
+ { re: /^(.+?)\s*--\s*([^-][^|]*?)\s*-{1,2}>\s*(.+)$/, kind: "solid" },
2841
+ { re: /^(.+?)\s*-\.\s*([^.][^|]*?)\s*\.-+>\s*(.+)$/, kind: "dashed" },
2842
+ { re: /^(.+?)\s*==\s*([^=][^|]*?)\s*={1,2}>\s*(.+)$/, kind: "thick" }
2843
+ ];
2844
+ for (const { re, kind } of labeled) {
2845
+ const m = line.match(re);
2846
+ if (m) return { from: m[1].trim(), to: m[3].trim(), label: m[2].trim(), kind };
2847
+ }
2848
+ const unlabeled = [
2849
+ { re: /^(.+?)\s*~~~\s*(?:\|([^|]+)\|\s*)?(.+)$/, kind: "invisible" },
2850
+ { re: /^(.+?)\s*-\.-+>\s*(?:\|([^|]+)\|\s*)?(.+)$/, kind: "dashed" },
2851
+ { re: /^(.+?)\s*={2,}>\s*(?:\|([^|]+)\|\s*)?(.+)$/, kind: "thick" },
2852
+ { re: /^(.+?)\s*-{2,}>\s*(?:\|([^|]+)\|\s*)?(.+)$/, kind: "solid" },
2853
+ { re: /^(.+?)\s*-{2,}\s*(?:\|([^|]+)\|\s*)?(.+)$/, kind: "solid" }
2854
+ ];
2855
+ for (const { re, kind } of unlabeled) {
2856
+ const m = line.match(re);
2857
+ if (m) {
2858
+ const label = m[2]?.trim();
2859
+ return { from: m[1].trim(), to: m[3].trim(), label: label || void 0, kind };
2860
+ }
2861
+ }
2862
+ return null;
2863
+ }
2864
+ var PIE_SLICE_RE = /^"([^"]+)"\s*:\s*([\d.]+)/;
2865
+ function parsePieChart(source) {
2866
+ const lines = source.split("\n").map((l) => l.trim());
2867
+ const ir = { type: "pie", slices: [] };
2868
+ for (let i = 0; i < lines.length; i++) {
2869
+ const line = lines[i];
2870
+ if (!line || line.startsWith("%%")) continue;
2871
+ if (/^pie\b/i.test(line)) {
2872
+ if (/\bshowData\b/i.test(line)) ir.showData = true;
2873
+ continue;
2874
+ }
2875
+ const titleMatch = line.match(/^title\s+(.+)$/i);
2876
+ if (titleMatch) {
2877
+ ir.title = titleMatch[1].trim();
2878
+ continue;
2879
+ }
2880
+ const sliceMatch = line.match(PIE_SLICE_RE);
2881
+ if (sliceMatch) {
2882
+ const value = parseFloat(sliceMatch[2]);
2883
+ if (!isNaN(value)) ir.slices.push({ label: sliceMatch[1], value });
2884
+ }
2885
+ }
2886
+ return ir;
2887
+ }
2888
+ var QUADRANT_POINT_RE = /^([^:]+):\s*\[\s*([\d.]+)\s*,\s*([\d.]+)\s*\]/;
2889
+ function parseQuadrantChart(source) {
2890
+ const lines = source.split("\n").map((l) => l.trim());
2891
+ const ir = { type: "quadrant", points: [] };
2892
+ for (let i = 0; i < lines.length; i++) {
2893
+ const line = lines[i];
2894
+ if (!line || line.startsWith("%%")) continue;
2895
+ if (/^quadrantchart\b/i.test(line)) continue;
2896
+ const titleMatch = line.match(/^title\s+(.+)$/i);
2897
+ if (titleMatch) {
2898
+ ir.title = titleMatch[1].trim();
2899
+ continue;
2900
+ }
2901
+ const xAxisMatch = line.match(/^x-axis\s+(.+?)\s*-->\s*(.+)$/i);
2902
+ if (xAxisMatch) {
2903
+ ir.xAxisLabel = { low: xAxisMatch[1].trim(), high: xAxisMatch[2].trim() };
2904
+ continue;
2905
+ }
2906
+ const yAxisMatch = line.match(/^y-axis\s+(.+?)\s*-->\s*(.+)$/i);
2907
+ if (yAxisMatch) {
2908
+ ir.yAxisLabel = { low: yAxisMatch[1].trim(), high: yAxisMatch[2].trim() };
2909
+ continue;
2910
+ }
2911
+ const qMatch = line.match(/^quadrant-([1-4])\s+(.+)$/i);
2912
+ if (qMatch) {
2913
+ ir.quadrantLabels = ir.quadrantLabels ?? {};
2914
+ ir.quadrantLabels[`q${qMatch[1]}`] = qMatch[2].trim();
2915
+ continue;
2916
+ }
2917
+ const pt = line.match(QUADRANT_POINT_RE);
2918
+ if (pt) {
2919
+ const x = parseFloat(pt[2]);
2920
+ const y = parseFloat(pt[3]);
2921
+ if (!isNaN(x) && !isNaN(y)) {
2922
+ ir.points.push({ label: pt[1].trim(), x, y });
2923
+ }
2924
+ }
2925
+ }
2926
+ return ir;
2927
+ }
2928
+ var JOURNEY_TASK_RE = /^([^:]+?)\s*:\s*([\d.]+)\s*:\s*(.+)$/;
2929
+ function parseJourney(source) {
2930
+ const lines = source.split("\n").map((l) => l.replace(/^\s+/, ""));
2931
+ const ir = { type: "journey", sections: [] };
2932
+ let currentSection = null;
2933
+ for (const rawLine of lines) {
2934
+ const line = rawLine.trimEnd();
2935
+ if (!line || line.startsWith("%%")) continue;
2936
+ if (/^journey\b/i.test(line)) continue;
2937
+ const titleMatch = line.match(/^title\s+(.+)$/i);
2938
+ if (titleMatch) {
2939
+ ir.title = titleMatch[1].trim();
2940
+ continue;
2941
+ }
2942
+ const sectionMatch = line.match(/^section\s+(.+)$/i);
2943
+ if (sectionMatch) {
2944
+ currentSection = { title: sectionMatch[1].trim(), tasks: [] };
2945
+ ir.sections.push(currentSection);
2946
+ continue;
2947
+ }
2948
+ const taskMatch = line.match(JOURNEY_TASK_RE);
2949
+ if (taskMatch && currentSection) {
2950
+ const score = parseFloat(taskMatch[2]);
2951
+ const actors = taskMatch[3].split(",").map((a) => a.trim()).filter(Boolean);
2952
+ currentSection.tasks.push({ label: taskMatch[1].trim(), score, actors });
2953
+ }
2954
+ }
2955
+ return ir;
2956
+ }
2957
+ var SEQ_ARROW_TOKENS = [
2958
+ { token: "-->>", arrow: "reply" },
2959
+ { token: "->>", arrow: "sync" },
2960
+ { token: "-->", arrow: "reply" },
2961
+ { token: "->", arrow: "sync" },
2962
+ { token: "-x", arrow: "cross" },
2963
+ { token: "-)", arrow: "async" }
2964
+ ];
2965
+ function matchSequenceArrow(line) {
2966
+ let bestIdx = -1;
2967
+ let bestToken = null;
2968
+ for (const t of SEQ_ARROW_TOKENS) {
2969
+ const idx = line.indexOf(t.token);
2970
+ if (idx === -1) continue;
2971
+ if (bestIdx === -1 || idx < bestIdx || idx === bestIdx && t.token.length > (bestToken?.token.length ?? 0)) {
2972
+ bestIdx = idx;
2973
+ bestToken = t;
2974
+ }
2975
+ }
2976
+ if (bestIdx === -1 || !bestToken) return null;
2977
+ const from = line.slice(0, bestIdx).trim();
2978
+ const rest = line.slice(bestIdx + bestToken.token.length);
2979
+ const colonIdx = rest.indexOf(":");
2980
+ if (colonIdx === -1) return null;
2981
+ let to = rest.slice(0, colonIdx).trim();
2982
+ const label = rest.slice(colonIdx + 1).trim();
2983
+ if (!from || !to || !label) return null;
2984
+ if (to.startsWith("+") || to.startsWith("-")) to = to.slice(1).trim();
2985
+ return { from, to, arrow: bestToken.arrow, label };
2986
+ }
2987
+ function parseSequence(source) {
2988
+ const lines = source.split("\n").map((l) => l.trim());
2989
+ const ir = { type: "sequence", participants: [], steps: [] };
2990
+ const ensureParticipant = (id, label) => {
2991
+ const existing = ir.participants.find((p) => p.id === id);
2992
+ if (existing) {
2993
+ if (label && existing.label === existing.id) existing.label = label;
2994
+ return;
2995
+ }
2996
+ ir.participants.push({ id, label: label ?? id });
2997
+ };
2998
+ for (const line of lines) {
2999
+ if (!line || line.startsWith("%%")) continue;
3000
+ if (/^sequencediagram\b/i.test(line)) continue;
3001
+ if (/^(loop|alt|opt|par|else|critical|break|rect|end|activate|deactivate|autonumber|box)\b/i.test(line)) continue;
3002
+ const titleMatch = line.match(/^title\s+(.+)$/i);
3003
+ if (titleMatch) {
3004
+ ir.title = titleMatch[1].trim();
3005
+ continue;
3006
+ }
3007
+ const partMatch = line.match(/^(?:participant|actor)\s+(.+)$/i);
3008
+ if (partMatch) {
3009
+ const rest = partMatch[1].trim();
3010
+ const asMatch = rest.match(/^(.+?)\s+as\s+(.+)$/i);
3011
+ if (asMatch) {
3012
+ ensureParticipant(stripQuotes(asMatch[1].trim()), stripQuotes(asMatch[2].trim()));
3013
+ } else {
3014
+ ensureParticipant(stripQuotes(rest));
3015
+ }
3016
+ continue;
3017
+ }
3018
+ const noteMatch = line.match(/^note\s+(left of|right of|over)\s+([^:]+)\s*:\s*(.+)$/i);
3019
+ if (noteMatch) {
3020
+ const sideRaw = noteMatch[1].toLowerCase();
3021
+ const side = sideRaw.startsWith("left") ? "left" : sideRaw.startsWith("right") ? "right" : "over";
3022
+ const participants = noteMatch[2].split(",").map((p) => p.trim()).filter(Boolean);
3023
+ participants.forEach((p) => ensureParticipant(p));
3024
+ const note = { kind: "note", side, participants, text: noteMatch[3].trim() };
3025
+ ir.steps.push(note);
3026
+ continue;
3027
+ }
3028
+ const arrowMatch = matchSequenceArrow(line);
3029
+ if (arrowMatch) {
3030
+ ensureParticipant(arrowMatch.from);
3031
+ ensureParticipant(arrowMatch.to);
3032
+ const msg = {
3033
+ kind: "message",
3034
+ from: arrowMatch.from,
3035
+ to: arrowMatch.to,
3036
+ arrow: arrowMatch.arrow,
3037
+ label: arrowMatch.label
3038
+ };
3039
+ ir.steps.push(msg);
3040
+ }
3041
+ }
3042
+ return ir;
3043
+ }
3044
+ var CLASS_VISIBILITY = {
3045
+ "+": "public",
3046
+ "-": "private",
3047
+ "#": "protected",
3048
+ "~": "package"
3049
+ };
3050
+ var CLASS_REL_PATTERNS = [
3051
+ { re: /^([\w-]+)\s*<\|--\s*([\w-]+)$/, kind: "inheritance", reversed: true },
3052
+ { re: /^([\w-]+)\s*--\|>\s*([\w-]+)$/, kind: "inheritance" },
3053
+ { re: /^([\w-]+)\s*<\|\.\.\s*([\w-]+)$/, kind: "realization", reversed: true },
3054
+ { re: /^([\w-]+)\s*\.\.\|>\s*([\w-]+)$/, kind: "realization" },
3055
+ { re: /^([\w-]+)\s*\*--\s*([\w-]+)$/, kind: "composition" },
3056
+ { re: /^([\w-]+)\s*o--\s*([\w-]+)$/, kind: "aggregation" },
3057
+ { re: /^([\w-]+)\s*<\.\.\s*([\w-]+)$/, kind: "dependency", reversed: true },
3058
+ { re: /^([\w-]+)\s*\.\.>\s*([\w-]+)$/, kind: "dependency" },
3059
+ { re: /^([\w-]+)\s*--\s*([\w-]+)$/, kind: "association" }
3060
+ ];
3061
+ function parseClassDiagram(source) {
3062
+ const lines = source.split("\n").map((l) => l.trim());
3063
+ const classes = /* @__PURE__ */ new Map();
3064
+ const relations = [];
3065
+ const ensureClass = (id) => {
3066
+ let cls = classes.get(id);
3067
+ if (!cls) {
3068
+ cls = { id, label: id, members: [] };
3069
+ classes.set(id, cls);
3070
+ }
3071
+ return cls;
3072
+ };
3073
+ let currentClassBody = null;
3074
+ for (let i = 0; i < lines.length; i++) {
3075
+ const line = lines[i];
3076
+ if (!line || line.startsWith("%%")) continue;
3077
+ if (/^classdiagram\b/i.test(line)) continue;
3078
+ if (currentClassBody) {
3079
+ if (/^\}\s*$/.test(line)) {
3080
+ currentClassBody = null;
3081
+ continue;
3082
+ }
3083
+ const stereotype = matchStereotype(line);
3084
+ if (stereotype) {
3085
+ ensureClass(currentClassBody).stereotype = stereotype;
3086
+ continue;
3087
+ }
3088
+ const member = parseClassMember(line);
3089
+ if (member) ensureClass(currentClassBody).members.push(member);
3090
+ continue;
3091
+ }
3092
+ const sameLine = line.match(/^class\s+([\w-]+)\s*\{(.*)\}\s*$/);
3093
+ if (sameLine) {
3094
+ const cls = ensureClass(sameLine[1]);
3095
+ const inner = sameLine[2].split(/[;\n]/).map((s) => s.trim()).filter(Boolean);
3096
+ for (const part of inner) {
3097
+ const stereotype = matchStereotype(part);
3098
+ if (stereotype) {
3099
+ cls.stereotype = stereotype;
3100
+ continue;
3101
+ }
3102
+ const m = parseClassMember(part);
3103
+ if (m) cls.members.push(m);
3104
+ }
3105
+ continue;
3106
+ }
3107
+ const open = line.match(/^class\s+([\w-]+)\s*\{$/);
3108
+ if (open) {
3109
+ ensureClass(open[1]);
3110
+ currentClassBody = open[1];
3111
+ continue;
3112
+ }
3113
+ const decl = line.match(/^class\s+([\w-]+)$/);
3114
+ if (decl) {
3115
+ ensureClass(decl[1]);
3116
+ continue;
3117
+ }
3118
+ const memberDecl = line.match(/^([\w-]+)\s*:\s*(.+)$/);
3119
+ if (memberDecl && !line.includes("-->") && !line.includes("--")) {
3120
+ const stereotype = matchStereotype(memberDecl[2]);
3121
+ if (stereotype) {
3122
+ ensureClass(memberDecl[1]).stereotype = stereotype;
3123
+ continue;
3124
+ }
3125
+ const m = parseClassMember(memberDecl[2]);
3126
+ if (m) ensureClass(memberDecl[1]).members.push(m);
3127
+ continue;
3128
+ }
3129
+ let labelPart;
3130
+ let relLine = line;
3131
+ const labelMatch = line.match(/^(.+?)\s*:\s*(.+)$/);
3132
+ if (labelMatch && /(<\|--|--\|>|<\|\.\.|\.\.\|>|\*--|o--|<\.\.|\.\.>|--)/.test(labelMatch[1])) {
3133
+ relLine = labelMatch[1];
3134
+ labelPart = labelMatch[2].trim();
3135
+ }
3136
+ for (const { re, kind, reversed } of CLASS_REL_PATTERNS) {
3137
+ const m = relLine.match(re);
3138
+ if (m) {
3139
+ const a = m[1];
3140
+ const b = m[2];
3141
+ ensureClass(a);
3142
+ ensureClass(b);
3143
+ relations.push({
3144
+ source: reversed ? b : a,
3145
+ target: reversed ? a : b,
3146
+ kind,
3147
+ label: labelPart
3148
+ });
3149
+ break;
3150
+ }
3151
+ }
3152
+ }
3153
+ return { type: "class", classes: [...classes.values()], relations };
3154
+ }
3155
+ function matchStereotype(text) {
3156
+ const m = text.trim().match(/^<<\s*([^>]+?)\s*>>$/);
3157
+ return m ? m[1] : void 0;
3158
+ }
3159
+ function parseClassMember(text) {
3160
+ const t = text.trim();
3161
+ if (!t) return null;
3162
+ let visibility;
3163
+ let body = t;
3164
+ const first = body[0];
3165
+ if (first in CLASS_VISIBILITY) {
3166
+ visibility = CLASS_VISIBILITY[first];
3167
+ body = body.slice(1).trim();
3168
+ }
3169
+ const methodMatch = body.match(/^([\w-]+)\(([^)]*)\)(?:\s*([\w<>[\]]+))?$/);
3170
+ if (methodMatch) {
3171
+ return {
3172
+ kind: "method",
3173
+ visibility,
3174
+ name: methodMatch[1],
3175
+ parameters: methodMatch[2] || void 0,
3176
+ returnType: methodMatch[3] || void 0
3177
+ };
3178
+ }
3179
+ const colonMatch = body.match(/^([\w-]+)\s*:\s*([\w<>[\]]+)$/);
3180
+ if (colonMatch) {
3181
+ return { kind: "attribute", visibility, name: colonMatch[1], returnType: colonMatch[2] };
3182
+ }
3183
+ const typeNameMatch = body.match(/^([\w<>[\]]+)\s+([\w-]+)$/);
3184
+ if (typeNameMatch) {
3185
+ return { kind: "attribute", visibility, name: typeNameMatch[2], returnType: typeNameMatch[1] };
3186
+ }
3187
+ return { kind: "attribute", visibility, name: body };
3188
+ }
3189
+ function parseStateDiagram(source) {
3190
+ const lines = source.split("\n").map((l) => l.trim());
3191
+ const states = /* @__PURE__ */ new Map();
3192
+ const transitions = [];
3193
+ const parentStack = [];
3194
+ const currentParent = () => parentStack.length > 0 ? parentStack[parentStack.length - 1] : void 0;
3195
+ const markerId = (kind, parent) => parent ? `__${kind}_${parent}` : `__${kind}`;
3196
+ const ensureMarker = (kind) => {
3197
+ const parent = currentParent();
3198
+ const id = markerId(kind, parent);
3199
+ let s = states.get(id);
3200
+ if (!s) {
3201
+ s = { id, label: "", kind, parent };
3202
+ states.set(id, s);
3203
+ }
3204
+ return s;
3205
+ };
3206
+ const ensureState = (id) => {
3207
+ let s = states.get(id);
3208
+ if (s) return s;
3209
+ s = { id, label: id, kind: "state", parent: currentParent() };
3210
+ states.set(id, s);
3211
+ return s;
3212
+ };
3213
+ for (const line of lines) {
3214
+ if (!line || line.startsWith("%%")) continue;
3215
+ if (/^statediagram(-v2)?\b/i.test(line)) continue;
3216
+ const compositeOpen = line.match(/^state\s+([\w-]+)\s*\{$/);
3217
+ if (compositeOpen) {
3218
+ const id = compositeOpen[1];
3219
+ const existing = states.get(id);
3220
+ if (existing) {
3221
+ existing.kind = "composite";
3222
+ } else {
3223
+ states.set(id, { id, label: id, kind: "composite", parent: currentParent() });
3224
+ }
3225
+ parentStack.push(id);
3226
+ continue;
3227
+ }
3228
+ if (line === "}") {
3229
+ parentStack.pop();
3230
+ continue;
3231
+ }
3232
+ const arrow = line.match(/^(\[\*\]|[\w-]+)\s*-->\s*(\[\*\]|[\w-]+)(?:\s*:\s*(.+))?$/);
3233
+ if (arrow) {
3234
+ const src = arrow[1] === "[*]" ? ensureMarker("start") : ensureState(arrow[1]);
3235
+ const tgt = arrow[2] === "[*]" ? ensureMarker("end") : ensureState(arrow[2]);
3236
+ transitions.push({
3237
+ source: src.id,
3238
+ target: tgt.id,
3239
+ label: arrow[3]?.trim(),
3240
+ parent: currentParent()
3241
+ });
3242
+ continue;
3243
+ }
3244
+ const stateLabel = line.match(/^([\w-]+)\s*:\s*(.+)$/);
3245
+ if (stateLabel) {
3246
+ const s = ensureState(stateLabel[1]);
3247
+ s.label = stateLabel[2].trim();
3248
+ continue;
3249
+ }
3250
+ if (/^[\w-]+$/.test(line)) ensureState(line);
3251
+ }
3252
+ return { type: "state", states: [...states.values()], transitions };
3253
+ }
3254
+ function parseMermaidERDiagram(source) {
3255
+ const lines = source.split("\n").map((l) => l.trim());
3256
+ const tables = /* @__PURE__ */ new Map();
3257
+ const relations = [];
3258
+ const ensureTable = (name) => {
3259
+ let t = tables.get(name);
3260
+ if (!t) {
3261
+ t = { name, columns: [] };
3262
+ tables.set(name, t);
3263
+ }
3264
+ return t;
3265
+ };
3266
+ let currentTableBody = null;
3267
+ for (const line of lines) {
3268
+ if (!line || line.startsWith("%%")) continue;
3269
+ if (/^erdiagram\b/i.test(line)) continue;
3270
+ if (currentTableBody) {
3271
+ if (/^\}\s*$/.test(line)) {
3272
+ currentTableBody = null;
3273
+ continue;
3274
+ }
3275
+ const col = line.match(/^([\w-]+)\s+([\w-]+)(?:\s+(.+))?$/);
3276
+ if (col) {
3277
+ const flags = (col[3] ?? "").toUpperCase();
3278
+ const column = {
3279
+ name: col[2],
3280
+ type: col[1],
3281
+ isPK: /\bPK\b/.test(flags),
3282
+ isFK: /\bFK\b/.test(flags),
3283
+ isNullable: !/\bNOT NULL\b/.test(flags),
3284
+ isUnique: /\bUK\b/.test(flags) || /\bUNIQUE\b/.test(flags)
3285
+ };
3286
+ ensureTable(currentTableBody).columns.push(column);
3287
+ }
3288
+ continue;
3289
+ }
3290
+ const blockOpen = line.match(/^([A-Z][\w-]*)\s*\{$/);
3291
+ if (blockOpen) {
3292
+ ensureTable(blockOpen[1]);
3293
+ currentTableBody = blockOpen[1];
3294
+ continue;
3295
+ }
3296
+ const rel = line.match(/^([A-Z][\w-]*)\s+([|}{o\-.]+)\s+([A-Z][\w-]*)\s*:\s*(.+)$/);
3297
+ if (rel) {
3298
+ const fromTable = rel[1];
3299
+ const toTable = rel[3];
3300
+ const cardinality = rel[2];
3301
+ ensureTable(fromTable);
3302
+ ensureTable(toTable);
3303
+ relations.push({
3304
+ fromTable,
3305
+ fromCol: rel[4].trim(),
3306
+ toTable,
3307
+ toCol: rel[4].trim(),
3308
+ nullable: cardinality.includes("o")
3309
+ });
3310
+ }
3311
+ }
3312
+ const schema = {
3313
+ tables: [...tables.values()],
3314
+ relations,
3315
+ inputFormat: "unknown"
3316
+ };
3317
+ return { type: "er", schema };
3318
+ }
3319
+ var GANTT_TASK_LINE_RE = /^([^:]+?)\s*:\s*(.+)$/;
3320
+ var STATUS_TOKENS = {
3321
+ done: "done",
3322
+ active: "active",
3323
+ crit: "crit",
3324
+ milestone: "milestone"
3325
+ };
3326
+ function parseGantt(source) {
3327
+ const lines = source.split("\n").map((l) => l.trim());
3328
+ const ir = { type: "gantt", tasks: [] };
3329
+ const intermediates = [];
3330
+ let currentSection;
3331
+ let anonCounter = 0;
3332
+ for (const line of lines) {
3333
+ if (!line || line.startsWith("%%")) continue;
3334
+ if (/^gantt\b/i.test(line)) continue;
3335
+ const titleMatch = line.match(/^title\s+(.+)$/i);
3336
+ if (titleMatch) {
3337
+ ir.title = titleMatch[1].trim();
3338
+ continue;
3339
+ }
3340
+ const dateMatch = line.match(/^dateFormat\s+(.+)$/i);
3341
+ if (dateMatch) {
3342
+ ir.dateFormat = dateMatch[1].trim();
3343
+ continue;
3344
+ }
3345
+ const axisMatch = line.match(/^axisFormat\s+(.+)$/i);
3346
+ if (axisMatch) {
3347
+ ir.axisFormat = axisMatch[1].trim();
3348
+ continue;
3349
+ }
3350
+ const sectionMatch = line.match(/^section\s+(.+)$/i);
3351
+ if (sectionMatch) {
3352
+ currentSection = sectionMatch[1].trim();
3353
+ continue;
3354
+ }
3355
+ if (/^(excludes|includes|todayMarker|click|tickInterval|weekday)\b/i.test(line)) continue;
3356
+ const taskMatch = line.match(GANTT_TASK_LINE_RE);
3357
+ if (!taskMatch) continue;
3358
+ const label = taskMatch[1].trim();
3359
+ const parts = taskMatch[2].split(",").map((p) => p.trim()).filter(Boolean);
3360
+ if (parts.length === 0) continue;
3361
+ let status = "default";
3362
+ let id;
3363
+ let startSpec;
3364
+ let durationOrEnd;
3365
+ for (const part of parts) {
3366
+ const lower = part.toLowerCase();
3367
+ if (lower in STATUS_TOKENS) {
3368
+ status = STATUS_TOKENS[lower];
3369
+ continue;
3370
+ }
3371
+ if (id === void 0 && /^[\w-]+$/.test(part) && !isDateLike(part) && !/^\d/.test(part)) {
3372
+ id = part;
3373
+ continue;
3374
+ }
3375
+ if (startSpec === void 0) {
3376
+ startSpec = part;
3377
+ continue;
3378
+ }
3379
+ if (durationOrEnd === void 0) {
3380
+ durationOrEnd = part;
3381
+ continue;
3382
+ }
3383
+ }
3384
+ if (!startSpec || !durationOrEnd) continue;
3385
+ intermediates.push({
3386
+ id: id ?? `__gantt_${anonCounter++}`,
3387
+ label,
3388
+ status,
3389
+ startSpec,
3390
+ durationOrEnd,
3391
+ section: currentSection
3392
+ });
3393
+ }
3394
+ const byId = /* @__PURE__ */ new Map();
3395
+ for (const it of intermediates) {
3396
+ const start = resolveGanttDate(it.startSpec, byId);
3397
+ if (!start) continue;
3398
+ let end = resolveGanttDate(it.durationOrEnd, byId);
3399
+ if (!end) {
3400
+ end = applyGanttDuration(start, it.durationOrEnd);
3401
+ }
3402
+ if (!end) continue;
3403
+ if (it.status === "milestone") end = start;
3404
+ const task = {
3405
+ id: it.id,
3406
+ label: it.label,
3407
+ start: toIsoDay(start),
3408
+ end: toIsoDay(end),
3409
+ status: it.status,
3410
+ section: it.section
3411
+ };
3412
+ ir.tasks.push(task);
3413
+ byId.set(it.id, task);
3414
+ }
3415
+ return ir;
3416
+ }
3417
+ function isDateLike(s) {
3418
+ return /^\d{4}-\d{2}-\d{2}/.test(s);
3419
+ }
3420
+ function resolveGanttDate(spec, byId) {
3421
+ if (isDateLike(spec)) {
3422
+ const d = new Date(spec);
3423
+ return isNaN(d.getTime()) ? null : d;
3424
+ }
3425
+ const after = spec.match(/^after\s+(.+)$/i);
3426
+ if (after) {
3427
+ const refIds = after[1].split(/\s+/);
3428
+ let max = null;
3429
+ for (const refId of refIds) {
3430
+ const ref = byId.get(refId);
3431
+ if (!ref) continue;
3432
+ const d = new Date(ref.end);
3433
+ if (!max || d.getTime() > max.getTime()) max = d;
3434
+ }
3435
+ return max;
3436
+ }
3437
+ return null;
3438
+ }
3439
+ function applyGanttDuration(start, dur) {
3440
+ const m = dur.match(/^(\d+(?:\.\d+)?)\s*(d|h|w|m|y)$/i);
3441
+ if (!m) return null;
3442
+ const n = parseFloat(m[1]);
3443
+ const unit = m[2].toLowerCase();
3444
+ const result = new Date(start);
3445
+ switch (unit) {
3446
+ case "h":
3447
+ result.setHours(result.getHours() + n);
3448
+ break;
3449
+ case "d":
3450
+ result.setDate(result.getDate() + n);
3451
+ break;
3452
+ case "w":
3453
+ result.setDate(result.getDate() + n * 7);
3454
+ break;
3455
+ case "m":
3456
+ result.setMonth(result.getMonth() + n);
3457
+ break;
3458
+ case "y":
3459
+ result.setFullYear(result.getFullYear() + n);
3460
+ break;
3461
+ default:
3462
+ return null;
3463
+ }
3464
+ return result;
3465
+ }
3466
+ function toIsoDay(d) {
3467
+ return d.toISOString();
3468
+ }
3469
+ function parseTimeline(source) {
3470
+ const lines = source.split("\n");
3471
+ const ir = { type: "timeline", events: [] };
3472
+ let currentSection;
3473
+ let counter = 0;
3474
+ for (const rawLine of lines) {
3475
+ const line = rawLine.trim();
3476
+ if (!line || line.startsWith("%%")) continue;
3477
+ if (/^timeline\b/i.test(line)) continue;
3478
+ const titleMatch = line.match(/^title\s+(.+)$/i);
3479
+ if (titleMatch) {
3480
+ ir.title = titleMatch[1].trim();
3481
+ continue;
3482
+ }
3483
+ const sectionMatch = line.match(/^section\s+(.+)$/i);
3484
+ if (sectionMatch) {
3485
+ currentSection = sectionMatch[1].trim();
3486
+ continue;
3487
+ }
3488
+ const parts = line.split(":").map((p) => p.trim()).filter(Boolean);
3489
+ if (parts.length < 2) continue;
3490
+ const period = parts[0];
3491
+ for (let i = 1; i < parts.length; i++) {
3492
+ const event = {
3493
+ id: `__tl_${counter++}`,
3494
+ period,
3495
+ text: parts[i],
3496
+ section: currentSection
3497
+ };
3498
+ ir.events.push(event);
3499
+ }
3500
+ }
3501
+ return ir;
3502
+ }
3503
+ var MINDMAP_SHAPE_PATTERNS = [
3504
+ { re: /^([\w-]*)\(\(([^)]*)\)\)$/, shape: "circle" },
3505
+ // ((text))
3506
+ { re: /^([\w-]*)\)\)([^(]*)\(\($/, shape: "bang" },
3507
+ // ))text(( bang
3508
+ { re: /^([\w-]*)\)([^(]*)\($/, shape: "cloud" },
3509
+ // )text( cloud
3510
+ { re: /^([\w-]*)\{\{([^}]*)\}\}$/, shape: "hexagon" },
3511
+ // {{text}}
3512
+ { re: /^([\w-]*)\(([^)]*)\)$/, shape: "rounded" },
3513
+ // (text)
3514
+ { re: /^([\w-]*)\[([^\]]*)\]$/, shape: "square" }
3515
+ // [text]
3516
+ ];
3517
+ function parseMindmap(source) {
3518
+ const rawLines = source.split("\n");
3519
+ const stripped = [];
3520
+ let inHeader = true;
3521
+ for (const line of rawLines) {
3522
+ if (line.trim().length === 0) continue;
3523
+ if (inHeader && /^\s*mindmap\b/i.test(line)) {
3524
+ inHeader = false;
3525
+ continue;
3526
+ }
3527
+ inHeader = false;
3528
+ if (line.trim().startsWith("%%")) continue;
3529
+ const indent = line.match(/^\s*/)?.[0]?.length ?? 0;
3530
+ stripped.push({ indent, raw: line.trim() });
3531
+ }
3532
+ if (stripped.length === 0) {
3533
+ return {
3534
+ type: "mindmap",
3535
+ root: { id: "__mm_0", label: "Mindmap", shape: "default", children: [] }
3536
+ };
3537
+ }
3538
+ let counter = 0;
3539
+ const makeNode = (raw) => {
3540
+ let label = raw;
3541
+ let shape = "default";
3542
+ let icon;
3543
+ const iconMatch = label.match(/^(.*?)\s*::icon\(([^)]+)\)\s*$/);
3544
+ if (iconMatch) {
3545
+ label = iconMatch[1].trim();
3546
+ icon = iconMatch[2].trim();
3547
+ }
3548
+ for (const { re, shape: s } of MINDMAP_SHAPE_PATTERNS) {
3549
+ const m = label.match(re);
3550
+ if (m) {
3551
+ label = m[2].trim() || m[1].trim();
3552
+ shape = s;
3553
+ break;
3554
+ }
3555
+ }
3556
+ const node = {
3557
+ id: `__mm_${counter++}`,
3558
+ label,
3559
+ shape,
3560
+ children: []
3561
+ };
3562
+ if (icon) node.icon = icon;
3563
+ return node;
3564
+ };
3565
+ const rootEntry = stripped[0];
3566
+ const root = makeNode(rootEntry.raw);
3567
+ const stack = [{ indent: rootEntry.indent, node: root }];
3568
+ for (let i = 1; i < stripped.length; i++) {
3569
+ const { indent, raw } = stripped[i];
3570
+ while (stack.length > 0 && stack[stack.length - 1].indent >= indent) stack.pop();
3571
+ const parent = stack.length > 0 ? stack[stack.length - 1].node : root;
3572
+ const node = makeNode(raw);
3573
+ parent.children.push(node);
3574
+ stack.push({ indent, node });
3575
+ }
3576
+ return { type: "mindmap", root };
3577
+ }
3578
+ var ARCH_DECL_RE = /^(group|service)\s+([\w-]+)\s*(?:\(([^)]+)\))?\s*(?:\[([^\]]+)\])?\s*(?:in\s+([\w-]+))?\s*$/i;
3579
+ var ARCH_EDGE_RE = /^([\w-]+)(?::([LRTB]))?\s*(?:--|<-->|<--|-->|<-|->)\s*(?:([LRTB]):)?([\w-]+)(?:\s*\[([^\]]+)\])?$/i;
3580
+ function parseArchitecture(source) {
3581
+ const lines = source.split("\n").map((l) => l.trim());
3582
+ const nodes = [];
3583
+ const edges = [];
3584
+ for (const line of lines) {
3585
+ if (!line || line.startsWith("%%")) continue;
3586
+ if (/^architecture(-beta)?\b/i.test(line)) continue;
3587
+ if (/^title\b/i.test(line)) continue;
3588
+ const decl = line.match(ARCH_DECL_RE);
3589
+ if (decl) {
3590
+ const [, kind, id, icon, label, parent] = decl;
3591
+ nodes.push({
3592
+ id,
3593
+ kind: kind.toLowerCase() === "group" ? "group" : "service",
3594
+ label: label?.trim() || id,
3595
+ icon: icon?.trim() || void 0,
3596
+ parent: parent?.trim() || void 0
3597
+ });
3598
+ continue;
3599
+ }
3600
+ const edge = line.match(ARCH_EDGE_RE);
3601
+ if (edge) {
3602
+ const [, source2, sourceSide, targetSide, target, label] = edge;
3603
+ edges.push({
3604
+ source: source2,
3605
+ target,
3606
+ sourceSide: sourceSide?.toUpperCase() ?? void 0,
3607
+ targetSide: targetSide?.toUpperCase() ?? void 0,
3608
+ label: label?.trim() || void 0
3609
+ });
3610
+ }
3611
+ }
3612
+ return { type: "architecture", nodes, edges };
3613
+ }
3614
+ var C4_VARIANT_RE = /^C4(Context|Container|Component|Deployment)\b/i;
3615
+ var C4_ELEMENT_RE = /^([A-Z][\w_]*)\s*\(\s*([^,)]+)(?:\s*,\s*"([^"]*)")?(?:\s*,\s*"([^"]*)")?(?:\s*,\s*"([^"]*)")?(?:\s*,\s*"([^"]*)")?\s*\)\s*\{?\s*$/;
3616
+ var C4_REL_RE = /^(Rel|BiRel|Rel_Back|Rel_Up|Rel_Down|Rel_Left|Rel_Right)\s*\(\s*([\w_]+)\s*,\s*([\w_]+)\s*(?:,\s*"([^"]*)")?(?:\s*,\s*"([^"]*)")?\s*\)\s*$/i;
3617
+ var C4_KIND_MAP = {
3618
+ Person: "person",
3619
+ Person_Ext: "person-external",
3620
+ System: "system",
3621
+ System_Ext: "system-external",
3622
+ SystemDb: "system-db",
3623
+ SystemDb_Ext: "system-db",
3624
+ SystemQueue: "system-queue",
3625
+ SystemQueue_Ext: "system-queue",
3626
+ Container: "container",
3627
+ Container_Ext: "container-external",
3628
+ ContainerDb: "container-db",
3629
+ ContainerDb_Ext: "container-db",
3630
+ ContainerQueue: "container-queue",
3631
+ Component: "component",
3632
+ Component_Ext: "component-external",
3633
+ ComponentDb: "component-db",
3634
+ ComponentQueue: "component-queue",
3635
+ Boundary: "boundary",
3636
+ System_Boundary: "system-boundary",
3637
+ Container_Boundary: "container-boundary",
3638
+ Enterprise_Boundary: "enterprise-boundary",
3639
+ Node: "node",
3640
+ Deployment_Node: "node"
3641
+ };
3642
+ function parseC4(source) {
3643
+ const lines = source.split("\n").map((l) => l.trim());
3644
+ const elements = [];
3645
+ const relations = [];
3646
+ let variant = "context";
3647
+ let title;
3648
+ const boundaryStack = [];
3649
+ for (const rawLine of lines) {
3650
+ let line = rawLine;
3651
+ if (!line || line.startsWith("%%")) continue;
3652
+ const v = line.match(C4_VARIANT_RE);
3653
+ if (v) {
3654
+ const t = v[1].toLowerCase();
3655
+ variant = t === "context" ? "context" : t === "container" ? "container" : t === "component" ? "component" : "deployment";
3656
+ continue;
3657
+ }
3658
+ const titleMatch = line.match(/^title\s+(.+)$/i);
3659
+ if (titleMatch) {
3660
+ title = titleMatch[1].trim();
3661
+ continue;
3662
+ }
3663
+ if (line === "}") {
3664
+ boundaryStack.pop();
3665
+ continue;
3666
+ }
3667
+ const rel = line.match(C4_REL_RE);
3668
+ if (rel) {
3669
+ const [, , src, tgt, label, technology] = rel;
3670
+ relations.push({ source: src, target: tgt, label, technology });
3671
+ continue;
3672
+ }
3673
+ const el = line.match(C4_ELEMENT_RE);
3674
+ if (el) {
3675
+ const [, type, id, ...rest] = el;
3676
+ const kind = C4_KIND_MAP[type] ?? "system";
3677
+ const label = rest[0] ?? id;
3678
+ const isBoundary = kind.endsWith("boundary") || kind === "node";
3679
+ const technology = !isBoundary ? rest[1] : void 0;
3680
+ const description = !isBoundary ? rest[2] : rest[1];
3681
+ elements.push({
3682
+ id,
3683
+ kind,
3684
+ label: label.trim(),
3685
+ technology: technology?.trim(),
3686
+ description: description?.trim(),
3687
+ parent: boundaryStack.length > 0 ? boundaryStack[boundaryStack.length - 1] : void 0
3688
+ });
3689
+ if (line.endsWith("{") && isBoundary) {
3690
+ boundaryStack.push(id);
3691
+ }
3692
+ }
3693
+ }
3694
+ return { type: "c4", variant, title, elements, relations };
3695
+ }
3696
+ function parseGitGraph(source) {
3697
+ const lines = source.split("\n").map((l) => l.trim());
3698
+ const ops = [];
3699
+ let title;
3700
+ for (const line of lines) {
3701
+ if (!line || line.startsWith("%%")) continue;
3702
+ if (/^gitgraph\b/i.test(line) || /^---/.test(line)) continue;
3703
+ const titleMatch = line.match(/^title:\s*(.+)$/i) || line.match(/^title\s+(.+)$/i);
3704
+ if (titleMatch) {
3705
+ title = titleMatch[1].trim();
3706
+ continue;
3707
+ }
3708
+ const commit = line.match(/^commit\b(.*)$/i);
3709
+ if (commit) {
3710
+ const rest = commit[1];
3711
+ const idMatch = rest.match(/id:\s*"([^"]+)"/);
3712
+ const tagMatch = rest.match(/tag:\s*"([^"]+)"/);
3713
+ const typeMatch = rest.match(/type:\s*(HIGHLIGHT|REVERSE|NORMAL)/);
3714
+ ops.push({
3715
+ kind: "commit",
3716
+ id: idMatch?.[1],
3717
+ tag: tagMatch?.[1],
3718
+ type: typeMatch ? typeMatch[1] : "NORMAL"
3719
+ });
3720
+ continue;
3721
+ }
3722
+ const branch = line.match(/^branch\s+([\w/-]+)/i);
3723
+ if (branch) {
3724
+ ops.push({ kind: "branch", name: branch[1] });
3725
+ continue;
3726
+ }
3727
+ const checkout = line.match(/^(?:checkout|switch)\s+([\w/-]+)/i);
3728
+ if (checkout) {
3729
+ ops.push({ kind: "checkout", name: checkout[1] });
3730
+ continue;
3731
+ }
3732
+ const merge = line.match(/^merge\s+([\w/-]+)(?:\s+tag:\s*"([^"]+)")?/i);
3733
+ if (merge) {
3734
+ ops.push({ kind: "merge", from: merge[1], tag: merge[2] });
3735
+ continue;
3736
+ }
3737
+ const cherry = line.match(/^cherry-pick\s+id:\s*"([^"]+)"/i);
3738
+ if (cherry) {
3739
+ ops.push({ kind: "cherry-pick", commitId: cherry[1] });
3740
+ }
3741
+ }
3742
+ return { type: "gitgraph", title, ops };
3743
+ }
3744
+
3745
+ // src/utils/diagrams/registry.ts
3746
+ var REGISTRY = /* @__PURE__ */ new Map();
3747
+ function register(entry) {
3748
+ REGISTRY.set(entry.type, entry);
3749
+ }
3750
+ function getRenderer(type) {
3751
+ return REGISTRY.get(type) ?? null;
3752
+ }
3753
+ function hasRenderer(type) {
3754
+ return REGISTRY.has(type);
3755
+ }
3756
+ function _resetRegistry() {
3757
+ REGISTRY.clear();
3758
+ }
3759
+ var LAZY_RENDERERS = /* @__PURE__ */ new Map();
3760
+ function getLazyRenderer(type) {
3761
+ let lazyComp = LAZY_RENDERERS.get(type);
3762
+ if (lazyComp) return lazyComp;
3763
+ const entry = getRenderer(type);
3764
+ if (!entry) return null;
3765
+ lazyComp = lazy(entry.loader);
3766
+ LAZY_RENDERERS.set(type, lazyComp);
3767
+ return lazyComp;
3768
+ }
3769
+ function DiagramRenderer({ source, dark, handleRef, onError }) {
3770
+ const [parsed, setParsed] = useState(null);
3771
+ useEffect(() => {
3772
+ let cancelled = false;
3773
+ setParsed(null);
3774
+ parseToIR(source).then((result) => {
3775
+ if (cancelled) return;
3776
+ if (!result.ok && onError) onError(result.error);
3777
+ setParsed(result);
3778
+ });
3779
+ return () => {
3780
+ cancelled = true;
3781
+ };
3782
+ }, [source, onError]);
3783
+ if (!parsed) {
3784
+ return /* @__PURE__ */ jsx("div", { className: "my-4 text-slate-400 text-xs italic", children: "Parsing diagram\u2026" });
3785
+ }
3786
+ if (!parsed.ok) {
3787
+ return /* @__PURE__ */ jsxs("div", { className: "my-4 p-3 bg-red-50 border border-red-200 rounded-lg text-red-600 text-xs font-mono", children: [
3788
+ "Diagram parse error: ",
3789
+ parsed.error
3790
+ ] });
3791
+ }
3792
+ const ir = parsed.ir;
3793
+ const lazyRenderer = getLazyRenderer(ir.type);
3794
+ if (!lazyRenderer) {
3795
+ return /* @__PURE__ */ jsxs("div", { className: "my-4 p-3 bg-amber-50 border border-amber-200 rounded-lg text-amber-700 text-xs font-mono", children: [
3796
+ 'No renderer registered for "',
3797
+ ir.type,
3798
+ '".'
3799
+ ] });
3800
+ }
3801
+ const Component = lazyRenderer;
3802
+ return /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "my-4 text-slate-400 text-xs italic", children: "Loading renderer\u2026" }), children: /* @__PURE__ */ jsx(Component, { ir, dark, handleRef }) });
3803
+ }
3804
+
3805
+ // src/utils/diagrams/export.ts
3806
+ var INLINEABLE_STYLE_PROPS = [
3807
+ "fill",
3808
+ "fill-opacity",
3809
+ "stroke",
3810
+ "stroke-width",
3811
+ "stroke-opacity",
3812
+ "stroke-dasharray",
3813
+ "stroke-linecap",
3814
+ "stroke-linejoin",
3815
+ "stroke-miterlimit",
3816
+ "opacity",
3817
+ "visibility",
3818
+ "display",
3819
+ "font-family",
3820
+ "font-size",
3821
+ "font-weight",
3822
+ "font-style",
3823
+ "text-anchor",
3824
+ "dominant-baseline",
3825
+ "alignment-baseline",
3826
+ "letter-spacing",
3827
+ "color",
3828
+ "cursor"
3829
+ ];
3830
+ var SVG_NS = "http://www.w3.org/2000/svg";
3831
+ var XLINK_NS = "http://www.w3.org/1999/xlink";
3832
+ function resolveSource(source) {
3833
+ if (typeof source === "string") return source;
3834
+ if (typeof source === "function") {
3835
+ const el = source();
3836
+ if (!el) throw new Error("SVG source resolver returned null");
3837
+ return el;
3838
+ }
3839
+ return source;
3840
+ }
3841
+ function inlineStylesOnClone(live, clone) {
3842
+ if (typeof window === "undefined" || !window.getComputedStyle) return;
3843
+ const visit = (l, c) => {
3844
+ const computed = window.getComputedStyle(l);
3845
+ const decls = [];
3846
+ for (const prop of INLINEABLE_STYLE_PROPS) {
3847
+ const value = computed.getPropertyValue(prop);
3848
+ if (value && value !== "" && value !== "none" && value !== "normal") {
3849
+ decls.push(`${prop}: ${value}`);
3850
+ }
3851
+ }
3852
+ if (decls.length > 0) {
3853
+ const existing = c.getAttribute("style") ?? "";
3854
+ const merged = existing ? `${existing}; ${decls.join("; ")}` : decls.join("; ");
3855
+ c.setAttribute("style", merged);
3856
+ }
3857
+ const liveChildren = l.children;
3858
+ const cloneChildren = c.children;
3859
+ const n = Math.min(liveChildren.length, cloneChildren.length);
3860
+ for (let i = 0; i < n; i++) visit(liveChildren[i], cloneChildren[i]);
3861
+ };
3862
+ visit(live, clone);
3863
+ }
3864
+ function toSvgString(source, options = {}) {
3865
+ const inlineStyles = options.inlineStyles !== false;
3866
+ const stripForeignObject = options.stripForeignObject !== false;
3867
+ const resolved = resolveSource(source);
3868
+ if (typeof resolved === "string") return resolved;
3869
+ const clone = resolved.cloneNode(true);
3870
+ if (!clone.getAttribute("xmlns")) clone.setAttribute("xmlns", SVG_NS);
3871
+ if (!clone.getAttribute("xmlns:xlink")) clone.setAttribute("xmlns:xlink", XLINK_NS);
3872
+ if (inlineStyles) inlineStylesOnClone(resolved, clone);
3873
+ if (stripForeignObject) clone.querySelectorAll("foreignObject").forEach((el) => el.remove());
3874
+ return new XMLSerializer().serializeToString(clone);
3875
+ }
3876
+ function getSvgDimensions(svg) {
3877
+ const fallback = { width: 800, height: 600 };
3878
+ const vbMatch = svg.match(/viewBox\s*=\s*["']([^"']+)["']/);
3879
+ if (vbMatch) {
3880
+ const parts = vbMatch[1].split(/[\s,]+/).map((p) => parseFloat(p)).filter((n) => !isNaN(n));
3881
+ if (parts.length === 4 && parts[2] > 0 && parts[3] > 0) {
3882
+ return { width: parts[2], height: parts[3] };
3883
+ }
3884
+ }
3885
+ const rootMatch = svg.match(/<svg\b[^>]*>/);
3886
+ if (rootMatch) {
3887
+ const root = rootMatch[0];
3888
+ const wMatch = root.match(/\swidth\s*=\s*["']([^"']+)["']/);
3889
+ const hMatch = root.match(/\sheight\s*=\s*["']([^"']+)["']/);
3890
+ const w = wMatch ? parseFloat(wMatch[1]) : NaN;
3891
+ const h = hMatch ? parseFloat(hMatch[1]) : NaN;
3892
+ return {
3893
+ width: !isNaN(w) && w > 0 ? w : fallback.width,
3894
+ height: !isNaN(h) && h > 0 ? h : fallback.height
3895
+ };
3896
+ }
3897
+ return fallback;
3898
+ }
3899
+ async function svgToPngBlob(svg, options = {}) {
3900
+ const scale = options.scale ?? 2;
3901
+ const background = options.background === void 0 ? "#ffffff" : options.background;
3902
+ const { width, height } = getSvgDimensions(svg);
3903
+ const blob = new Blob([svg], { type: "image/svg+xml;charset=utf-8" });
3904
+ const url = URL.createObjectURL(blob);
3905
+ try {
3906
+ const img = new Image();
3907
+ img.crossOrigin = "anonymous";
3908
+ await new Promise((resolve, reject) => {
3909
+ img.onload = () => resolve();
3910
+ img.onerror = () => reject(new Error("Failed to load SVG image for PNG conversion"));
3911
+ img.src = url;
3912
+ });
3913
+ const canvas = document.createElement("canvas");
3914
+ canvas.width = Math.round(width * scale);
3915
+ canvas.height = Math.round(height * scale);
3916
+ const ctx = canvas.getContext("2d");
3917
+ if (!ctx) throw new Error("Canvas 2D context unavailable");
3918
+ if (background !== null) {
3919
+ ctx.fillStyle = background;
3920
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
3921
+ }
3922
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
3923
+ return await new Promise(
3924
+ (resolve, reject) => canvas.toBlob(
3925
+ (b) => b ? resolve(b) : reject(new Error("canvas.toBlob() returned null")),
3926
+ "image/png"
3927
+ )
3928
+ );
3929
+ } finally {
3930
+ URL.revokeObjectURL(url);
3931
+ }
3932
+ }
3933
+ function triggerDownload(blob, filename) {
3934
+ const url = URL.createObjectURL(blob);
3935
+ const a = document.createElement("a");
3936
+ a.href = url;
3937
+ a.download = filename;
3938
+ a.rel = "noopener";
3939
+ document.body.appendChild(a);
3940
+ a.click();
3941
+ setTimeout(() => {
3942
+ document.body.removeChild(a);
3943
+ URL.revokeObjectURL(url);
3944
+ }, 1500);
3945
+ }
3946
+ async function downloadSvg(source, filename) {
3947
+ const svg = toSvgString(source, { stripForeignObject: false });
3948
+ const blob = new Blob([svg], { type: "image/svg+xml;charset=utf-8" });
3949
+ triggerDownload(blob, filename);
3950
+ }
3951
+ async function downloadPng(source, filename, options = {}) {
3952
+ const svg = toSvgString(source);
3953
+ const blob = await svgToPngBlob(svg, options);
3954
+ triggerDownload(blob, filename);
3955
+ }
3956
+ async function copySvgToClipboard(source) {
3957
+ const svg = toSvgString(source, { stripForeignObject: false });
3958
+ await navigator.clipboard.writeText(svg);
3959
+ }
3960
+ async function copyPngToClipboard(source, options = {}) {
3961
+ const svg = toSvgString(source);
3962
+ const blob = await svgToPngBlob(svg, options);
3963
+ if (typeof ClipboardItem === "undefined" || !navigator.clipboard?.write) {
3964
+ throw new Error("Clipboard image write not supported in this environment");
3965
+ }
3966
+ await navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]);
3967
+ }
3968
+ var ICON_PROPS = {
3969
+ width: 11,
3970
+ height: 11,
3971
+ viewBox: "0 0 24 24",
3972
+ fill: "none",
3973
+ stroke: "currentColor",
3974
+ strokeWidth: 2,
3975
+ strokeLinecap: "round",
3976
+ strokeLinejoin: "round",
3977
+ "aria-hidden": true
3978
+ };
3979
+ var IconCopy = () => /* @__PURE__ */ jsxs("svg", { ...ICON_PROPS, children: [
3980
+ /* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
3981
+ /* @__PURE__ */ jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
3982
+ ] });
3983
+ var IconCheck = (props) => /* @__PURE__ */ jsx("svg", { ...ICON_PROPS, stroke: props.color ?? "currentColor", children: /* @__PURE__ */ jsx("polyline", { points: "20 6 9 17 4 12" }) });
3984
+ var IconDownload = () => /* @__PURE__ */ jsxs("svg", { ...ICON_PROPS, children: [
3985
+ /* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
3986
+ /* @__PURE__ */ jsx("polyline", { points: "7 10 12 15 17 10" }),
3987
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
3988
+ ] });
3989
+ var IconFileImage = () => /* @__PURE__ */ jsxs("svg", { ...ICON_PROPS, children: [
3990
+ /* @__PURE__ */ jsx("path", { d: "M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" }),
3991
+ /* @__PURE__ */ jsx("polyline", { points: "14 2 14 8 20 8" }),
3992
+ /* @__PURE__ */ jsx("circle", { cx: "10", cy: "13", r: "2" }),
3993
+ /* @__PURE__ */ jsx("path", { d: "m20 17-1.09-1.09a2 2 0 0 0-2.82 0L10 22" })
3994
+ ] });
3995
+ var IconImageDown = () => /* @__PURE__ */ jsxs("svg", { ...ICON_PROPS, children: [
3996
+ /* @__PURE__ */ jsx("path", { d: "M10.3 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v5.3" }),
3997
+ /* @__PURE__ */ jsx("path", { d: "m14 19 3 3 3-3" }),
3998
+ /* @__PURE__ */ jsx("path", { d: "M17 22V13" }),
3999
+ /* @__PURE__ */ jsx("circle", { cx: "9", cy: "9", r: "2" })
4000
+ ] });
4001
+ function DiagramExportToolbar({
4002
+ source,
4003
+ filenameBase = "diagram",
4004
+ pngScale = 2,
4005
+ pngBackground,
4006
+ className = "",
4007
+ onError
4008
+ }) {
4009
+ const [flash, setFlash] = useState("idle");
4010
+ const flashAndReset = (state) => {
4011
+ setFlash(state);
4012
+ setTimeout(() => setFlash("idle"), 2e3);
4013
+ };
4014
+ const safe = async (fn, e) => {
4015
+ e.stopPropagation();
4016
+ try {
4017
+ await fn();
4018
+ } catch (err) {
4019
+ onError?.(err instanceof Error ? err : new Error(String(err)));
4020
+ }
4021
+ };
4022
+ const handleCopySvg = (e) => safe(async () => {
4023
+ await copySvgToClipboard(source);
4024
+ flashAndReset("svg-copied");
4025
+ }, e);
4026
+ const handleCopyPng = (e) => safe(async () => {
4027
+ await copyPngToClipboard(source, { scale: pngScale, background: pngBackground });
4028
+ flashAndReset("png-copied");
4029
+ }, e);
4030
+ const handleDownloadSvg = (e) => safe(() => downloadSvg(source, `${filenameBase}.svg`), e);
4031
+ const handleDownloadPng = (e) => safe(
4032
+ () => downloadPng(source, `${filenameBase}.png`, { scale: pngScale, background: pngBackground }),
4033
+ e
4034
+ );
4035
+ const buttonClass = "flex items-center gap-1 px-2 py-1 rounded-md bg-white border border-slate-200 text-slate-500 hover:text-blue-600 hover:border-blue-300 text-[10px] font-bold shadow-sm transition-colors";
4036
+ return /* @__PURE__ */ jsxs("div", { className: `flex gap-1 ${className}`, role: "toolbar", "aria-label": "Diagram export", children: [
4037
+ /* @__PURE__ */ jsxs(
4038
+ "button",
4039
+ {
4040
+ type: "button",
4041
+ onClick: handleCopySvg,
4042
+ className: buttonClass,
4043
+ "aria-label": "Copy SVG markup to clipboard",
4044
+ title: "Copy SVG markup",
4045
+ children: [
4046
+ flash === "svg-copied" ? /* @__PURE__ */ jsx(IconCheck, { color: "#22c55e" }) : /* @__PURE__ */ jsx(IconCopy, {}),
4047
+ flash === "svg-copied" ? "Copied!" : "SVG"
4048
+ ]
4049
+ }
4050
+ ),
4051
+ /* @__PURE__ */ jsxs(
4052
+ "button",
4053
+ {
4054
+ type: "button",
4055
+ onClick: handleCopyPng,
4056
+ className: buttonClass,
4057
+ "aria-label": "Copy diagram as PNG to clipboard",
4058
+ title: "Copy as PNG image",
4059
+ children: [
4060
+ flash === "png-copied" ? /* @__PURE__ */ jsx(IconCheck, { color: "#22c55e" }) : /* @__PURE__ */ jsx(IconFileImage, {}),
4061
+ flash === "png-copied" ? "Copied!" : "PNG"
4062
+ ]
4063
+ }
4064
+ ),
4065
+ /* @__PURE__ */ jsxs(
4066
+ "button",
4067
+ {
4068
+ type: "button",
4069
+ onClick: handleDownloadSvg,
4070
+ className: buttonClass,
4071
+ "aria-label": "Download diagram as SVG file",
4072
+ title: "Download SVG",
4073
+ children: [
4074
+ /* @__PURE__ */ jsx(IconDownload, {}),
4075
+ " SVG"
4076
+ ]
4077
+ }
4078
+ ),
4079
+ /* @__PURE__ */ jsxs(
4080
+ "button",
4081
+ {
4082
+ type: "button",
4083
+ onClick: handleDownloadPng,
4084
+ className: buttonClass,
4085
+ "aria-label": "Download diagram as PNG file",
4086
+ title: "Download PNG",
4087
+ children: [
4088
+ /* @__PURE__ */ jsx(IconImageDown, {}),
4089
+ " PNG"
4090
+ ]
4091
+ }
4092
+ )
4093
+ ] });
4094
+ }
4095
+
4096
+ // src/index.ts
4097
+ init_FlowchartRenderer();
4098
+ init_ERRenderer();
4099
+ init_PieRenderer();
4100
+ init_QuadrantRenderer();
4101
+ init_JourneyRenderer();
4102
+ init_SequenceRenderer();
4103
+ init_ClassRenderer();
4104
+ init_StateRenderer();
4105
+ init_GanttRenderer();
4106
+ init_TimelineRenderer();
4107
+ init_MindmapRenderer();
4108
+ init_ArchitectureRenderer();
4109
+ init_C4Renderer();
4110
+ init_GitGraphRenderer();
4111
+
4112
+ // src/components/diagrams/bootstrap.ts
4113
+ var bootstrapped = false;
4114
+ function bootstrapDiagramRenderers() {
4115
+ if (bootstrapped) return;
4116
+ bootstrapped = true;
4117
+ register({
4118
+ type: "flowchart",
4119
+ loader: () => Promise.resolve().then(() => (init_FlowchartRenderer(), FlowchartRenderer_exports))
4120
+ });
4121
+ register({
4122
+ type: "er",
4123
+ loader: () => Promise.resolve().then(() => (init_ERRenderer(), ERRenderer_exports))
4124
+ });
4125
+ register({
4126
+ type: "pie",
4127
+ loader: () => Promise.resolve().then(() => (init_PieRenderer(), PieRenderer_exports))
4128
+ });
4129
+ register({
4130
+ type: "quadrant",
4131
+ loader: () => Promise.resolve().then(() => (init_QuadrantRenderer(), QuadrantRenderer_exports))
4132
+ });
4133
+ register({
4134
+ type: "journey",
4135
+ loader: () => Promise.resolve().then(() => (init_JourneyRenderer(), JourneyRenderer_exports))
4136
+ });
4137
+ register({
4138
+ type: "sequence",
4139
+ loader: () => Promise.resolve().then(() => (init_SequenceRenderer(), SequenceRenderer_exports))
4140
+ });
4141
+ register({
4142
+ type: "class",
4143
+ loader: () => Promise.resolve().then(() => (init_ClassRenderer(), ClassRenderer_exports))
4144
+ });
4145
+ register({
4146
+ type: "state",
4147
+ loader: () => Promise.resolve().then(() => (init_StateRenderer(), StateRenderer_exports))
4148
+ });
4149
+ register({
4150
+ type: "gantt",
4151
+ loader: () => Promise.resolve().then(() => (init_GanttRenderer(), GanttRenderer_exports))
4152
+ });
4153
+ register({
4154
+ type: "timeline",
4155
+ loader: () => Promise.resolve().then(() => (init_TimelineRenderer(), TimelineRenderer_exports))
4156
+ });
4157
+ register({
4158
+ type: "mindmap",
4159
+ loader: () => Promise.resolve().then(() => (init_MindmapRenderer(), MindmapRenderer_exports))
4160
+ });
4161
+ register({
4162
+ type: "architecture",
4163
+ loader: () => Promise.resolve().then(() => (init_ArchitectureRenderer(), ArchitectureRenderer_exports))
4164
+ });
4165
+ register({
4166
+ type: "c4",
4167
+ loader: () => Promise.resolve().then(() => (init_C4Renderer(), C4Renderer_exports))
4168
+ });
4169
+ register({
4170
+ type: "gitgraph",
4171
+ loader: () => Promise.resolve().then(() => (init_GitGraphRenderer(), GitGraphRenderer_exports))
4172
+ });
4173
+ }
4174
+
4175
+ // src/utils/diagrams/convenience.ts
4176
+ init_dagreLayout();
4177
+ init_svgBuilders();
4178
+ var FLOW_DEFAULT_SIZE = { width: 180, height: 60 };
4179
+ var CLASS_NODE_WIDTH = 220;
4180
+ var ER_NODE_WIDTH = 240;
4181
+ var ER_HEADER_H = 34;
4182
+ var ER_ROW_H = 26;
4183
+ function flowchartNodeSize2(label) {
4184
+ const len = label.length;
4185
+ return {
4186
+ width: Math.max(160, Math.min(320, len * 8 + 40)),
4187
+ height: 48
4188
+ };
4189
+ }
4190
+ function flowchartToSvg(ir, options = {}) {
4191
+ const nodeSizes = /* @__PURE__ */ new Map();
4192
+ for (const node of ir.nodes) {
4193
+ nodeSizes.set(node.id, flowchartNodeSize2(node.label || node.id));
4194
+ }
4195
+ const { nodePositions } = layoutFlowchart(ir, {
4196
+ defaultNodeSize: FLOW_DEFAULT_SIZE,
4197
+ nodeSizes
4198
+ });
4199
+ return buildFlowchartSvg(ir, nodePositions, options);
4200
+ }
4201
+ function classToSvg(ir, options = {}) {
4202
+ const g = new dagre2.graphlib.Graph();
4203
+ g.setGraph({ rankdir: "TB", nodesep: 50, ranksep: 80, marginx: 24, marginy: 24 });
4204
+ g.setDefaultEdgeLabel(() => ({}));
4205
+ const sizeFor = (memberCount) => ({
4206
+ width: CLASS_NODE_WIDTH,
4207
+ height: Math.max(64, 40 + memberCount * 18)
4208
+ });
4209
+ for (const cls of ir.classes) g.setNode(cls.id, sizeFor(cls.members.length));
4210
+ for (const rel of ir.relations) {
4211
+ if (g.hasNode(rel.source) && g.hasNode(rel.target)) g.setEdge(rel.source, rel.target);
4212
+ }
4213
+ dagre2.layout(g);
4214
+ const positions = /* @__PURE__ */ new Map();
4215
+ for (const cls of ir.classes) {
4216
+ const { x, y } = g.node(cls.id);
4217
+ const size = sizeFor(cls.members.length);
4218
+ positions.set(cls.id, {
4219
+ x: x - size.width / 2,
4220
+ y: y - size.height / 2,
4221
+ width: size.width,
4222
+ height: size.height
4223
+ });
4224
+ }
4225
+ return buildClassSvg(ir, positions, options);
4226
+ }
4227
+ function erToSvg(ir, options = {}) {
4228
+ const g = new dagre2.graphlib.Graph();
4229
+ g.setGraph({ rankdir: "LR", nodesep: 60, ranksep: 100, marginx: 24, marginy: 24 });
4230
+ g.setDefaultEdgeLabel(() => ({}));
4231
+ const tableHeight = (cols) => ER_HEADER_H + cols * ER_ROW_H;
4232
+ for (const table of ir.schema.tables) {
4233
+ g.setNode(table.name, {
4234
+ width: ER_NODE_WIDTH,
4235
+ height: tableHeight(table.columns.length)
4236
+ });
4237
+ }
4238
+ for (const rel of ir.schema.relations) {
4239
+ if (g.hasNode(rel.fromTable) && g.hasNode(rel.toTable)) {
4240
+ g.setEdge(rel.fromTable, rel.toTable);
4241
+ }
4242
+ }
4243
+ dagre2.layout(g);
4244
+ const positions = /* @__PURE__ */ new Map();
4245
+ for (const table of ir.schema.tables) {
4246
+ const { x, y } = g.node(table.name);
4247
+ const h = tableHeight(table.columns.length);
4248
+ positions.set(table.name, {
4249
+ x: x - ER_NODE_WIDTH / 2,
4250
+ y: y - h / 2,
4251
+ width: ER_NODE_WIDTH,
4252
+ height: h
4253
+ });
4254
+ }
4255
+ return buildErSvg(ir, positions, options);
4256
+ }
4257
+
4258
+ // src/index.ts
4259
+ init_svgBuilders();
4260
+ init_dagreLayout();
4261
+
4262
+ // src/components/diagrams/darkMode.ts
4263
+ function isDarkMode() {
4264
+ if (typeof document === "undefined") return false;
4265
+ return document.documentElement.classList.contains("dark");
4266
+ }
4267
+ function watchDarkMode(callback) {
4268
+ if (typeof MutationObserver === "undefined" || typeof document === "undefined") {
4269
+ return () => {
4270
+ };
4271
+ }
4272
+ let last = isDarkMode();
4273
+ const observer = new MutationObserver(() => {
4274
+ const next = isDarkMode();
4275
+ if (next !== last) {
4276
+ last = next;
4277
+ callback(next);
4278
+ }
4279
+ });
4280
+ observer.observe(document.documentElement, {
4281
+ attributes: true,
4282
+ attributeFilter: ["class"]
4283
+ });
4284
+ return () => observer.disconnect();
4285
+ }
4286
+
4287
+ // src/index.ts
4288
+ init_theme();
4289
+
4290
+ export { ArchitectureRenderer, C4Renderer, ClassRenderer, DiagramExportToolbar, DiagramRenderer, ERRenderer, FlowchartRenderer, GanttRenderer, GitGraphRenderer, INLINEABLE_STYLE_PROPS, JourneyRenderer, MindmapRenderer, PieRenderer, QuadrantRenderer, SequenceRenderer, StateRenderer, TimelineRenderer, _resetRegistry, bootstrapDiagramRenderers, buildArchitectureSvg, buildC4Svg, buildClassSvg, buildErSvg, buildFlowchartSvg, buildGanttSvg, buildGitGraphSvg, buildJourneySvg, buildMindmapSvg, buildPieSvg, buildQuadrantSvg, buildStateSvg, buildTimelineSvg, classToSvg, copyPngToClipboard, copySvgToClipboard, detectDiagramType, downloadPng, downloadSvg, erToSvg, flowchartToSvg, getDiagramTheme, getRenderer, getSvgDimensions, hasRenderer, isDarkMode, layoutFlowchart, parseToIR, register, svgStringToElement, svgToPngBlob, toSvgString, watchDarkMode };
4291
+ //# sourceMappingURL=index.js.map
4292
+ //# sourceMappingURL=index.js.map