@chatpanel/events 0.102.0 → 0.104.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/adapters.js +1 -83
  2. package/adaptive-tool-policy.js +1 -45
  3. package/agent.js +2 -255
  4. package/attribution.js +1 -132
  5. package/backup-envelope.js +1 -221
  6. package/board-tool.js +2 -165
  7. package/budget.js +1 -116
  8. package/capability.js +1 -126
  9. package/citations.js +5 -127
  10. package/client-prefs.js +1 -148
  11. package/context-attachments.js +8 -144
  12. package/cowriter-router.js +1 -83
  13. package/cowriter-writer.js +19 -193
  14. package/cowriter.js +1 -190
  15. package/curate.js +3 -509
  16. package/distance.js +1 -124
  17. package/engine.js +1 -131
  18. package/entitlement.js +1 -332
  19. package/entity.js +1 -354
  20. package/event.js +1 -175
  21. package/extraction.js +4 -293
  22. package/failover.js +1 -93
  23. package/find-tool.js +1 -66
  24. package/flowchart.js +2 -644
  25. package/gate.js +1 -74
  26. package/harness.js +4 -101
  27. package/index.js +1 -345
  28. package/invariants.js +1 -174
  29. package/job.js +3 -147
  30. package/kernel.js +1 -255
  31. package/knowledge-derive.js +1 -267
  32. package/knowledge.js +3 -230
  33. package/library.js +11 -280
  34. package/loop.js +1 -132
  35. package/manifest.js +1 -107
  36. package/markdown-authoring.js +11 -288
  37. package/markdown-render.js +0 -0
  38. package/mcp-client.js +8 -358
  39. package/mcp-dispatch.js +1 -52
  40. package/mcp-errors.js +1 -106
  41. package/mcp-manager.js +1 -127
  42. package/media-transcript.js +8 -531
  43. package/meeting-analyzers.js +1 -83
  44. package/meeting-insights.js +7 -164
  45. package/meeting-shape.js +1 -282
  46. package/meeting-text.js +5 -94
  47. package/memory.js +2 -638
  48. package/model-candidates.js +1 -358
  49. package/model-health.js +1 -182
  50. package/model-ledger.js +1 -228
  51. package/model-picker.js +1 -188
  52. package/note-actions.js +15 -246
  53. package/note-graph.js +1 -158
  54. package/note-links.js +1 -68
  55. package/note-mentions.js +8 -114
  56. package/note-plan.js +9 -107
  57. package/note-research.js +1 -135
  58. package/observability.js +1 -119
  59. package/omni.js +1 -125
  60. package/order.js +1 -78
  61. package/package.json +4 -5
  62. package/pdf-layout.js +7 -253
  63. package/policy.js +1 -0
  64. package/project-run.js +20 -444
  65. package/project.js +1 -170
  66. package/promotion.js +1 -171
  67. package/queue.js +1 -98
  68. package/reach.js +1 -23
  69. package/recipe-tool.js +2 -161
  70. package/recipe.js +1 -264
  71. package/record-list.js +2 -110
  72. package/recruit.js +3 -456
  73. package/redaction-tokens.js +1 -61
  74. package/ref.js +1 -55
  75. package/registry.js +1 -240
  76. package/route-graph.js +1 -115
  77. package/route-strategies.js +2 -232
  78. package/router.js +2 -880
  79. package/rrf.js +1 -89
  80. package/rules.js +1 -142
  81. package/schedule.js +1 -663
  82. package/scm-connection.js +1 -180
  83. package/scopes.js +1 -12
  84. package/scorecard.js +1 -335
  85. package/search-engines.js +1 -81
  86. package/skill-manifest.js +1 -330
  87. package/skill-scan.js +3 -209
  88. package/skill-sources.js +1 -162
  89. package/skill-vars.js +2 -252
  90. package/slash-commands.js +1 -158
  91. package/source-gate.js +1 -92
  92. package/sources-retrieval.js +5 -186
  93. package/sources.js +1 -273
  94. package/store.js +1 -171
  95. package/structured.js +4 -901
  96. package/subject-kinds.js +1 -5
  97. package/subject-name.js +1 -96
  98. package/sync-plan.js +1 -213
  99. package/synthesis.js +3 -123
  100. package/tags.js +1 -203
  101. package/team-board.js +6 -337
  102. package/team-cache.js +2 -63
  103. package/team-observe.js +1 -118
  104. package/team-org.js +1 -442
  105. package/team-plan.js +4 -131
  106. package/team-record.js +1 -225
  107. package/team-run.js +9 -652
  108. package/team-subtask.js +2 -191
  109. package/team-task.js +2 -124
  110. package/team-tool.js +2 -196
  111. package/team-trail.js +2 -73
  112. package/team-worklog.js +2 -149
  113. package/team.js +1 -302
  114. package/text-search.js +1 -155
  115. package/theme.js +17 -154
  116. package/titles.js +4 -285
  117. package/tool-discovery.js +1 -87
  118. package/tool-dispatch.js +5 -230
  119. package/tool-groups.js +1 -81
  120. package/tool-hints.js +10 -185
  121. package/tool-loop-guard.js +4 -182
  122. package/tool-need.js +2 -124
  123. package/tool-result.js +2 -438
  124. package/tool-round.js +0 -0
  125. package/tool-schema.js +1 -155
  126. package/tool-traits.js +1 -182
  127. package/toolset.js +2 -88
  128. package/trajectory.js +1 -530
  129. package/turn-loop.js +4 -440
  130. package/upcast.js +1 -37
  131. package/vault.js +2 -250
  132. package/view.js +1 -88
  133. package/voice-intents.js +2 -1488
  134. package/voice-speaker.js +1 -98
  135. package/weather-tool.js +1 -65
  136. package/weather.js +2 -211
  137. package/web-search-tool.js +14 -102
  138. package/web-search.js +1 -160
  139. package/widget.js +1 -153
