agentlas 1.0.42 → 1.0.44

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 (70) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/engine/agentlas-workforce.cjs +1 -1
  3. package/engine/hephaestus/runtime.cjs +1 -1
  4. package/engine/storm/storm.cjs +1 -1
  5. package/engine/storm/swarm.cjs +1 -1
  6. package/engine/ui/palette.cjs +1 -0
  7. package/engine/ui/repl.cjs +34 -4
  8. package/engine/ui/shell.cjs +22 -3
  9. package/engine/vendor/mermaid/LICENSE +205 -0
  10. package/engine/vendor/mermaid/ansi.js +23 -0
  11. package/engine/vendor/mermaid/canvas.js +366 -0
  12. package/engine/vendor/mermaid/graph.js +91 -0
  13. package/engine/vendor/mermaid/index.js +100 -0
  14. package/engine/vendor/mermaid/labels.js +324 -0
  15. package/engine/vendor/mermaid/layout-seq.js +194 -0
  16. package/engine/vendor/mermaid/layout.js +881 -0
  17. package/engine/vendor/mermaid/package.json +1 -0
  18. package/engine/vendor/mermaid/parse.js +1108 -0
  19. package/engine/vendor/mermaid/source-box.js +78 -0
  20. package/engine/vendor/mermaid/types.js +1 -0
  21. package/engine/vendor/mermaid/width-data.js +994 -0
  22. package/engine/vendor/mermaid/width.js +76 -0
  23. package/engine/vendor/tui/LICENSE +20 -0
  24. package/engine/vendor/tui/autocomplete.js +632 -0
  25. package/engine/vendor/tui/components/alt-screen-flash.js +37 -0
  26. package/engine/vendor/tui/components/box.js +104 -0
  27. package/engine/vendor/tui/components/cancellable-loader.js +35 -0
  28. package/engine/vendor/tui/components/editor.js +1961 -0
  29. package/engine/vendor/tui/components/h-stack.js +43 -0
  30. package/engine/vendor/tui/components/image.js +90 -0
  31. package/engine/vendor/tui/components/input.js +378 -0
  32. package/engine/vendor/tui/components/loader.js +69 -0
  33. package/engine/vendor/tui/components/markdown.js +806 -0
  34. package/engine/vendor/tui/components/scroll-view.js +173 -0
  35. package/engine/vendor/tui/components/select-list.js +159 -0
  36. package/engine/vendor/tui/components/settings-list.js +182 -0
  37. package/engine/vendor/tui/components/spacer.js +23 -0
  38. package/engine/vendor/tui/components/stack.js +111 -0
  39. package/engine/vendor/tui/components/text.js +89 -0
  40. package/engine/vendor/tui/components/truncated-text.js +51 -0
  41. package/engine/vendor/tui/components/v-stack.js +26 -0
  42. package/engine/vendor/tui/deps/east-asian-width/LICENSE +9 -0
  43. package/engine/vendor/tui/deps/east-asian-width/index.js +30 -0
  44. package/engine/vendor/tui/deps/east-asian-width/lookup-data.js +21 -0
  45. package/engine/vendor/tui/deps/east-asian-width/lookup.js +138 -0
  46. package/engine/vendor/tui/deps/east-asian-width/utilities.js +24 -0
  47. package/engine/vendor/tui/deps/marked/LICENSE +44 -0
  48. package/engine/vendor/tui/deps/marked/index.js +77 -0
  49. package/engine/vendor/tui/editor-component.js +2 -0
  50. package/engine/vendor/tui/fuzzy.js +110 -0
  51. package/engine/vendor/tui/index.js +42 -0
  52. package/engine/vendor/tui/keybindings.js +209 -0
  53. package/engine/vendor/tui/keys.js +1174 -0
  54. package/engine/vendor/tui/kill-ring.js +44 -0
  55. package/engine/vendor/tui/latex.js +1264 -0
  56. package/engine/vendor/tui/layout-node.js +6 -0
  57. package/engine/vendor/tui/layout.js +314 -0
  58. package/engine/vendor/tui/native-modifiers.js +60 -0
  59. package/engine/vendor/tui/package.json +1 -0
  60. package/engine/vendor/tui/stdin-buffer.js +361 -0
  61. package/engine/vendor/tui/terminal-colors.js +59 -0
  62. package/engine/vendor/tui/terminal-image.js +518 -0
  63. package/engine/vendor/tui/terminal.js +436 -0
  64. package/engine/vendor/tui/tui-alt-screen.js +902 -0
  65. package/engine/vendor/tui/tui-main-screen.js +533 -0
  66. package/engine/vendor/tui/tui.js +937 -0
  67. package/engine/vendor/tui/undo-stack.js +25 -0
  68. package/engine/vendor/tui/utils.js +1191 -0
  69. package/engine/vendor/tui/word-navigation.js +96 -0
  70. package/package.json +2 -6