package/flowchart.js CHANGED
@@ -1,644 +1,2 @@
1
- // flowchart.js Mermaid `flowchart` text a self-contained SVG string.
2
- //
3
- // Why this exists: models answer "draw me a diagram" with a ```mermaid block, and showing
4
- // the source instead of the picture is a dead end for the reader. Mermaid itself is a ~3 MB
5
- // dependency that needs a live DOM to measure text, which rules it out for a side panel that
6
- // treats first-paint weight as a release gate — and for a renderer we want to unit-test.
7
- //
8
- // So this is a focused renderer for the shape models actually emit: `flowchart TB/LR` with
9
- // labelled nodes, edges, subgraphs and classDef styling. It is PURE (string in, string out),
10
- // so it is testable without a browser and runs identically in the extension, a desktop app or
11
- // a mobile client — which is why it lives in the shared package rather than in one client.
12
- //
13
- // SAFETY: the output is only ever shown through an <img src="data:image/svg+xml,…">, which
14
- // loads SVG in restricted mode (no scripts, no external fetches). Every piece of model text
15
- // is XML-escaped here as well, so a label can't break out of its element either way.
16
- //
17
- // Anything it can't parse returns null, and the caller keeps showing the code block.
18
-
19
- // Node shapes, LONGEST OPENER FIRST — `[(` has to be tried before `[`, or a database node
20
- // keeps its own bracket in the label and renders as `"etcd…")`. The value is the closer and
21
- // the shape name; shape drives the corner radius, not a different silhouette (a hexagon that
22
- // is really a rounded rect still reads correctly; a label with a stray `)` does not).
23
- const NODE_SHAPES = [
24
- ['[[', ']]', 'rect'], // subroutine
25
- ['[(', ')]', 'round'], // database / cylinder
26
- ['((', '))', 'pill'], // circle
27
- ['([', '])', 'pill'], // stadium
28
- ['{{', '}}', 'rect'], // hexagon
29
- ['[/', '/]', 'rect'], // parallelogram
30
- ['[\\', '\\]', 'rect'],
31
- ['[', ']', 'rect'],
32
- ['(', ')', 'round'],
33
- ['{', '}', 'rect'], // diamond → a rounded rect with a tint; shape fidelity isn't the point
34
- ['>', ']', 'rect'], // asymmetric
35
- ];
36
-
37
- const DEFAULT_PALETTE = {
38
- fill: '#f8fafc', stroke: '#cbd5e1', color: '#1e293b',
39
- };
40
- // Ranks get progressively lighter accents when the diagram declares no classes of its own,
41
- // so an unstyled chart still reads as designed rather than as a grey wireframe.
42
- const RANK_TINTS = [
43
- { fill: '#1e293b', stroke: '#1e293b', color: '#ffffff' },
44
- { fill: '#e0e7ff', stroke: '#a5b4fc', color: '#312e81' },
45
- { fill: '#f1f5f9', stroke: '#cbd5e1', color: '#0f172a' },
46
- ];
47
-
48
- function esc(s) {
49
- return String(s == null ? '' : s)
50
- .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
51
- .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
52
- }
53
-
54
- // Labels carry inline HTML — models write `Name<br/><i>what it does</i>` constantly, and a
55
- // renderer that only knows <br/> prints the tags themselves, which a reader reads as a bug.
56
- // A whole line wrapped in one emphasis tag becomes a styled line; a formatting tag anywhere
57
- // else is dropped, because half-styled runs would need per-run text layout for very little.
58
- //
59
- // ONLY the formatting tags below are dropped. Anything else a label contains is left exactly
60
- // as the model wrote it and escaped at render time, so `<script>` still shows up as the text
61
- // `<script>` rather than quietly vanishing — a renderer that eats unknown markup hides content
62
- // as readily as it hides an attack.
63
- const FORMAT_TAGS = /<\/?(?:i|em|b|strong|u|code|span|small|sup|sub)\s*\/?>/gi;
64
- const EMPH = [
65
- [/^<(i|em)>([\s\S]*)<\/\1>$/i, 'italic'],
66
- [/^<(b|strong)>([\s\S]*)<\/\1>$/i, 'bold'],
67
- ];
68
- function styleOfPart(raw) {
69
- let text = String(raw || '').trim();
70
- let italic = false;
71
- let bold = false;
72
- // Peel repeatedly so `<b><i>x</i></b>` picks up both.
73
- for (let n = 0; n < 3; n++) {
74
- let hit = false;
75
- for (const [re, kind] of EMPH) {
76
- const m = re.exec(text);
77
- if (!m) continue;
78
- text = m[2].trim();
79
- if (kind === 'italic') italic = true; else bold = true;
80
- hit = true;
81
- }
82
- if (!hit) break;
83
- }
84
- return { text: text.replace(FORMAT_TAGS, ' ').trim(), italic, bold };
85
- }
86
-
87
- // Split a label into display lines: explicit <br/> first, then greedy wrap on words. Each line
88
- // carries its own emphasis so the renderer can set font-style/weight per <text> element.
89
- export function wrapLabel(raw, maxChars = 22) {
90
- const parts = String(raw || '').split(/<br\s*\/?>/i);
91
- const lines = [];
92
- for (const part of parts) {
93
- const { text, italic, bold } = styleOfPart(part);
94
- const words = text.split(/\s+/).filter(Boolean);
95
- if (!words.length) continue;
96
- let line = '';
97
- for (const w of words) {
98
- if (!line) line = w;
99
- else if ((line + ' ' + w).length <= maxChars) line += ' ' + w;
100
- else { lines.push({ text: line, italic, bold }); line = w; }
101
- }
102
- if (line) lines.push({ text: line, italic, bold });
103
- }
104
- return lines.length ? lines : [{ text: '', italic: false, bold: false }];
105
- }
106
-
107
- // Strip mermaid's quoting around a label.
108
- function cleanLabel(s) {
109
- let t = String(s || '').trim();
110
- if ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'"))) t = t.slice(1, -1);
111
- return t.trim();
112
- }
113
-
114
- // ONE connector, matched at the current scan position:
115
- // an optional opening head `<`, a run of -/=/. , an optional mid-label (`A -- yes --> B`),
116
- // and an optional closing head `>` / `o` / `x`.
117
- // The head characters are excluded from the body and from the mid-label, so `-->` can never be
118
- // read as a body and `A --> B` can never swallow `B` as a label.
119
- const CONNECTOR = /^(<?)([-=.]{2,})(?:[ \t]*([^-=<>|\n]{1,80}?)[ \t]*([-=.]{2,}))?([>ox]?)/;
120
-
121
- /**
122
- * Split one line into node tokens + the edge labels between them, honouring brackets and
123
- * quotes so an arrow inside a label can't be read as a connector. Returns null when the line
124
- * holds no top-level edge operator. Handles chains (`A --> B -->|yes| C`), bidirectional
125
- * links (`A <--> B`) and mid-line labels (`A -- yes --> B`).
126
- */
127
- export function splitEdgeChain(line) {
128
- const s = String(line || '');
129
- const parts = [];
130
- const labels = [];
131
- const bidir = [];
132
- let buf = '';
133
- let depth = 0;
134
- let quote = '';
135
- for (let i = 0; i < s.length; i++) {
136
- const ch = s[i];
137
- if (quote) { buf += ch; if (ch === quote) quote = ''; continue; }
138
- if (ch === '"' || ch === "'") { quote = ch; buf += ch; continue; }
139
- if (ch === '[' || ch === '(' || ch === '{') { depth++; buf += ch; continue; }
140
- if (ch === ']' || ch === ')' || ch === '}') { depth = Math.max(0, depth - 1); buf += ch; continue; }
141
- if (depth === 0 && (ch === '-' || ch === '=' || ch === '<')) {
142
- const m = CONNECTOR.exec(s.slice(i));
143
- // A `<` only opens a connector when a real arrow body follows it.
144
- if (m && (ch !== '<' || m[1])) {
145
- i += m[0].length - 1;
146
- let label = (m[3] || '').trim();
147
- // An explicit |label| directly after the arrow wins over a mid-line one.
148
- const rest = s.slice(i + 1);
149
- const lm = rest.match(/^\s*\|([^|]*)\|/);
150
- if (lm) { label = lm[1]; i += lm[0].length; }
151
- parts.push(buf); labels.push(label); bidir.push(m[1] === '<'); buf = '';
152
- continue;
153
- }
154
- }
155
- buf += ch;
156
- }
157
- parts.push(buf);
158
- if (parts.length < 2) return null;
159
- return { parts: parts.map((p) => p.trim().replace(/;$/, '')).filter(Boolean), labels, bidir };
160
- }
161
-
162
- /**
163
- * Parse a mermaid flowchart into { dir, nodes, edges, classDefs, nodeClass, groups }.
164
- * Returns null when the text isn't a flowchart we handle.
165
- */
166
- export function parseFlowchart(text) {
167
- const src = String(text || '');
168
- const header = src.match(/^\s*(?:flowchart|graph)\s+(TB|TD|BT|LR|RL)\b/im);
169
- if (!header) return null;
170
- const dir = header[1].toUpperCase();
171
-
172
- const nodes = new Map(); // id -> { id, label, shape }
173
- const edges = []; // { from, to, label, both }
174
- const classDefs = new Map();
175
- const nodeClass = new Map();
176
- const groups = []; // [{ id, title, members: [] }] — subgraphs, in source order
177
- const stack = []; // open subgraphs; the innermost one owns a node
178
- const memberOf = new Map();
179
-
180
- const claim = (id) => {
181
- const g = stack[stack.length - 1];
182
- if (!g || memberOf.has(id)) return;
183
- memberOf.set(id, g);
184
- g.members.push(id);
185
- };
186
- const ensure = (id, label, shape) => {
187
- const key = String(id).trim();
188
- if (!key) return null;
189
- if (!nodes.has(key)) nodes.set(key, { id: key, label: label != null ? label : key, shape: shape || 'rect' });
190
- else {
191
- const n = nodes.get(key);
192
- if (label != null) n.label = label;
193
- if (shape) n.shape = shape;
194
- }
195
- claim(key);
196
- return nodes.get(key);
197
- };
198
-
199
- // `ID["label"]` / `ID(label)` / `ID[("label")]` / `ID{label}` → id + label + shape, else a
200
- // bare id. The label runs to the LAST closer, so brackets inside a label survive.
201
- function readNodeToken(token) {
202
- const t = token.trim();
203
- if (!t) return null;
204
- const m = t.match(/^([A-Za-z0-9_.-]+)\s*([[({>])([\s\S]*)$/);
205
- if (!m) return ensure(t.replace(/^[[(]|[\])]$/g, ''), null);
206
- const id = m[1];
207
- const after = t.slice(id.length).trim();
208
- for (const [open, close, shape] of NODE_SHAPES) {
209
- if (!after.startsWith(open)) continue;
210
- const rest = after.slice(open.length);
211
- const end = rest.lastIndexOf(close);
212
- return ensure(id, cleanLabel(end >= 0 ? rest.slice(0, end) : rest), shape);
213
- }
214
- return ensure(id, null);
215
- }
216
-
217
- for (const raw of src.split('\n')) {
218
- const line = raw.trim();
219
- if (!line || /^%%/.test(line)) continue; // comment
220
- if (/^(?:flowchart|graph)\b/i.test(line)) continue; // header
221
- if (/^direction\b/i.test(line)) continue; // per-subgraph direction: not modelled
222
-
223
- // subgraph CP["Control Plane"] / subgraph CP [Control Plane] / subgraph Control Plane
224
- const sg = line.match(/^subgraph\b\s*(.*)$/i);
225
- if (sg) {
226
- const rest = sg[1].trim();
227
- const withLabel = rest.match(/^([A-Za-z0-9_.-]+)\s*([[("])([\s\S]*)$/);
228
- let id = rest || `sub${groups.length + 1}`;
229
- let title = rest;
230
- if (withLabel) {
231
- id = withLabel[1];
232
- const open = withLabel[2];
233
- const close = open === '[' ? ']' : open === '(' ? ')' : '"';
234
- const body = withLabel[3];
235
- const end = body.lastIndexOf(close);
236
- title = cleanLabel(end >= 0 ? body.slice(0, end) : body);
237
- }
238
- const g = { id, title: cleanLabel(title), members: [] };
239
- groups.push(g);
240
- stack.push(g);
241
- continue;
242
- }
243
- if (/^end\b/i.test(line)) { stack.pop(); continue; }
244
-
245
- // classDef name fill:#fff,color:#000,stroke:#ccc
246
- const cd = line.match(/^classDef\s+([A-Za-z0-9_-]+)\s+(.+?);?$/i);
247
- if (cd) {
248
- const style = {};
249
- for (const pair of cd[2].split(',')) {
250
- const [k, v] = pair.split(':').map((x) => (x || '').trim());
251
- if (k && v) style[k.toLowerCase()] = v;
252
- }
253
- classDefs.set(cd[1], style);
254
- continue;
255
- }
256
- // class A,B,C name
257
- const cl = line.match(/^class\s+([A-Za-z0-9_,.\s-]+?)\s+([A-Za-z0-9_-]+)\s*;?$/i);
258
- if (cl) {
259
- for (const id of cl[1].split(',').map((x) => x.trim()).filter(Boolean)) nodeClass.set(id, cl[2]);
260
- continue;
261
- }
262
- if (/^(?:style|linkStyle|click)\b/i.test(line)) continue; // not modelled
263
-
264
- // Edges, including CHAINS: `A --> B --> C` is two edges, and mermaid emits them often.
265
- // Split on edge operators that are at bracket/quote depth 0, so an arrow inside a label
266
- // ("a --> b") is never mistaken for a connector.
267
- const seg = splitEdgeChain(line);
268
- if (seg && seg.parts.length > 1) {
269
- let prev = readNodeToken(seg.parts[0]);
270
- for (let k = 1; k < seg.parts.length; k++) {
271
- const next = readNodeToken(seg.parts[k]);
272
- if (prev && next) {
273
- edges.push({
274
- from: prev.id, to: next.id,
275
- label: cleanLabel(seg.labels[k - 1] || ''),
276
- both: !!seg.bidir[k - 1],
277
- });
278
- }
279
- prev = next;
280
- }
281
- continue;
282
- }
283
- // A standalone node definition, or a bare id inside a subgraph — which is how mermaid
284
- // says "this existing node belongs to this group".
285
- if (/^[A-Za-z0-9_.-]+\s*[[({>]/.test(line)) { readNodeToken(line); continue; }
286
- if (stack.length && /^[A-Za-z0-9_.-]+;?$/.test(line)) { ensure(line.replace(/;$/, ''), null); continue; }
287
- }
288
-
289
- if (!nodes.size) return null;
290
- return { dir, nodes, edges, classDefs, nodeClass, groups: groups.filter((g) => g.members.length) };
291
- }
292
-
293
- // Longest-path ranking over a SUBSET of the graph: a node sits one level below its deepest
294
- // parent. Cycles are broken by the visited guard, so a malformed graph still lays out instead
295
- // of hanging. Taking ids rather than the whole node map is what lets one subgraph be laid out
296
- // on its own, by the same code that lays out the chart as a whole.
297
- function rankNodes(ids, edges) {
298
- const set = new Set(ids);
299
- const parents = new Map();
300
- const children = new Map();
301
- for (const id of set) { parents.set(id, []); children.set(id, []); }
302
- for (const e of edges) {
303
- if (!set.has(e.from) || !set.has(e.to)) continue;
304
- parents.get(e.to).push(e.from);
305
- children.get(e.from).push(e.to);
306
- }
307
- const rank = new Map();
308
- const roots = [...set].filter((id) => parents.get(id).length === 0);
309
- const queue = roots.length ? [...roots] : [set.values().next().value];
310
- for (const r of queue) rank.set(r, 0);
311
- let guard = set.size * 4;
312
- while (queue.length && guard-- > 0) {
313
- const id = queue.shift();
314
- const r = rank.get(id) || 0;
315
- for (const c of children.get(id) || []) {
316
- const want = r + 1;
317
- if ((rank.get(c) ?? -1) < want) { rank.set(c, want); queue.push(c); }
318
- }
319
- }
320
- for (const id of set) if (!rank.has(id)) rank.set(id, 0);
321
- return { rank, parents, children };
322
- }
323
-
324
- const CHAR_W = 7.1; // ~13px system-ui average advance; good enough for box sizing
325
- const LINE_H = 18;
326
- const PAD_X = 14;
327
- const PAD_Y = 12;
328
- const GROUP_PAD = 16; // breathing room between a subgraph's frame and its nodes
329
- const GROUP_TITLE_H = 28; // the band the subgraph's own name sits in
330
-
331
- function measure(labelLines) {
332
- const w = Math.max(...labelLines.map((l) => l.text.length * (l.bold ? 1.07 : 1))) * CHAR_W + PAD_X * 2;
333
- const h = labelLines.length * LINE_H + PAD_Y * 2;
334
- return { w: Math.max(72, Math.round(w)), h: Math.round(h) };
335
- }
336
-
337
- /**
338
- * Place a set of ids on a grid: rank → row (TB) or column (LR). PURE geometry over sizes, so
339
- * it serves both levels of a clustered chart — the nodes inside one subgraph, and the
340
- * subgraphs themselves as blocks.
341
- */
342
- function placeGrid(ids, edges, sizeOf, { horizontal, gapMain = 56, gapCross = 22 } = {}) {
343
- const { rank, parents } = rankNodes(ids, edges);
344
- const byRank = new Map();
345
- for (const id of ids) {
346
- const r = rank.get(id) || 0;
347
- if (!byRank.has(r)) byRank.set(r, []);
348
- byRank.get(r).push(id);
349
- }
350
- // Order each rank by the average position of its parents (one barycenter sweep) so edges
351
- // cross as little as possible without a full layout engine.
352
- const order = new Map();
353
- const ranks = [...byRank.keys()].sort((a, b) => a - b);
354
- for (const r of ranks) {
355
- const list = byRank.get(r);
356
- if (r === ranks[0]) { list.forEach((id, i) => order.set(id, i)); continue; }
357
- list.sort((a, b) => {
358
- const pa = parents.get(a).map((p) => order.get(p) ?? 0);
359
- const pb = parents.get(b).map((p) => order.get(p) ?? 0);
360
- const ma = pa.length ? pa.reduce((x, y) => x + y, 0) / pa.length : 0;
361
- const mb = pb.length ? pb.reduce((x, y) => x + y, 0) / pb.length : 0;
362
- return ma - mb;
363
- });
364
- list.forEach((id, i) => order.set(id, i));
365
- }
366
-
367
- // Cross-axis extent of each rank, then centre every rank in the widest one.
368
- const rankExtent = new Map();
369
- for (const r of ranks) {
370
- const list = byRank.get(r);
371
- const total = list.reduce((sum, id) => sum + (horizontal ? sizeOf(id).h : sizeOf(id).w), 0)
372
- + gapCross * Math.max(0, list.length - 1);
373
- rankExtent.set(r, total);
374
- }
375
- const maxExtent = Math.max(...rankExtent.values(), 1);
376
-
377
- // Main-axis offset per rank = the tallest/widest box in each preceding rank.
378
- const mainOffset = new Map();
379
- let main = 0;
380
- for (const r of ranks) {
381
- mainOffset.set(r, main);
382
- const size = Math.max(...byRank.get(r).map((id) => (horizontal ? sizeOf(id).w : sizeOf(id).h)));
383
- main += size + gapMain;
384
- }
385
- const mainTotal = Math.max(0, main - gapMain);
386
-
387
- const pos = new Map();
388
- for (const r of ranks) {
389
- let cross = (maxExtent - rankExtent.get(r)) / 2;
390
- for (const id of byRank.get(r)) {
391
- const s = sizeOf(id);
392
- if (horizontal) { pos.set(id, { x: mainOffset.get(r), y: cross }); cross += s.h + gapCross; }
393
- else { pos.set(id, { x: cross, y: mainOffset.get(r) }); cross += s.w + gapCross; }
394
- }
395
- }
396
- return {
397
- pos,
398
- rank,
399
- width: horizontal ? mainTotal : maxExtent,
400
- height: horizontal ? maxExtent : mainTotal,
401
- };
402
- }
403
-
404
- function boxesFor(graph, ids) {
405
- const boxes = new Map();
406
- for (const id of ids) {
407
- const n = graph.nodes.get(id);
408
- const lines = wrapLabel(n.label);
409
- const { w, h } = measure(lines);
410
- boxes.set(id, { id, lines, w, h, shape: n.shape || 'rect', rank: 0 });
411
- }
412
- return boxes;
413
- }
414
-
415
- /** One grid, no clusters: rank → row (TB) or column (LR). Pure geometry. */
416
- function layoutFlat(graph, { dir = graph.dir, gapMain = 56, gapCross = 22 } = {}) {
417
- const horizontal = dir === 'LR' || dir === 'RL';
418
- const ids = [...graph.nodes.keys()];
419
- const boxes = boxesFor(graph, ids);
420
- const placed = placeGrid(ids, graph.edges, (id) => boxes.get(id), { horizontal, gapMain, gapCross });
421
- for (const [id, b] of boxes) {
422
- const p = placed.pos.get(id);
423
- b.x = p.x; b.y = p.y; b.rank = placed.rank.get(id) || 0;
424
- }
425
- return {
426
- boxes, edges: graph.edges, dir, horizontal, clusters: [],
427
- width: placed.width, height: placed.height,
428
- };
429
- }
430
-
431
- /**
432
- * Lay a chart out CLUSTER-FIRST: every subgraph is laid out on its own, then placed as a
433
- * single block in the chart around it.
434
- *
435
- * Flattening subgraphs (what this used to do) does not just lose the frame, it loses the
436
- * meaning. A "control plane / worker node" diagram flattened is nine boxes whose grouping was
437
- * the entire point — and a member with no edges of its own, like a kube-proxy, drifts up to
438
- * rank 0 among strangers. Two levels of the SAME grid fix both: members only rank against
439
- * each other, so a group holds together, and groups rank against each other by the edges that
440
- * cross between them. A chart with no subgraphs never reaches this function.
441
- */
442
- function layoutClustered(graph, { dir = graph.dir, gapMain = 56, gapCross = 22 } = {}) {
443
- const horizontal = dir === 'LR' || dir === 'RL';
444
- const boxes = boxesFor(graph, [...graph.nodes.keys()]);
445
-
446
- // Every node belongs to exactly one cluster: its subgraph, or a cluster of its own.
447
- const clusterOf = new Map();
448
- const clusters = new Map();
449
- graph.groups.forEach((g, i) => {
450
- const key = `g${i}`;
451
- clusters.set(key, { key, title: g.title || g.id, group: true, ids: [] });
452
- for (const m of g.members) if (graph.nodes.has(m) && !clusterOf.has(m)) clusterOf.set(m, key);
453
- });
454
- for (const id of graph.nodes.keys()) {
455
- const key = clusterOf.get(id) || `n:${id}`;
456
- if (!clusters.has(key)) clusters.set(key, { key, title: '', group: false, ids: [] });
457
- clusterOf.set(id, key);
458
- clusters.get(key).ids.push(id);
459
- }
460
-
461
- // Inner layout, one cluster at a time, using only the edges that stay inside it.
462
- for (const c of clusters.values()) {
463
- const inner = placeGrid(c.ids, graph.edges, (id) => boxes.get(id), { horizontal, gapMain, gapCross });
464
- c.inner = inner;
465
- c.padL = c.group ? GROUP_PAD : 0;
466
- c.padT = c.group ? GROUP_TITLE_H : 0;
467
- c.w = inner.width + c.padL * 2;
468
- c.h = inner.height + c.padT + (c.group ? GROUP_PAD : 0);
469
- }
470
-
471
- // Cluster-level edges: one per crossing PAIR, so the ranking sees structure, not volume.
472
- const seen = new Set();
473
- const clusterEdges = [];
474
- for (const e of graph.edges) {
475
- const a = clusterOf.get(e.from);
476
- const b = clusterOf.get(e.to);
477
- if (!a || !b || a === b) continue;
478
- const key = `${a} ${b}`;
479
- if (seen.has(key)) continue;
480
- seen.add(key);
481
- clusterEdges.push({ from: a, to: b });
482
- }
483
-
484
- const keys = [...clusters.keys()];
485
- const outer = placeGrid(keys, clusterEdges, (k) => clusters.get(k), {
486
- horizontal, gapMain: gapMain + 14, gapCross: gapCross + 16,
487
- });
488
- for (const k of keys) {
489
- const c = clusters.get(k);
490
- const p = outer.pos.get(k);
491
- c.x = p.x; c.y = p.y;
492
- const clusterRank = outer.rank.get(k) || 0;
493
- for (const id of c.ids) {
494
- const b = boxes.get(id);
495
- const ip = c.inner.pos.get(id);
496
- b.x = c.x + c.padL + ip.x;
497
- b.y = c.y + c.padT + ip.y;
498
- b.rank = clusterRank + (c.inner.rank.get(id) || 0);
499
- }
500
- }
501
-
502
- return {
503
- boxes, edges: graph.edges, dir, horizontal,
504
- clusters: [...clusters.values()].filter((c) => c.group),
505
- width: outer.width, height: outer.height,
506
- };
507
- }
508
-
509
- /**
510
- * Lay a parsed chart out. A chart with subgraphs is laid out cluster-first; one without takes
511
- * the single grid, byte for byte as before. Callers never choose — the chart does.
512
- */
513
- export function layoutFlowchart(graph, opts = {}) {
514
- return graph.groups?.length ? layoutClustered(graph, opts) : layoutFlat(graph, opts);
515
- }
516
-
517
- function styleFor(id, graph, rankIdx) {
518
- const cls = graph.nodeClass.get(id);
519
- const def = cls ? graph.classDefs.get(cls) : null;
520
- if (def) {
521
- return {
522
- fill: def.fill || DEFAULT_PALETTE.fill,
523
- stroke: def.stroke || def['stroke-width'] ? (def.stroke || DEFAULT_PALETTE.stroke) : DEFAULT_PALETTE.stroke,
524
- color: def.color || DEFAULT_PALETTE.color,
525
- };
526
- }
527
- if (graph.classDefs.size) return DEFAULT_PALETTE; // the author styled some nodes; stay neutral
528
- return RANK_TINTS[Math.min(rankIdx, RANK_TINTS.length - 1)];
529
- }
530
-
531
- function radiusFor(b) {
532
- if (b.shape === 'pill') return Math.round(Math.min(b.h, b.w) / 2);
533
- if (b.shape === 'round') return 16;
534
- return 9;
535
- }
536
-
537
- /**
538
- * Mermaid flowchart text → a complete SVG document string, or null if it isn't a flowchart
539
- * this renderer handles (the caller then keeps the code block).
540
- */
541
- export function renderFlowchartSvg(text, { padding = 18, maxWidth = 1400, autoFlip = true } = {}) {
542
- const graph = parseFlowchart(text);
543
- if (!graph) return null;
544
- const lay = (opts) => layoutFlowchart(graph, opts);
545
- let L = lay({});
546
- if (!L.boxes.size) return null;
547
-
548
- // A broad tree laid out top-down (one root, six categories, ~28 leaves) becomes a 3000px
549
- // strip that is unreadable in a side panel. When a chart comes out far wider than it is
550
- // tall, lay it out left-to-right instead: the same graph, but the wide rank stacks
551
- // vertically and every label stays legible at fit-width. Purely a presentation choice —
552
- // the Code view always shows what the model actually wrote.
553
- if (autoFlip) {
554
- const aspect = L.width / Math.max(1, L.height);
555
- if (aspect > 2.2 && !L.horizontal) L = lay({ dir: 'LR' });
556
- else if (aspect < 0.25 && L.horizontal) L = lay({ dir: 'TB' });
557
- }
558
-
559
- const W = Math.min(maxWidth, Math.ceil(L.width + padding * 2));
560
- const H = Math.ceil(L.height + padding * 2);
561
- const out = [];
562
- out.push(
563
- `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${Math.ceil(L.width + padding * 2)} ${H}" width="${W}" height="${H}" font-family="system-ui,-apple-system,Segoe UI,Roboto,sans-serif">`,
564
- );
565
- out.push(
566
- '<defs><marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">'
567
- + '<path d="M0,0 L10,5 L0,10 z" fill="#94a3b8"/></marker></defs>',
568
- );
569
- out.push(`<rect width="100%" height="100%" fill="#ffffff" rx="10"/>`);
570
-
571
- const px = (v) => Math.round(v * 10) / 10;
572
-
573
- // Subgraph frames first — they sit UNDER everything, as the surface a group is drawn on.
574
- for (const c of L.clusters) {
575
- out.push(
576
- `<rect x="${px(c.x + padding)}" y="${px(c.y + padding)}" width="${px(c.w)}" height="${px(c.h)}" rx="14" `
577
- + `fill="#f8fafc" stroke="#cbd5e1" stroke-width="1.5" stroke-dasharray="5 4"/>`,
578
- );
579
- if (c.title) {
580
- out.push(
581
- `<text x="${px(c.x + padding + 14)}" y="${px(c.y + padding + 19)}" font-size="12" font-weight="700" `
582
- + `fill="#475569">${esc(c.title.slice(0, 48))}</text>`,
583
- );
584
- }
585
- }
586
-
587
- // Edges next, so boxes paint over the joins.
588
- for (const e of L.edges) {
589
- const a = L.boxes.get(e.from);
590
- const b = L.boxes.get(e.to);
591
- if (!a || !b) continue;
592
- let x1, y1, x2, y2, d;
593
- if (L.horizontal) {
594
- // Leave from whichever face actually points at the target: with subgraphs an edge can
595
- // run backwards, and a curve out of the wrong face reads as a different connection.
596
- const back = b.x + b.w < a.x;
597
- x1 = back ? a.x + padding : a.x + a.w + padding;
598
- y1 = a.y + a.h / 2 + padding;
599
- x2 = back ? b.x + b.w + padding : b.x + padding;
600
- y2 = b.y + b.h / 2 + padding;
601
- const mx = (x1 + x2) / 2;
602
- d = `M${px(x1)},${px(y1)} C${px(mx)},${px(y1)} ${px(mx)},${px(y2)} ${px(x2)},${px(y2)}`;
603
- } else {
604
- const back = b.y + b.h < a.y;
605
- x1 = a.x + a.w / 2 + padding;
606
- y1 = back ? a.y + padding : a.y + a.h + padding;
607
- x2 = b.x + b.w / 2 + padding;
608
- y2 = back ? b.y + b.h + padding : b.y + padding;
609
- const my = (y1 + y2) / 2;
610
- d = `M${px(x1)},${px(y1)} C${px(x1)},${px(my)} ${px(x2)},${px(my)} ${px(x2)},${px(y2)}`;
611
- }
612
- // A bidirectional link (`A <--> B`) gets a head at BOTH ends — the marker is declared
613
- // orient="auto-start-reverse", so the same one points the right way at the start.
614
- const startHead = e.both ? ' marker-start="url(#a)"' : '';
615
- out.push(`<path d="${d}" fill="none" stroke="#94a3b8" stroke-width="1.5"${startHead} marker-end="url(#a)"/>`);
616
- if (e.label) {
617
- const lx = (x1 + x2) / 2;
618
- const ly = (y1 + y2) / 2;
619
- out.push(
620
- `<rect x="${px(lx - e.label.length * 3.4 - 5)}" y="${px(ly - 9)}" width="${px(e.label.length * 6.8 + 10)}" height="18" rx="5" fill="#ffffff" stroke="#e2e8f0"/>`
621
- + `<text x="${px(lx)}" y="${px(ly + 4)}" font-size="11" fill="#475569" text-anchor="middle">${esc(e.label)}</text>`,
622
- );
623
- }
624
- }
625
-
626
- for (const [id, b] of L.boxes) {
627
- const s = styleFor(id, graph, b.rank);
628
- out.push(
629
- `<rect x="${px(b.x + padding)}" y="${px(b.y + padding)}" width="${px(b.w)}" height="${px(b.h)}" rx="${radiusFor(b)}" `
630
- + `fill="${esc(s.fill)}" stroke="${esc(s.stroke)}" stroke-width="1.5"/>`,
631
- );
632
- const startY = b.y + padding + PAD_Y + LINE_H - 5;
633
- b.lines.forEach((line, i) => {
634
- const style = (line.italic ? ' font-style="italic"' : '') + (line.bold ? ' font-weight="700"' : '');
635
- out.push(
636
- `<text x="${px(b.x + b.w / 2 + padding)}" y="${px(startY + i * LINE_H)}" font-size="13" `
637
- + `fill="${esc(s.color)}" text-anchor="middle"${style}>${esc(line.text)}</text>`,
638
- );
639
- });
640
- }
641
-
642
- out.push('</svg>');
643
- return out.join('');
644
- }
1
+ const H=[["[[","]]","rect"],["[(",")]","round"],["((","))","pill"],["([","])","pill"],["{{","}}","rect"],["[/","/]","rect"],["[\\","\\]","rect"],["[","]","rect"],["(",")","round"],["{","}","rect"],[">","]","rect"]],A={fill:"#f8fafc",stroke:"#cbd5e1",color:"#1e293b"},C=[{fill:"#1e293b",stroke:"#1e293b",color:"#ffffff"},{fill:"#e0e7ff",stroke:"#a5b4fc",color:"#312e81"},{fill:"#f1f5f9",stroke:"#cbd5e1",color:"#0f172a"}];function T(c){return String(c??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}const N=/<\/?(?:i|em|b|strong|u|code|span|small|sup|sub)\s*\/?>/gi,O=[[/^<(i|em)>([\s\S]*)<\/\1>$/i,"italic"],[/^<(b|strong)>([\s\S]*)<\/\1>$/i,"bold"]];function W(c){let s=String(c||"").trim(),g=!1,p=!1;for(let f=0;f<3;f++){let h=!1;for(const[l,m]of O){const u=l.exec(s);u&&(s=u[2].trim(),m==="italic"?g=!0:p=!0,h=!0)}if(!h)break}return{text:s.replace(N," ").trim(),italic:g,bold:p}}function Z(c,s=22){const g=String(c||"").split(/<br\s*\/?>/i),p=[];for(const f of g){const{text:h,italic:l,bold:m}=W(f),u=h.split(/\s+/).filter(Boolean);if(!u.length)continue;let t="";for(const o of u)t?(t+" "+o).length<=s?t+=" "+o:(p.push({text:t,italic:l,bold:m}),t=o):t=o;t&&p.push({text:t,italic:l,bold:m})}return p.length?p:[{text:"",italic:!1,bold:!1}]}function E(c){let s=String(c||"").trim();return(s.startsWith('"')&&s.endsWith('"')||s.startsWith("'")&&s.endsWith("'"))&&(s=s.slice(1,-1)),s.trim()}const I=/^(<?)([-=.]{2,})(?:[ \t]*([^-=<>|\n]{1,80}?)[ \t]*([-=.]{2,}))?([>ox]?)/;function U(c){const s=String(c||""),g=[],p=[],f=[];let h="",l=0,m="";for(let u=0;u<s.length;u++){const t=s[u];if(m){h+=t,t===m&&(m="");continue}if(t==='"'||t==="'"){m=t,h+=t;continue}if(t==="["||t==="("||t==="{"){l++,h+=t;continue}if(t==="]"||t===")"||t==="}"){l=Math.max(0,l-1),h+=t;continue}if(l===0&&(t==="-"||t==="="||t==="<")){const o=I.exec(s.slice(u));if(o&&(t!=="<"||o[1])){u+=o[0].length-1;let a=(o[3]||"").trim();const n=s.slice(u+1).match(/^\s*\|([^|]*)\|/);n&&(a=n[1],u+=n[0].length),g.push(h),p.push(a),f.push(o[1]==="<"),h="";continue}}h+=t}return g.push(h),g.length<2?null:{parts:g.map(u=>u.trim().replace(/;$/,"")).filter(Boolean),labels:p,bidir:f}}function G(c){const s=String(c||""),g=s.match(/^\s*(?:flowchart|graph)\s+(TB|TD|BT|LR|RL)\b/im);if(!g)return null;const p=g[1].toUpperCase(),f=new Map,h=[],l=new Map,m=new Map,u=[],t=[],o=new Map,a=x=>{const r=t[t.length-1];!r||o.has(x)||(o.set(x,r),r.members.push(x))},e=(x,r,$)=>{const i=String(x).trim();if(!i)return null;if(!f.has(i))f.set(i,{id:i,label:r??i,shape:$||"rect"});else{const d=f.get(i);r!=null&&(d.label=r),$&&(d.shape=$)}return a(i),f.get(i)};function n(x){const r=x.trim();if(!r)return null;const $=r.match(/^([A-Za-z0-9_.-]+)\s*([[({>])([\s\S]*)$/);if(!$)return e(r.replace(/^[[(]|[\])]$/g,""),null);const i=$[1],d=r.slice(i.length).trim();for(const[k,w,b]of H){if(!d.startsWith(k))continue;const y=d.slice(k.length),M=y.lastIndexOf(w);return e(i,E(M>=0?y.slice(0,M):y),b)}return e(i,null)}for(const x of s.split(`
2
+ `)){const r=x.trim();if(!r||/^%%/.test(r)||/^(?:flowchart|graph)\b/i.test(r)||/^direction\b/i.test(r))continue;const $=r.match(/^subgraph\b\s*(.*)$/i);if($){const w=$[1].trim(),b=w.match(/^([A-Za-z0-9_.-]+)\s*([[("])([\s\S]*)$/);let y=w||`sub${u.length+1}`,M=w;if(b){y=b[1];const L=b[2],_=L==="["?"]":L==="("?")":'"',R=b[3],D=R.lastIndexOf(_);M=E(D>=0?R.slice(0,D):R)}const S={id:y,title:E(M),members:[]};u.push(S),t.push(S);continue}if(/^end\b/i.test(r)){t.pop();continue}const i=r.match(/^classDef\s+([A-Za-z0-9_-]+)\s+(.+?);?$/i);if(i){const w={};for(const b of i[2].split(",")){const[y,M]=b.split(":").map(S=>(S||"").trim());y&&M&&(w[y.toLowerCase()]=M)}l.set(i[1],w);continue}const d=r.match(/^class\s+([A-Za-z0-9_,.\s-]+?)\s+([A-Za-z0-9_-]+)\s*;?$/i);if(d){for(const w of d[1].split(",").map(b=>b.trim()).filter(Boolean))m.set(w,d[2]);continue}if(/^(?:style|linkStyle|click)\b/i.test(r))continue;const k=U(r);if(k&&k.parts.length>1){let w=n(k.parts[0]);for(let b=1;b<k.parts.length;b++){const y=n(k.parts[b]);w&&y&&h.push({from:w.id,to:y.id,label:E(k.labels[b-1]||""),both:!!k.bidir[b-1]}),w=y}continue}if(/^[A-Za-z0-9_.-]+\s*[[({>]/.test(r)){n(r);continue}if(t.length&&/^[A-Za-z0-9_.-]+;?$/.test(r)){e(r.replace(/;$/,""),null);continue}}return f.size?{dir:p,nodes:f,edges:h,classDefs:l,nodeClass:m,groups:u.filter(x=>x.members.length)}:null}function q(c,s){const g=new Set(c),p=new Map,f=new Map;for(const t of g)p.set(t,[]),f.set(t,[]);for(const t of s)!g.has(t.from)||!g.has(t.to)||(p.get(t.to).push(t.from),f.get(t.from).push(t.to));const h=new Map,l=[...g].filter(t=>p.get(t).length===0),m=l.length?[...l]:[g.values().next().value];for(const t of m)h.set(t,0);let u=g.size*4;for(;m.length&&u-- >0;){const t=m.shift(),o=h.get(t)||0;for(const a of f.get(t)||[]){const e=o+1;(h.get(a)??-1)<e&&(h.set(a,e),m.push(a))}}for(const t of g)h.has(t)||h.set(t,0);return{rank:h,parents:p,children:f}}const Y=7.1,v=18,X=14,F=12,P=16,j=28;function K(c){const s=Math.max(...c.map(p=>p.text.length*(p.bold?1.07:1)))*Y+X*2,g=c.length*v+F*2;return{w:Math.max(72,Math.round(s)),h:Math.round(g)}}function z(c,s,g,{horizontal:p,gapMain:f=56,gapCross:h=22}={}){const{rank:l,parents:m}=q(c,s),u=new Map;for(const i of c){const d=l.get(i)||0;u.has(d)||u.set(d,[]),u.get(d).push(i)}const t=new Map,o=[...u.keys()].sort((i,d)=>i-d);for(const i of o){const d=u.get(i);if(i===o[0]){d.forEach((k,w)=>t.set(k,w));continue}d.sort((k,w)=>{const b=m.get(k).map(L=>t.get(L)??0),y=m.get(w).map(L=>t.get(L)??0),M=b.length?b.reduce((L,_)=>L+_,0)/b.length:0,S=y.length?y.reduce((L,_)=>L+_,0)/y.length:0;return M-S}),d.forEach((k,w)=>t.set(k,w))}const a=new Map;for(const i of o){const d=u.get(i),k=d.reduce((w,b)=>w+(p?g(b).h:g(b).w),0)+h*Math.max(0,d.length-1);a.set(i,k)}const e=Math.max(...a.values(),1),n=new Map;let x=0;for(const i of o){n.set(i,x);const d=Math.max(...u.get(i).map(k=>p?g(k).w:g(k).h));x+=d+f}const r=Math.max(0,x-f),$=new Map;for(const i of o){let d=(e-a.get(i))/2;for(const k of u.get(i)){const w=g(k);p?($.set(k,{x:n.get(i),y:d}),d+=w.h+h):($.set(k,{x:d,y:n.get(i)}),d+=w.w+h)}}return{pos:$,rank:l,width:p?r:e,height:p?e:r}}function B(c,s){const g=new Map;for(const p of s){const f=c.nodes.get(p),h=Z(f.label),{w:l,h:m}=K(h);g.set(p,{id:p,lines:h,w:l,h:m,shape:f.shape||"rect",rank:0})}return g}function J(c,{dir:s=c.dir,gapMain:g=56,gapCross:p=22}={}){const f=s==="LR"||s==="RL",h=[...c.nodes.keys()],l=B(c,h),m=z(h,c.edges,u=>l.get(u),{horizontal:f,gapMain:g,gapCross:p});for(const[u,t]of l){const o=m.pos.get(u);t.x=o.x,t.y=o.y,t.rank=m.rank.get(u)||0}return{boxes:l,edges:c.edges,dir:s,horizontal:f,clusters:[],width:m.width,height:m.height}}function Q(c,{dir:s=c.dir,gapMain:g=56,gapCross:p=22}={}){const f=s==="LR"||s==="RL",h=B(c,[...c.nodes.keys()]),l=new Map,m=new Map;c.groups.forEach((e,n)=>{const x=`g${n}`;m.set(x,{key:x,title:e.title||e.id,group:!0,ids:[]});for(const r of e.members)c.nodes.has(r)&&!l.has(r)&&l.set(r,x)});for(const e of c.nodes.keys()){const n=l.get(e)||`n:${e}`;m.has(n)||m.set(n,{key:n,title:"",group:!1,ids:[]}),l.set(e,n),m.get(n).ids.push(e)}for(const e of m.values()){const n=z(e.ids,c.edges,x=>h.get(x),{horizontal:f,gapMain:g,gapCross:p});e.inner=n,e.padL=e.group?P:0,e.padT=e.group?j:0,e.w=n.width+e.padL*2,e.h=n.height+e.padT+(e.group?P:0)}const u=new Set,t=[];for(const e of c.edges){const n=l.get(e.from),x=l.get(e.to);if(!n||!x||n===x)continue;const r=`${n} ${x}`;u.has(r)||(u.add(r),t.push({from:n,to:x}))}const o=[...m.keys()],a=z(o,t,e=>m.get(e),{horizontal:f,gapMain:g+14,gapCross:p+16});for(const e of o){const n=m.get(e),x=a.pos.get(e);n.x=x.x,n.y=x.y;const r=a.rank.get(e)||0;for(const $ of n.ids){const i=h.get($),d=n.inner.pos.get($);i.x=n.x+n.padL+d.x,i.y=n.y+n.padT+d.y,i.rank=r+(n.inner.rank.get($)||0)}}return{boxes:h,edges:c.edges,dir:s,horizontal:f,clusters:[...m.values()].filter(e=>e.group),width:a.width,height:a.height}}function V(c,s={}){return c.groups?.length?Q(c,s):J(c,s)}function tt(c,s,g){const p=s.nodeClass.get(c),f=p?s.classDefs.get(p):null;return f?{fill:f.fill||A.fill,stroke:(f.stroke||f["stroke-width"])&&f.stroke||A.stroke,color:f.color||A.color}:s.classDefs.size?A:C[Math.min(g,C.length-1)]}function et(c){return c.shape==="pill"?Math.round(Math.min(c.h,c.w)/2):c.shape==="round"?16:9}function st(c,{padding:s=18,maxWidth:g=1400,autoFlip:p=!0}={}){const f=G(c);if(!f)return null;const h=a=>V(f,a);let l=h({});if(!l.boxes.size)return null;if(p){const a=l.width/Math.max(1,l.height);a>2.2&&!l.horizontal?l=h({dir:"LR"}):a<.25&&l.horizontal&&(l=h({dir:"TB"}))}const m=Math.min(g,Math.ceil(l.width+s*2)),u=Math.ceil(l.height+s*2),t=[];t.push(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${Math.ceil(l.width+s*2)} ${u}" width="${m}" height="${u}" font-family="system-ui,-apple-system,Segoe UI,Roboto,sans-serif">`),t.push('<defs><marker id="a" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#94a3b8"/></marker></defs>'),t.push('<rect width="100%" height="100%" fill="#ffffff" rx="10"/>');const o=a=>Math.round(a*10)/10;for(const a of l.clusters)t.push(`<rect x="${o(a.x+s)}" y="${o(a.y+s)}" width="${o(a.w)}" height="${o(a.h)}" rx="14" fill="#f8fafc" stroke="#cbd5e1" stroke-width="1.5" stroke-dasharray="5 4"/>`),a.title&&t.push(`<text x="${o(a.x+s+14)}" y="${o(a.y+s+19)}" font-size="12" font-weight="700" fill="#475569">${T(a.title.slice(0,48))}</text>`);for(const a of l.edges){const e=l.boxes.get(a.from),n=l.boxes.get(a.to);if(!e||!n)continue;let x,r,$,i,d;if(l.horizontal){const w=n.x+n.w<e.x;x=w?e.x+s:e.x+e.w+s,r=e.y+e.h/2+s,$=w?n.x+n.w+s:n.x+s,i=n.y+n.h/2+s;const b=(x+$)/2;d=`M${o(x)},${o(r)} C${o(b)},${o(r)} ${o(b)},${o(i)} ${o($)},${o(i)}`}else{const w=n.y+n.h<e.y;x=e.x+e.w/2+s,r=w?e.y+s:e.y+e.h+s,$=n.x+n.w/2+s,i=w?n.y+n.h+s:n.y+s;const b=(r+i)/2;d=`M${o(x)},${o(r)} C${o(x)},${o(b)} ${o($)},${o(b)} ${o($)},${o(i)}`}const k=a.both?' marker-start="url(#a)"':"";if(t.push(`<path d="${d}" fill="none" stroke="#94a3b8" stroke-width="1.5"${k} marker-end="url(#a)"/>`),a.label){const w=(x+$)/2,b=(r+i)/2;t.push(`<rect x="${o(w-a.label.length*3.4-5)}" y="${o(b-9)}" width="${o(a.label.length*6.8+10)}" height="18" rx="5" fill="#ffffff" stroke="#e2e8f0"/><text x="${o(w)}" y="${o(b+4)}" font-size="11" fill="#475569" text-anchor="middle">${T(a.label)}</text>`)}}for(const[a,e]of l.boxes){const n=tt(a,f,e.rank);t.push(`<rect x="${o(e.x+s)}" y="${o(e.y+s)}" width="${o(e.w)}" height="${o(e.h)}" rx="${et(e)}" fill="${T(n.fill)}" stroke="${T(n.stroke)}" stroke-width="1.5"/>`);const x=e.y+s+F+v-5;e.lines.forEach((r,$)=>{const i=(r.italic?' font-style="italic"':"")+(r.bold?' font-weight="700"':"");t.push(`<text x="${o(e.x+e.w/2+s)}" y="${o(x+$*v)}" font-size="13" fill="${T(n.color)}" text-anchor="middle"${i}>${T(r.text)}</text>`)})}return t.push("</svg>"),t.join("")}export{V as layoutFlowchart,G as parseFlowchart,st as renderFlowchartSvg,U as splitEdgeChain,Z as wrapLabel};