@@ -0,0 +1,881 @@
1
+ /**
2
+ * Graph layout: rank, order, place, route, draw.
3
+ *
4
+ * Follows the Sugiyama outline — assign ranks along the flow axis, reorder
5
+ * within ranks to cut crossings, then relax positions on the cross axis so
6
+ * chains stay straight. Edges between adjacent ranks share horizontal "bus"
7
+ * rows; everything else is routed around the diagram through vertical "lanes".
8
+ *
9
+ * `BT` and `RL` reuse the `TD`/`LR` layouts and flip the finished canvas, so
10
+ * text never ends up mirrored.
11
+ */
12
+ import { Canvas, CONT, D, drawText, drawTextOverEdges, L, R, STY_DOT, STY_SOLID, STY_THICK, U, } from './canvas.js';
13
+ import { Graph } from './graph.js';
14
+ import { fitLabel, MAX_LABEL, MAX_LINES, WRAP_WIDTH, wrapLabel } from './labels.js';
15
+ import { measured, stringWidth } from './width.js';
16
+ /** Cells of padding between a box border and its text. */
17
+ const PAD = 1;
18
+ /** Minimum horizontal / vertical space between boxes. */
19
+ const GAP_X = 3;
20
+ const GAP_Y = 2;
21
+ /** Refuse to allocate a canvas larger than this many cells. */
22
+ const MAX_CANVAS_CELLS = 1 << 21;
23
+ /** Saturating subtraction; Rust's `usize` arithmetic never goes negative. */
24
+ const sat = (a, b) => Math.max(0, a - b);
25
+ const half = (n) => Math.floor(n / 2);
26
+ // ------------------------------------------------------------------ ranking
27
+ /**
28
+ * Longest-path ranking over the graph's DAG.
29
+ *
30
+ * Back edges (those closing a cycle) are excluded by a DFS colouring pass, so
31
+ * `A --> B --> C --> A` still ranks 0, 1, 2 rather than diverging.
32
+ */
33
+ export function computeRanks(graph) {
34
+ const n = graph.nodes.length;
35
+ const children = Array.from({ length: n }, () => []);
36
+ const indeg = new Array(n).fill(0);
37
+ for (const e of graph.edges) {
38
+ if (e.from !== e.to) {
39
+ children[e.from].push(e.to);
40
+ indeg[e.to]++;
41
+ }
42
+ }
43
+ const color = new Uint8Array(n);
44
+ const dag = Array.from({ length: n }, () => []);
45
+ const order = [];
46
+ // Roots first so ranks grow from natural entry points, then any leftovers.
47
+ const roots = [...Array(n).keys()].filter((i) => indeg[i] === 0);
48
+ for (const start of [...roots, ...Array(n).keys()]) {
49
+ if (color[start] === 0)
50
+ dfsDag(start, children, color, dag, order);
51
+ }
52
+ const rank = new Array(n).fill(0);
53
+ for (let i = order.length - 1; i >= 0; i--) {
54
+ const u = order[i];
55
+ for (const v of dag[u])
56
+ rank[v] = Math.max(rank[v], rank[u] + 1);
57
+ }
58
+ return rank;
59
+ }
60
+ /** Iterative DFS recording postorder and skipping edges back into the stack. */
61
+ function dfsDag(start, children, color, dag, order) {
62
+ const stack = [{ u: start, i: 0 }];
63
+ color[start] = 1;
64
+ while (stack.length > 0) {
65
+ const frame = stack[stack.length - 1];
66
+ const u = frame.u;
67
+ if (frame.i < children[u].length) {
68
+ const v = children[u][frame.i];
69
+ frame.i++;
70
+ if (color[v] === 1)
71
+ continue; // grey: a back edge, ignore it
72
+ dag[u].push(v);
73
+ if (color[v] === 0) {
74
+ color[v] = 1;
75
+ stack.push({ u: v, i: 0 });
76
+ }
77
+ }
78
+ else {
79
+ color[u] = 2;
80
+ order.push(u);
81
+ stack.pop();
82
+ }
83
+ }
84
+ }
85
+ /**
86
+ * Reorder nodes within each rank to minimise edge crossings (barycenter
87
+ * sweeps): alternate down/up passes sort each rank by the mean position of its
88
+ * neighbours, keeping whichever ordering crossed least.
89
+ */
90
+ export function orderRanks(byRank, edges, ranks) {
91
+ const n = ranks.length;
92
+ if (byRank.length < 2 || n < 3)
93
+ return;
94
+ const parents = Array.from({ length: n }, () => []);
95
+ const children = Array.from({ length: n }, () => []);
96
+ for (const e of edges) {
97
+ if (e.from !== e.to && ranks[e.to] > ranks[e.from]) {
98
+ parents[e.to].push(e.from);
99
+ children[e.from].push(e.to);
100
+ }
101
+ }
102
+ const pos = new Array(n).fill(0);
103
+ const reindex = (row) => {
104
+ for (let i = 0; i < row.length; i++)
105
+ pos[row[i]] = i;
106
+ };
107
+ for (const row of byRank)
108
+ reindex(row);
109
+ let best = byRank.map((row) => [...row]);
110
+ let bestCrossings = countCrossings(edges, ranks, pos);
111
+ if (bestCrossings === 0)
112
+ return;
113
+ for (let it = 0; it < 8; it++) {
114
+ // Alternate sweeping down (sort by parents) and up (sort by children).
115
+ const rows = it % 2 === 0 ? byRank.slice(1) : byRank.slice(0, -1).reverse();
116
+ const neigh = it % 2 === 0 ? parents : children;
117
+ for (const row of rows) {
118
+ sortByBarycenter(row, neigh, pos);
119
+ reindex(row);
120
+ }
121
+ const crossings = countCrossings(edges, ranks, pos);
122
+ if (crossings < bestCrossings) {
123
+ bestCrossings = crossings;
124
+ best = byRank.map((row) => [...row]);
125
+ }
126
+ if (bestCrossings === 0)
127
+ break;
128
+ }
129
+ for (let i = 0; i < byRank.length; i++)
130
+ byRank[i].splice(0, byRank[i].length, ...best[i]);
131
+ }
132
+ function sortByBarycenter(row, neigh, pos) {
133
+ const keyed = row.map((v) => ({
134
+ key: neigh[v].length === 0 ? pos[v] : neigh[v].reduce((s, u) => s + pos[u], 0) / neigh[v].length,
135
+ v,
136
+ }));
137
+ keyed.sort((a, b) => a.key - b.key);
138
+ for (let i = 0; i < keyed.length; i++)
139
+ row[i] = keyed[i].v;
140
+ }
141
+ export function countCrossings(edges, ranks, pos) {
142
+ const adjacent = edges
143
+ .filter((e) => e.from !== e.to && ranks[e.to] === ranks[e.from] + 1)
144
+ .map((e) => [ranks[e.from], pos[e.from], pos[e.to]]);
145
+ let crossings = 0;
146
+ for (let i = 0; i < adjacent.length; i++) {
147
+ const a = adjacent[i];
148
+ for (let j = i + 1; j < adjacent.length; j++) {
149
+ const b = adjacent[j];
150
+ if (a[0] === b[0] && ((a[1] < b[1] && a[2] > b[2]) || (a[1] > b[1] && a[2] < b[2]))) {
151
+ crossings++;
152
+ }
153
+ }
154
+ }
155
+ return crossings;
156
+ }
157
+ /**
158
+ * Assign a cross-axis centre to every node so nodes line up under their
159
+ * neighbours: each node drifts toward the average of its neighbours while
160
+ * ranks keep their order and boxes keep `sep` between them.
161
+ */
162
+ export function assignPositions(byRank, size, sep, edges, ranks) {
163
+ const n = size.length;
164
+ const parents = Array.from({ length: n }, () => []);
165
+ const children = Array.from({ length: n }, () => []);
166
+ for (const e of edges) {
167
+ if (e.from !== e.to && ranks[e.to] > ranks[e.from]) {
168
+ parents[e.to].push(e.from);
169
+ children[e.from].push(e.to);
170
+ }
171
+ }
172
+ const pos = new Array(n).fill(0);
173
+ for (const row of byRank) {
174
+ let x = 0;
175
+ for (const v of row) {
176
+ const h = size[v] / 2;
177
+ x += h;
178
+ pos[v] = x;
179
+ x += h + sep;
180
+ }
181
+ }
182
+ for (let it = 0; it < 10; it++) {
183
+ const rows = it % 2 === 0 ? byRank : [...byRank].reverse();
184
+ const neigh = it % 2 === 0 ? parents : children;
185
+ for (const row of rows)
186
+ relaxRank(row, neigh, pos, size, sep);
187
+ }
188
+ let minLeft = Number.POSITIVE_INFINITY;
189
+ for (let v = 0; v < n; v++)
190
+ minLeft = Math.min(minLeft, pos[v] - size[v] / 2);
191
+ if (!Number.isFinite(minLeft))
192
+ minLeft = 0;
193
+ return Array.from({ length: n }, (_, v) => Math.max(0, Math.round(pos[v] - minLeft)));
194
+ }
195
+ function relaxRank(nodes, neigh, pos, size, sep) {
196
+ const n = nodes.length;
197
+ if (n === 0)
198
+ return;
199
+ const desired = nodes.map((v) => neigh[v].length === 0 ? pos[v] : neigh[v].reduce((s, u) => s + pos[u], 0) / neigh[v].length);
200
+ const halfOf = (i) => size[nodes[i]] / 2;
201
+ // Sweep right then left, then take the midpoint: this centres a node between
202
+ // the tightest packing that respects order from either side.
203
+ const left = new Array(n);
204
+ for (let i = 0; i < n; i++) {
205
+ left[i] =
206
+ i === 0 ? desired[i] : Math.max(desired[i], left[i - 1] + halfOf(i - 1) + sep + halfOf(i));
207
+ }
208
+ const right = new Array(n);
209
+ for (let i = n - 1; i >= 0; i--) {
210
+ right[i] =
211
+ i === n - 1
212
+ ? desired[i]
213
+ : Math.min(desired[i], right[i + 1] - halfOf(i + 1) - sep - halfOf(i));
214
+ }
215
+ for (let i = 0; i < n; i++)
216
+ pos[nodes[i]] = (left[i] + right[i]) / 2;
217
+ for (let i = 1; i < n; i++) {
218
+ const minP = pos[nodes[i - 1]] + halfOf(i - 1) + sep + halfOf(i);
219
+ if (pos[nodes[i]] < minP)
220
+ pos[nodes[i]] = minP;
221
+ }
222
+ }
223
+ /**
224
+ * Pack spans into as few parallel tracks as possible.
225
+ *
226
+ * Two spans share a track when they are two cells apart, or when they share an
227
+ * endpoint — edges fanning out of one node deliberately reuse a single row so
228
+ * a merge draws one arrowhead rather than a stack of them.
229
+ */
230
+ export function assignTracks(spans) {
231
+ const sorted = [...spans].sort((a, b) => {
232
+ for (let i = 0; i < 5; i++)
233
+ if (a[i] !== b[i])
234
+ return a[i] - b[i];
235
+ return 0;
236
+ });
237
+ const tracks = [];
238
+ const assigned = [];
239
+ for (const [s, e, f, t, idx] of sorted) {
240
+ let slot = tracks.findIndex((members) => members.every(([s2, e2, f2, t2]) => e2 + 2 <= s || e + 2 <= s2 || f2 === f || t2 === t));
241
+ if (slot === -1) {
242
+ tracks.push([]);
243
+ slot = tracks.length - 1;
244
+ }
245
+ tracks[slot].push([s, e, f, t]);
246
+ assigned.push([idx, slot]);
247
+ }
248
+ return { assigned, count: tracks.length };
249
+ }
250
+ /** Edges from rank `r` to `r + 1` that must jog sideways, so need a bus row. */
251
+ function busSpans(graph, ranks, centers, r, exact) {
252
+ const out = [];
253
+ graph.edges.forEach((e, i) => {
254
+ const jogs = exact
255
+ ? centers[e.from] !== centers[e.to]
256
+ : Math.abs(centers[e.from] - centers[e.to]) > 1;
257
+ if (e.from !== e.to && ranks[e.from] === r && ranks[e.to] === r + 1 && jogs) {
258
+ out.push([
259
+ Math.min(centers[e.from], centers[e.to]),
260
+ Math.max(centers[e.from], centers[e.to]),
261
+ e.from,
262
+ e.to,
263
+ i,
264
+ ]);
265
+ }
266
+ });
267
+ return out;
268
+ }
269
+ /** Edges skipping a rank or running backwards; these go around in a lane. */
270
+ function laneSpans(graph, ranks, placed, vertical) {
271
+ const out = [];
272
+ graph.edges.forEach((e, i) => {
273
+ if (e.from === e.to || ranks[e.to] === ranks[e.from] + 1)
274
+ return;
275
+ const pf = placed[e.from];
276
+ const pt = placed[e.to];
277
+ const a = vertical ? Math.min(pf.cy, pt.cy) : Math.min(pf.cx, pt.cx);
278
+ const b = vertical ? Math.max(pf.cy, pt.cy) : Math.max(pf.cx, pt.cx);
279
+ out.push([a, b, e.from, e.to, i]);
280
+ });
281
+ return out;
282
+ }
283
+ // ----------------------------------------------------------------- placement
284
+ function placeTd(ranks, maxRank, byRank, sizes, graph, placed) {
285
+ const centers = assignPositions(byRank, sizes.layW, GAP_X, graph.edges, ranks);
286
+ const edgeBus = new Array(graph.edges.length).fill(0);
287
+ const busTracks = new Array(maxRank + 1).fill(0);
288
+ for (let r = 0; r < maxRank; r++) {
289
+ const spans = busSpans(graph, ranks, centers, r, false);
290
+ if (spans.length === 0)
291
+ continue;
292
+ const { assigned, count } = assignTracks(spans);
293
+ for (const [idx, slot] of assigned)
294
+ edgeBus[idx] = slot;
295
+ busTracks[r] = count;
296
+ }
297
+ const rankH = byRank.map((row) => row.length === 0 ? 3 : Math.max(...row.map((i) => sizes.boxH[i] + sizes.extraH[i])));
298
+ const rankY = new Array(maxRank + 1).fill(0);
299
+ for (let r = 1; r <= maxRank; r++) {
300
+ rankY[r] = rankY[r - 1] + rankH[r - 1] + Math.max(GAP_Y, busTracks[r - 1] + 1);
301
+ }
302
+ const canvasH = rankY[maxRank] + rankH[maxRank];
303
+ const bandEnd = Array.from({ length: maxRank + 1 }, (_, r) => rankY[r] + rankH[r]);
304
+ let diagramW = 1;
305
+ byRank.forEach((row, r) => {
306
+ for (const idx of row) {
307
+ const w = sizes.boxW[idx];
308
+ const h = sizes.boxH[idx];
309
+ const cx = centers[idx];
310
+ const x = sat(cx, half(w));
311
+ const y = rankY[r] + half(rankH[r] - h - sizes.extraH[idx]);
312
+ placed[idx] = { x, y, w, h, cx, cy: y + half(h), rank: r };
313
+ diagramW = Math.max(diagramW, x + w);
314
+ if (sizes.extraH[idx] > 0 && sizes.selfLabelW[idx] > 0) {
315
+ diagramW = Math.max(diagramW, x + w + 2 + sizes.selfLabelW[idx]);
316
+ }
317
+ }
318
+ });
319
+ let contentW = diagramW;
320
+ for (const e of graph.edges) {
321
+ if (e.from === e.to || e.label === null)
322
+ continue;
323
+ const lw = Math.min(stringWidth(e.label), MAX_LABEL);
324
+ contentW =
325
+ ranks[e.to] === ranks[e.from] + 1
326
+ ? Math.max(contentW, placed[e.to].cx + 2 + lw)
327
+ : Math.max(contentW, diagramW + lw + 1);
328
+ }
329
+ const edgeLane = new Array(graph.edges.length).fill(0);
330
+ const lanes = laneSpans(graph, ranks, placed, true);
331
+ let canvasW = contentW;
332
+ let laneBase = 0;
333
+ if (lanes.length > 0) {
334
+ const { assigned, count } = assignTracks(lanes);
335
+ for (const [idx, slot] of assigned)
336
+ edgeLane[idx] = slot;
337
+ canvasW = contentW + 1 + count;
338
+ laneBase = contentW + 1;
339
+ }
340
+ return { canvasW, canvasH, bandEnd, edgeBus, laneBase, edgeLane };
341
+ }
342
+ function placeLr(ranks, maxRank, byRank, sizes, graph, placed) {
343
+ const colW = byRank.map((row) => row.length === 0 ? 0 : Math.max(...row.map((i) => sizes.boxW[i])));
344
+ // Left-to-right edge labels sit in the gap between columns, so the gap has
345
+ // to be wide enough for the widest of them.
346
+ const labelWidths = graph.edges
347
+ .filter((e) => e.from === e.to || ranks[e.to] === ranks[e.from] + 1)
348
+ .filter((e) => e.label !== null)
349
+ .map((e) => Math.min(stringWidth(e.label), MAX_LABEL));
350
+ const maxLabel = labelWidths.length === 0 ? 0 : Math.max(...labelWidths);
351
+ const baseGap = Math.max(GAP_X + 1, maxLabel + 3);
352
+ const centers = assignPositions(byRank, sizes.layH, 1, graph.edges, ranks);
353
+ const edgeBus = new Array(graph.edges.length).fill(0);
354
+ const busTracks = new Array(maxRank + 1).fill(0);
355
+ for (let r = 0; r < maxRank; r++) {
356
+ const spans = busSpans(graph, ranks, centers, r, true);
357
+ if (spans.length === 0)
358
+ continue;
359
+ const { assigned, count } = assignTracks(spans);
360
+ for (const [idx, slot] of assigned)
361
+ edgeBus[idx] = slot;
362
+ busTracks[r] = count;
363
+ }
364
+ const rankX = new Array(maxRank + 1).fill(0);
365
+ for (let r = 1; r <= maxRank; r++) {
366
+ rankX[r] = rankX[r - 1] + colW[r - 1] + Math.max(baseGap, busTracks[r - 1] + 1);
367
+ }
368
+ const selfTails = byRank[maxRank]
369
+ .filter((i) => sizes.extraH[i] > 0 && sizes.selfLabelW[i] > 0)
370
+ .map((i) => 2 + sizes.selfLabelW[i]);
371
+ const canvasW = rankX[maxRank] + colW[maxRank] + (selfTails.length === 0 ? 0 : Math.max(...selfTails));
372
+ const bandEnd = Array.from({ length: maxRank + 1 }, (_, r) => rankX[r] + colW[r]);
373
+ let diagramH = 1;
374
+ byRank.forEach((row, r) => {
375
+ const x = rankX[r];
376
+ for (const idx of row) {
377
+ const w = sizes.boxW[idx];
378
+ const h = sizes.boxH[idx];
379
+ const cy = centers[idx];
380
+ const y = sat(cy, half(h + sizes.extraH[idx]));
381
+ placed[idx] = { x, y, w, h, cx: x + half(w), cy: y + half(h), rank: r };
382
+ diagramH = Math.max(diagramH, y + h + sizes.extraH[idx]);
383
+ }
384
+ });
385
+ const edgeLane = new Array(graph.edges.length).fill(0);
386
+ const lanes = laneSpans(graph, ranks, placed, false);
387
+ let canvasH = diagramH;
388
+ let laneBase = 0;
389
+ if (lanes.length > 0) {
390
+ const { assigned, count } = assignTracks(lanes);
391
+ for (const [idx, slot] of assigned)
392
+ edgeLane[idx] = slot;
393
+ canvasH = diagramH + 1 + count;
394
+ laneBase = diagramH + 1;
395
+ }
396
+ return { canvasW, canvasH, bandEnd, edgeBus, laneBase, edgeLane };
397
+ }
398
+ // -------------------------------------------------------------------- canvas
399
+ /** Rank, place, draw and route a graph onto a fresh canvas. */
400
+ export function layoutCanvas(graph, extras) {
401
+ const n = graph.nodes.length;
402
+ if (n === 0)
403
+ return null;
404
+ const ranks = computeRanks(graph);
405
+ const maxRank = Math.max(...ranks, 0);
406
+ const byRank = Array.from({ length: maxRank + 1 }, () => []);
407
+ for (let idx = 0; idx < ranks.length; idx++)
408
+ byRank[ranks[idx]].push(idx);
409
+ orderRanks(byRank, graph.edges, ranks);
410
+ const wrapped = graph.nodes.map((node) => wrapLabel(node.label, WRAP_WIDTH, MAX_LINES));
411
+ const widest = (lines) => Math.max(1, lines.length === 0 ? 1 : Math.max(...lines.map(stringWidth)));
412
+ const boxW = extras.map((extra, i) => {
413
+ if (extra.kind === 'frame') {
414
+ return Math.max(extra.sub.w + 2, stringWidth(fitLabel(graph.nodes[i].label, WRAP_WIDTH)) + 4);
415
+ }
416
+ if (extra.kind === 'compartments')
417
+ return widest(extra.sections.flat()) + 2 * PAD + 2;
418
+ return widest(wrapped[i]) + 2 * PAD + 2;
419
+ });
420
+ const boxH = extras.map((extra, i) => {
421
+ if (extra.kind === 'frame')
422
+ return extra.sub.h + 2;
423
+ if (extra.kind === 'compartments') {
424
+ const filled = extra.sections.filter((s) => s.length > 0).length;
425
+ return extra.sections.reduce((s, sec) => s + sec.length, 0) + sat(filled, 1) + 2;
426
+ }
427
+ return wrapped[i].length + 2;
428
+ });
429
+ // A self-edge needs two rows below its box, and room beside it for a label.
430
+ const extraH = new Array(n).fill(0);
431
+ const selfLabelW = new Array(n).fill(0);
432
+ for (const e of graph.edges) {
433
+ if (e.from !== e.to)
434
+ continue;
435
+ extraH[e.from] = 2;
436
+ if (e.label !== null) {
437
+ selfLabelW[e.from] = Math.max(selfLabelW[e.from], Math.min(stringWidth(e.label), MAX_LABEL));
438
+ }
439
+ }
440
+ for (let i = 0; i < n; i++)
441
+ if (extraH[i] > 0)
442
+ boxW[i] = Math.max(boxW[i], 7);
443
+ const sizes = {
444
+ boxW,
445
+ boxH,
446
+ layW: boxW.map((w, i) => w + (selfLabelW[i] > 0 ? 2 * (selfLabelW[i] + 3) : 0)),
447
+ layH: boxH.map((h, i) => h + extraH[i]),
448
+ extraH,
449
+ selfLabelW,
450
+ };
451
+ const placed = Array.from({ length: n }, () => ({
452
+ x: 0,
453
+ y: 0,
454
+ w: 0,
455
+ h: 0,
456
+ cx: 0,
457
+ cy: 0,
458
+ rank: 0,
459
+ }));
460
+ const vertical = graph.dir === 'down' || graph.dir === 'up';
461
+ const plan = vertical
462
+ ? placeTd(ranks, maxRank, byRank, sizes, graph, placed)
463
+ : placeLr(ranks, maxRank, byRank, sizes, graph, placed);
464
+ if (plan.canvasW * plan.canvasH > MAX_CANVAS_CELLS)
465
+ return null;
466
+ const canvas = new Canvas(plan.canvasW, plan.canvasH);
467
+ for (let idx = 0; idx < n; idx++) {
468
+ const extra = extras[idx];
469
+ if (extra.kind === 'frame')
470
+ drawFrame(canvas, placed[idx], graph.nodes[idx].label, extra.sub);
471
+ else if (extra.kind === 'compartments')
472
+ drawClassBox(canvas, placed[idx], extra.sections);
473
+ else
474
+ drawBox(canvas, placed[idx], wrapped[idx], graph.nodes[idx].shape);
475
+ }
476
+ graph.edges.forEach((edge, i) => {
477
+ canvas.curStyle =
478
+ edge.line === 'dotted' ? STY_DOT : edge.line === 'thick' ? STY_THICK : STY_SOLID;
479
+ if (edge.from === edge.to) {
480
+ routeSelf(canvas, placed[edge.from], edge);
481
+ return;
482
+ }
483
+ const from = placed[edge.from];
484
+ const to = placed[edge.to];
485
+ const adjacent = to.rank === from.rank + 1;
486
+ const bus = plan.bandEnd[from.rank] + plan.edgeBus[i];
487
+ const lane = plan.laneBase + plan.edgeLane[i];
488
+ if (vertical) {
489
+ if (adjacent)
490
+ routeForward(canvas, from, to, edge, bus);
491
+ else
492
+ routeBack(canvas, from, to, edge, lane);
493
+ }
494
+ else if (adjacent) {
495
+ routeForwardLr(canvas, from, to, edge, bus);
496
+ }
497
+ else {
498
+ routeBackLr(canvas, from, to, edge, lane);
499
+ }
500
+ });
501
+ canvas.finalizeMask();
502
+ return canvas;
503
+ }
504
+ /** Apply the direction flip a finished canvas needs for `BT` / `RL`. */
505
+ export function orient(canvas, graph) {
506
+ if (graph.dir === 'up')
507
+ canvas.flipVertical();
508
+ else if (graph.dir === 'left')
509
+ canvas.flipHorizontal();
510
+ return canvas;
511
+ }
512
+ /** Flowchart and state diagrams: plain boxes, no extra content. */
513
+ export function layoutFlowchart(graph) {
514
+ const extras = graph.nodes.map(() => ({ kind: 'plain' }));
515
+ const canvas = layoutCanvas(graph, extras);
516
+ return canvas && orient(canvas, graph);
517
+ }
518
+ /** Class and ER diagrams: boxes divided into title / attribute / method rows. */
519
+ export function layoutClass(graph, infos) {
520
+ const extras = graph.nodes.map((node, i) => {
521
+ const title = [];
522
+ if (infos[i].annotation !== null)
523
+ title.push(`«${infos[i].annotation}»`);
524
+ title.push(displayGenerics(node.label));
525
+ return { kind: 'compartments', sections: [title, infos[i].attrs, infos[i].methods] };
526
+ });
527
+ const canvas = layoutCanvas(graph, extras);
528
+ return canvas && orient(canvas, graph);
529
+ }
530
+ function displayGenerics(s) {
531
+ let out = '';
532
+ let open = false;
533
+ for (const c of s) {
534
+ if (c === '~') {
535
+ out += open ? '>' : '<';
536
+ open = !open;
537
+ }
538
+ else {
539
+ out += c;
540
+ }
541
+ }
542
+ return out;
543
+ }
544
+ const nodeKey = (i) => `n${i}`;
545
+ const groupKey = (i) => `g${i}`;
546
+ /**
547
+ * Lay out a flowchart that uses `subgraph`.
548
+ *
549
+ * Each subgraph becomes a framed box holding its own independently laid-out
550
+ * canvas. An edge is drawn in the innermost scope containing both endpoints;
551
+ * one crossing a subgraph boundary attaches to the frame instead of the node.
552
+ */
553
+ export function layoutGrouped(graph) {
554
+ // A node whose id matches a subgraph id stands in for that subgraph.
555
+ const proxy = new Map();
556
+ graph.groups.forEach((g, gi) => {
557
+ const ni = graph.index.get(g.id);
558
+ if (ni !== undefined)
559
+ proxy.set(ni, gi);
560
+ });
561
+ const groupChain = (g) => {
562
+ const chain = [];
563
+ let cur = g;
564
+ while (cur !== null) {
565
+ chain.push(cur);
566
+ cur = graph.groups[cur].parent;
567
+ }
568
+ return chain.reverse();
569
+ };
570
+ const endpoint = (n) => {
571
+ const gi = proxy.get(n);
572
+ return gi === undefined
573
+ ? { key: nodeKey(n), chain: groupChain(graph.nodeGroup[n]) }
574
+ : { key: groupKey(gi), chain: groupChain(graph.groups[gi].parent) };
575
+ };
576
+ /** Edges bucketed by the scope that draws them; `null` is the top level. */
577
+ const scopeEdges = new Map();
578
+ const referenced = new Array(graph.groups.length).fill(false);
579
+ graph.edges.forEach((e, ei) => {
580
+ const f = endpoint(e.from);
581
+ const t = endpoint(e.to);
582
+ let k = 0;
583
+ while (k < f.chain.length && k < t.chain.length && f.chain[k] === t.chain[k])
584
+ k++;
585
+ const scope = k === 0 ? null : f.chain[k - 1];
586
+ const fKey = f.chain.length > k ? groupKey(f.chain[k]) : f.key;
587
+ const tKey = t.chain.length > k ? groupKey(t.chain[k]) : t.key;
588
+ for (const key of [fKey, tKey]) {
589
+ if (key.startsWith('g'))
590
+ referenced[Number(key.slice(1))] = true;
591
+ }
592
+ const list = scopeEdges.get(scope);
593
+ if (list)
594
+ list.push([fKey, tKey, ei]);
595
+ else
596
+ scopeEdges.set(scope, [[fKey, tKey, ei]]);
597
+ });
598
+ const directNodes = new Map();
599
+ graph.nodeGroup.forEach((g, ni) => {
600
+ if (proxy.has(ni))
601
+ return;
602
+ const list = directNodes.get(g);
603
+ if (list)
604
+ list.push(ni);
605
+ else
606
+ directNodes.set(g, [ni]);
607
+ });
608
+ // Drop empty subgraphs, but keep any that an edge attaches to.
609
+ const keep = new Array(graph.groups.length).fill(false);
610
+ for (let gi = graph.groups.length - 1; gi >= 0; gi--) {
611
+ const hasNodes = (directNodes.get(gi) ?? []).length > 0;
612
+ const hasChildren = graph.groups.some((g, c) => g.parent === gi && keep[c]);
613
+ keep[gi] = hasNodes || hasChildren || referenced[gi];
614
+ }
615
+ const canvas = buildScope(graph, null, scopeEdges, directNodes, keep);
616
+ return canvas && orient(canvas, graph);
617
+ }
618
+ function buildScope(graph, scope, scopeEdges, directNodes, keep) {
619
+ const items = (directNodes.get(scope) ?? []).map(nodeKey);
620
+ const childGroups = graph.groups
621
+ .map((_, gi) => gi)
622
+ .filter((gi) => graph.groups[gi].parent === scope && keep[gi]);
623
+ items.push(...childGroups.map(groupKey));
624
+ if (items.length === 0)
625
+ return new Canvas(1, 1);
626
+ const indexOf = new Map();
627
+ const nodes = [];
628
+ const extras = [];
629
+ for (const item of items) {
630
+ indexOf.set(item, nodes.length);
631
+ const i = Number(item.slice(1));
632
+ if (item.startsWith('n')) {
633
+ nodes.push({ label: graph.nodes[i].label, shape: graph.nodes[i].shape });
634
+ extras.push({ kind: 'plain' });
635
+ }
636
+ else {
637
+ const sub = buildScope(graph, i, scopeEdges, directNodes, keep);
638
+ if (sub === null)
639
+ return null;
640
+ nodes.push({ label: graph.groups[i].label, shape: 'rect' });
641
+ extras.push({ kind: 'frame', sub });
642
+ }
643
+ }
644
+ const edges = [];
645
+ for (const [f, t, ei] of scopeEdges.get(scope) ?? []) {
646
+ const fi = indexOf.get(f);
647
+ const ti = indexOf.get(t);
648
+ if (fi === undefined || ti === undefined)
649
+ continue;
650
+ const e = graph.edges[ei];
651
+ edges.push({
652
+ from: fi,
653
+ to: ti,
654
+ label: e.label,
655
+ headTo: e.headTo,
656
+ headFrom: e.headFrom,
657
+ line: e.line,
658
+ });
659
+ }
660
+ // Layout only reads nodes/edges/dir, so a bare Graph carrying those is enough.
661
+ const synth = new Graph(graph.dir);
662
+ synth.nodes = nodes;
663
+ synth.edges = edges;
664
+ return layoutCanvas(synth, extras);
665
+ }
666
+ // ------------------------------------------------------------------- drawing
667
+ export function drawBox(canvas, p, lines, shape) {
668
+ const { x, y, w, h } = p;
669
+ const right = x + w - 1;
670
+ const bottom = y + h - 1;
671
+ const rounded = shape === 'round' || shape === 'diamond';
672
+ canvas.set(x, y, rounded ? '╭' : '┌', 'border');
673
+ canvas.set(right, y, rounded ? '╮' : '┐', 'border');
674
+ canvas.set(x, bottom, rounded ? '╰' : '└', 'border');
675
+ canvas.set(right, bottom, rounded ? '╯' : '┘', 'border');
676
+ // The perimeter is drawn as bits so edges can tee into it, but it is the box
677
+ // outline, so it claims `border` rather than `edge`.
678
+ for (let cx = x + 1; cx < right; cx++) {
679
+ canvas.addBits(cx, y, L | R, 'border');
680
+ canvas.addBits(cx, bottom, L | R, 'border');
681
+ }
682
+ for (let cy = y + 1; cy < bottom; cy++) {
683
+ canvas.addBits(x, cy, U | D, 'border');
684
+ canvas.addBits(right, cy, U | D, 'border');
685
+ }
686
+ for (let cy = y; cy <= bottom; cy++) {
687
+ for (let cx = x; cx <= right; cx++)
688
+ canvas.occupied[canvas.idx(cx, cy)] = 1;
689
+ }
690
+ const inner = Math.max(1, sat(w, 2 * PAD + 2));
691
+ lines.forEach((line, li) => {
692
+ const text = fitLabel(line, inner);
693
+ const textX = x + 1 + PAD + half(sat(inner, stringWidth(text)));
694
+ drawText(canvas, text, textX, y + 1 + li, 'text');
695
+ });
696
+ }
697
+ /** A class or ER box: sections separated by horizontal rules, title centred. */
698
+ function drawClassBox(canvas, p, sections) {
699
+ drawBox(canvas, p, [], 'rect');
700
+ const inner = Math.max(1, sat(p.w, 2 * PAD + 2));
701
+ let row = p.y + 1;
702
+ let first = true;
703
+ sections.forEach((section, si) => {
704
+ if (section.length === 0)
705
+ return;
706
+ if (!first) {
707
+ canvas.set(p.x, row, '├', 'border');
708
+ for (let x = p.x + 1; x < p.x + p.w - 1; x++)
709
+ canvas.set(x, row, '─', 'border');
710
+ canvas.set(p.x + p.w - 1, row, '┤', 'border');
711
+ row++;
712
+ }
713
+ first = false;
714
+ for (const line of section) {
715
+ const text = fitLabel(line, inner);
716
+ const tx = si === 0 ? p.x + 1 + PAD + half(sat(inner, stringWidth(text))) : p.x + 1 + PAD;
717
+ drawTextOverEdges(canvas, text, tx, row, 'text');
718
+ row++;
719
+ }
720
+ });
721
+ }
722
+ /** A subgraph frame: a titled box with a finished sub-canvas centred inside. */
723
+ function drawFrame(canvas, p, title, sub) {
724
+ drawBox(canvas, p, [], 'rect');
725
+ const t = fitLabel(title, sat(p.w, 4));
726
+ drawTextOverEdges(canvas, ` ${t} `, p.x + 1, p.y, 'text');
727
+ canvas.blit(sub, p.x + 1 + half(p.w - 2 - sub.w), p.y + 1 + half(p.h - 2 - sub.h));
728
+ }
729
+ // ------------------------------------------------------------------- routing
730
+ function headGlyph(head, arrow) {
731
+ switch (head) {
732
+ case 'circle':
733
+ return 'o';
734
+ case 'cross':
735
+ return '×';
736
+ case 'diamondFill':
737
+ return '◆';
738
+ case 'diamondOpen':
739
+ return '◇';
740
+ case 'triangle':
741
+ return { '▼': '▽', '▲': '△', '◄': '◁', '▶': '▷' }[arrow] ?? arrow;
742
+ default:
743
+ return arrow;
744
+ }
745
+ }
746
+ /** Adjacent ranks, top-down: drop, jog along the bus row, drop into the head. */
747
+ function routeForward(canvas, from, to, edge, bus) {
748
+ const tx = to.cx;
749
+ // A jog of one column reads as a kink; snap straight instead.
750
+ const bx = Math.abs(from.cx - tx) <= 1 ? tx : from.cx;
751
+ const by = from.y + from.h - 1;
752
+ const headRow = to.y - 1;
753
+ canvas.junction(bx, by, D);
754
+ canvas.segV(bx, by, bus);
755
+ if (bx === tx) {
756
+ canvas.segV(bx, bus, headRow);
757
+ }
758
+ else {
759
+ canvas.segH(bus, bx, tx);
760
+ canvas.segV(tx, bus, headRow);
761
+ }
762
+ if (edge.headTo === 'none')
763
+ canvas.addBits(tx, headRow, U);
764
+ else
765
+ canvas.set(tx, headRow, headGlyph(edge.headTo, '▼'), 'edge');
766
+ if (edge.headFrom !== 'none')
767
+ canvas.set(bx, by, headGlyph(edge.headFrom, '▲'), 'edge');
768
+ if (edge.label !== null)
769
+ placeLabel(canvas, edge.label, headRow, tx + 1);
770
+ }
771
+ /** A self-edge: a stub loop hanging below the box. */
772
+ function routeSelf(canvas, p, edge) {
773
+ const bottom = p.y + p.h - 1;
774
+ const exitX = p.cx + 1;
775
+ const retX = p.x + p.w - 2;
776
+ if (retX <= exitX || bottom + 2 >= canvas.h)
777
+ return;
778
+ const [v, h, bl, br] = edge.line === 'dotted'
779
+ ? ['╎', '╌', '╰', '╯']
780
+ : edge.line === 'thick'
781
+ ? ['┃', '━', '┗', '┛']
782
+ : ['│', '─', '╰', '╯'];
783
+ canvas.junction(exitX, bottom, D);
784
+ canvas.set(exitX, bottom + 1, v, 'edge');
785
+ canvas.set(exitX, bottom + 2, bl, 'edge');
786
+ for (let x = exitX + 1; x < retX; x++)
787
+ canvas.set(x, bottom + 2, h, 'edge');
788
+ canvas.set(retX, bottom + 2, br, 'edge');
789
+ canvas.set(retX, bottom + 1, headGlyph(edge.headTo, '▲'), 'edge');
790
+ if (edge.label !== null)
791
+ placeLabel(canvas, edge.label, bottom + 1, p.x + p.w + 1);
792
+ }
793
+ /** Skip or back edge, top-down: out the right side, up a lane, back in. */
794
+ function routeBack(canvas, from, to, edge, laneX) {
795
+ const sx = from.x + from.w - 1;
796
+ const sy = from.cy;
797
+ const tx = to.x + to.w - 1;
798
+ const tyc = to.cy;
799
+ canvas.junction(sx, sy, R);
800
+ canvas.segH(sy, sx, laneX);
801
+ canvas.segV(laneX, sy, tyc);
802
+ canvas.segH(tyc, tx + 1, laneX);
803
+ if (edge.headTo === 'none')
804
+ canvas.addBits(tx + 1, tyc, R);
805
+ else
806
+ canvas.set(tx + 1, tyc, headGlyph(edge.headTo, '◄'), 'edge');
807
+ if (edge.headFrom !== 'none')
808
+ canvas.set(sx, sy, headGlyph(edge.headFrom, '◄'), 'edge');
809
+ if (edge.label !== null) {
810
+ placeLabel(canvas, edge.label, sat(tyc, 1), sat(laneX, stringWidth(edge.label) + 1));
811
+ }
812
+ }
813
+ /** Adjacent ranks, left-to-right: out the right side, jog on the bus column. */
814
+ function routeForwardLr(canvas, from, to, edge, bus) {
815
+ const rx = from.x + from.w - 1;
816
+ const ry = from.cy;
817
+ const ly = to.cy;
818
+ const headCol = to.x - 1;
819
+ canvas.junction(rx, ry, R);
820
+ canvas.segH(ry, rx, bus);
821
+ if (ry === ly) {
822
+ canvas.segH(ry, bus, headCol);
823
+ }
824
+ else {
825
+ canvas.segV(bus, ry, ly);
826
+ canvas.segH(ly, bus, headCol);
827
+ }
828
+ if (edge.headTo === 'none')
829
+ canvas.addBits(headCol, ly, R);
830
+ else
831
+ canvas.set(headCol, ly, headGlyph(edge.headTo, '▶'), 'edge');
832
+ if (edge.headFrom !== 'none')
833
+ canvas.set(rx, ry, headGlyph(edge.headFrom, '◄'), 'edge');
834
+ if (edge.label !== null)
835
+ placeLabel(canvas, edge.label, sat(ly, 1), bus + 1);
836
+ }
837
+ /** Skip or back edge, left-to-right: down out the bottom, along a lane, back up. */
838
+ function routeBackLr(canvas, from, to, edge, laneY) {
839
+ const sx = from.cx;
840
+ const sy = from.y + from.h - 1;
841
+ const tx = to.cx;
842
+ const ty = to.y + to.h - 1;
843
+ canvas.junction(sx, sy, D);
844
+ canvas.segV(sx, sy, laneY);
845
+ canvas.segH(laneY, sx, tx);
846
+ canvas.segV(tx, laneY, ty + 1);
847
+ if (edge.headTo === 'none')
848
+ canvas.addBits(tx, ty + 1, D);
849
+ else
850
+ canvas.set(tx, ty + 1, headGlyph(edge.headTo, '▲'), 'edge');
851
+ if (edge.headFrom !== 'none')
852
+ canvas.set(sx, sy, headGlyph(edge.headFrom, '▲'), 'edge');
853
+ if (edge.label !== null)
854
+ placeLabel(canvas, edge.label, sat(laneY, 1), half(sx + tx));
855
+ }
856
+ /** Write an edge label, stopping at the first cell already occupied. */
857
+ function placeLabel(canvas, label, row, startX) {
858
+ if (row >= canvas.h)
859
+ return;
860
+ const text = fitLabel(label, MAX_LABEL);
861
+ let x = startX;
862
+ for (const [c, cw] of measured(text)) {
863
+ if (cw === 0)
864
+ continue;
865
+ if (x + cw > canvas.w)
866
+ break;
867
+ let blocked = false;
868
+ for (let k = 0; k < cw; k++) {
869
+ const i = canvas.idx(x + k, row);
870
+ if (canvas.ch[i] !== ' ' || canvas.mask[i] !== 0 || canvas.occupied[i])
871
+ blocked = true;
872
+ }
873
+ if (blocked)
874
+ break;
875
+ canvas.set(x, row, c, 'edgeLabel');
876
+ for (let k = 1; k < cw; k++)
877
+ canvas.set(x + k, row, CONT, 'edgeLabel');
878
+ x += cw;
879
+ }
880
+ }
881
+ //# sourceMappingURL=layout.js.map