@acpus/web 0.1.0-alpha.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 (141) hide show
  1. package/LICENSE +21 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/client/api.d.ts +398 -0
  4. package/dist/client/api.d.ts.map +1 -0
  5. package/dist/client/api.js +73 -0
  6. package/dist/client/api.js.map +1 -0
  7. package/dist/client/assets/index-Cn1qpJ2F.css +1 -0
  8. package/dist/client/assets/index-DVV3hISu.js +2 -0
  9. package/dist/client/assets/vendor-B7fc3-WT.js +69 -0
  10. package/dist/client/assets/vendor-icons-BQrCRas_.js +241 -0
  11. package/dist/client/assets/vendor-json-view-B9Z7D9kT.css +1 -0
  12. package/dist/client/assets/vendor-json-view-uYZpLE5K.js +1 -0
  13. package/dist/client/assets/vendor-query-Co8PPx4J.js +1 -0
  14. package/dist/client/assets/vendor-radix-C9JUg2bE.js +1 -0
  15. package/dist/client/assets/vendor-react-BRGuIgbw.js +49 -0
  16. package/dist/client/index.html +26 -0
  17. package/dist/client/lib/utils.d.ts +3 -0
  18. package/dist/client/lib/utils.d.ts.map +1 -0
  19. package/dist/client/lib/utils.js +6 -0
  20. package/dist/client/lib/utils.js.map +1 -0
  21. package/dist/client/main.d.ts +2 -0
  22. package/dist/client/main.d.ts.map +1 -0
  23. package/dist/client/main.js +18 -0
  24. package/dist/client/main.js.map +1 -0
  25. package/dist/client/static-viz.d.ts +8 -0
  26. package/dist/client/static-viz.d.ts.map +1 -0
  27. package/dist/client/static-viz.js +10 -0
  28. package/dist/client/static-viz.js.map +1 -0
  29. package/dist/client/ui/App.d.ts +40 -0
  30. package/dist/client/ui/App.d.ts.map +1 -0
  31. package/dist/client/ui/App.js +742 -0
  32. package/dist/client/ui/App.js.map +1 -0
  33. package/dist/client/ui/RunGraph.d.ts +9 -0
  34. package/dist/client/ui/RunGraph.d.ts.map +1 -0
  35. package/dist/client/ui/RunGraph.js +294 -0
  36. package/dist/client/ui/RunGraph.js.map +1 -0
  37. package/dist/client/ui/StaticGraphApp.d.ts +22 -0
  38. package/dist/client/ui/StaticGraphApp.d.ts.map +1 -0
  39. package/dist/client/ui/StaticGraphApp.js +102 -0
  40. package/dist/client/ui/StaticGraphApp.js.map +1 -0
  41. package/dist/client/ui/Toast.d.ts +16 -0
  42. package/dist/client/ui/Toast.d.ts.map +1 -0
  43. package/dist/client/ui/Toast.js +41 -0
  44. package/dist/client/ui/Toast.js.map +1 -0
  45. package/dist/client/ui/shadcn/alert.d.ts +3 -0
  46. package/dist/client/ui/shadcn/alert.d.ts.map +1 -0
  47. package/dist/client/ui/shadcn/alert.js +6 -0
  48. package/dist/client/ui/shadcn/alert.js.map +1 -0
  49. package/dist/client/ui/shadcn/badge.d.ts +10 -0
  50. package/dist/client/ui/shadcn/badge.d.ts.map +1 -0
  51. package/dist/client/ui/shadcn/badge.js +19 -0
  52. package/dist/client/ui/shadcn/badge.js.map +1 -0
  53. package/dist/client/ui/shadcn/breadcrumb.d.ts +9 -0
  54. package/dist/client/ui/shadcn/breadcrumb.d.ts.map +1 -0
  55. package/dist/client/ui/shadcn/breadcrumb.js +15 -0
  56. package/dist/client/ui/shadcn/breadcrumb.js.map +1 -0
  57. package/dist/client/ui/shadcn/button.d.ts +12 -0
  58. package/dist/client/ui/shadcn/button.d.ts.map +1 -0
  59. package/dist/client/ui/shadcn/button.js +37 -0
  60. package/dist/client/ui/shadcn/button.js.map +1 -0
  61. package/dist/client/ui/shadcn/card.d.ts +6 -0
  62. package/dist/client/ui/shadcn/card.d.ts.map +1 -0
  63. package/dist/client/ui/shadcn/card.js +10 -0
  64. package/dist/client/ui/shadcn/card.js.map +1 -0
  65. package/dist/client/ui/shadcn/dialog.d.ts +11 -0
  66. package/dist/client/ui/shadcn/dialog.d.ts.map +1 -0
  67. package/dist/client/ui/shadcn/dialog.js +15 -0
  68. package/dist/client/ui/shadcn/dialog.js.map +1 -0
  69. package/dist/client/ui/shadcn/input.d.ts +5 -0
  70. package/dist/client/ui/shadcn/input.d.ts.map +1 -0
  71. package/dist/client/ui/shadcn/input.js +6 -0
  72. package/dist/client/ui/shadcn/input.js.map +1 -0
  73. package/dist/client/ui/shadcn/list.d.ts +8 -0
  74. package/dist/client/ui/shadcn/list.d.ts.map +1 -0
  75. package/dist/client/ui/shadcn/list.js +8 -0
  76. package/dist/client/ui/shadcn/list.js.map +1 -0
  77. package/dist/client/ui/shadcn/popover.d.ts +8 -0
  78. package/dist/client/ui/shadcn/popover.d.ts.map +1 -0
  79. package/dist/client/ui/shadcn/popover.js +11 -0
  80. package/dist/client/ui/shadcn/popover.js.map +1 -0
  81. package/dist/client/ui/shadcn/select.d.ts +9 -0
  82. package/dist/client/ui/shadcn/select.d.ts.map +1 -0
  83. package/dist/client/ui/shadcn/select.js +17 -0
  84. package/dist/client/ui/shadcn/select.js.map +1 -0
  85. package/dist/client/ui/shadcn/tabs.d.ts +7 -0
  86. package/dist/client/ui/shadcn/tabs.d.ts.map +1 -0
  87. package/dist/client/ui/shadcn/tabs.js +12 -0
  88. package/dist/client/ui/shadcn/tabs.js.map +1 -0
  89. package/dist/client/ui/shadcn/textarea.d.ts +3 -0
  90. package/dist/client/ui/shadcn/textarea.d.ts.map +1 -0
  91. package/dist/client/ui/shadcn/textarea.js +6 -0
  92. package/dist/client/ui/shadcn/textarea.js.map +1 -0
  93. package/dist/graph-renderer.d.ts +114 -0
  94. package/dist/graph-renderer.d.ts.map +1 -0
  95. package/dist/graph-renderer.js +673 -0
  96. package/dist/graph-renderer.js.map +1 -0
  97. package/dist/index.d.ts +5 -0
  98. package/dist/index.d.ts.map +1 -0
  99. package/dist/index.js +5 -0
  100. package/dist/index.js.map +1 -0
  101. package/dist/runtime-status.d.ts +10 -0
  102. package/dist/runtime-status.d.ts.map +1 -0
  103. package/dist/runtime-status.js +45 -0
  104. package/dist/runtime-status.js.map +1 -0
  105. package/dist/server/app.d.ts +11 -0
  106. package/dist/server/app.d.ts.map +1 -0
  107. package/dist/server/app.js +244 -0
  108. package/dist/server/app.js.map +1 -0
  109. package/dist/server/assets.d.ts +3 -0
  110. package/dist/server/assets.d.ts.map +1 -0
  111. package/dist/server/assets.js +45 -0
  112. package/dist/server/assets.js.map +1 -0
  113. package/dist/server/errors.d.ts +14 -0
  114. package/dist/server/errors.d.ts.map +1 -0
  115. package/dist/server/errors.js +13 -0
  116. package/dist/server/errors.js.map +1 -0
  117. package/dist/server/graph.d.ts +137 -0
  118. package/dist/server/graph.d.ts.map +1 -0
  119. package/dist/server/graph.js +592 -0
  120. package/dist/server/graph.js.map +1 -0
  121. package/dist/server/launcher.d.ts +15 -0
  122. package/dist/server/launcher.d.ts.map +1 -0
  123. package/dist/server/launcher.js +30 -0
  124. package/dist/server/launcher.js.map +1 -0
  125. package/dist/server/node-inspection.d.ts +108 -0
  126. package/dist/server/node-inspection.d.ts.map +1 -0
  127. package/dist/server/node-inspection.js +435 -0
  128. package/dist/server/node-inspection.js.map +1 -0
  129. package/dist/server/security.d.ts +12 -0
  130. package/dist/server/security.d.ts.map +1 -0
  131. package/dist/server/security.js +43 -0
  132. package/dist/server/security.js.map +1 -0
  133. package/dist/server/static-viz-assets.generated.d.ts +3 -0
  134. package/dist/server/static-viz-assets.generated.d.ts.map +1 -0
  135. package/dist/server/static-viz-assets.generated.js +4 -0
  136. package/dist/server/static-viz-assets.generated.js.map +1 -0
  137. package/dist/server/workflows.d.ts +75 -0
  138. package/dist/server/workflows.d.ts.map +1 -0
  139. package/dist/server/workflows.js +205 -0
  140. package/dist/server/workflows.js.map +1 -0
  141. package/package.json +65 -0
@@ -0,0 +1,673 @@
1
+ import { displayNodeStatus, displayRunStatus, isActiveDisplayStatus, normalizeRuntimeStatus } from "./runtime-status.js";
2
+ const leafWidth = 200;
3
+ const leafHeight = 72;
4
+ const compositeMinWidth = 260;
5
+ const loopCompositeMinWidth = 340;
6
+ const compositeMinHeight = 78;
7
+ const containerMinWidth = 220;
8
+ const emptyContainerHeight = 64;
9
+ const headerHeight = 46;
10
+ const padding = 18;
11
+ const branchContainerPadding = 32;
12
+ const scopeContainerPadding = 56;
13
+ const verticalGap = 36;
14
+ const branchGap = 36;
15
+ const canvasPadding = 48;
16
+ const visualOverflowPadding = 24;
17
+ const minFitPadding = 12;
18
+ const maxFitPadding = 32;
19
+ const fitPaddingFactor = 0.045;
20
+ const minZoomFactor = 0.75;
21
+ const maxScale = 2;
22
+ const wheelZoomSensitivity = 0.0008;
23
+ const selectedVisibilityMargin = 48;
24
+ const graphItemZIndexBase = 1;
25
+ const graphItemZIndexMax = 18;
26
+ export const graphCanvasPadding = canvasPadding;
27
+ export const graphMaxScale = maxScale;
28
+ export const graphSelectedVisibilityMargin = selectedVisibilityMargin;
29
+ export function graphItemZIndex(depth) {
30
+ return graphItemZIndexBase + Math.min(Math.max(0, depth), graphItemZIndexMax - graphItemZIndexBase);
31
+ }
32
+ export function leafSubtitle(detail) {
33
+ if (!detail)
34
+ return undefined;
35
+ switch (detail.kind) {
36
+ case "assert":
37
+ return detail.condition;
38
+ case "signal":
39
+ return detail.outputSchema;
40
+ case "agent":
41
+ return [`use: ${detail.use ?? detail.command ?? detail.agent}`, detail.model].filter(Boolean).join(" · ");
42
+ case "task":
43
+ return detail.inputs.length > 0 ? `input: ${detail.inputs.join(", ")}` : undefined;
44
+ default:
45
+ return undefined;
46
+ }
47
+ }
48
+ export function compositeDescriptor(detail) {
49
+ if (!detail)
50
+ return undefined;
51
+ switch (detail.kind) {
52
+ case "parallel":
53
+ return `${detail.branches.length} branch(es)`;
54
+ case "switch":
55
+ return `${detail.cases.length + (detail.hasDefault ? 1 : 0)} branch(es)`;
56
+ case "if":
57
+ return `Condition: ${detail.condition}`;
58
+ case "fanout":
59
+ return detail.over;
60
+ case "loop":
61
+ return undefined;
62
+ default:
63
+ return undefined;
64
+ }
65
+ }
66
+ export function selectorOptionLabel(selector, option) {
67
+ if (selector.kind === "loop" && option.iteration !== undefined)
68
+ return `iter ${option.iteration}`;
69
+ return option.label;
70
+ }
71
+ export function selectorStatusSummary(selector, selectedOptionId) {
72
+ const option = selector.options.find(candidate => candidate.id === selectedOptionId)
73
+ ?? selector.options.find(candidate => candidate.id === selector.defaultOptionId)
74
+ ?? selector.options[0];
75
+ if (!option)
76
+ return undefined;
77
+ if (selector.kind === "fanout") {
78
+ const completed = selector.options.filter(candidate => normalizeRuntimeStatus(candidate.status) === "completed").length;
79
+ return { status: displayStatus(option.status), label: `${completed}/${selector.options.length}` };
80
+ }
81
+ return { status: displayStatus(option.status), label: displayStatus(option.status).replaceAll("_", " ") };
82
+ }
83
+ export function compositeBadge(kind) {
84
+ return kind.toUpperCase();
85
+ }
86
+ export function compositeStrategy(detail) {
87
+ if (detail?.kind === "parallel" || detail?.kind === "fanout")
88
+ return detail.strategy.toUpperCase();
89
+ return undefined;
90
+ }
91
+ export function toRenderModel(graph, selections = {}) {
92
+ if (!graph)
93
+ return { mode: "static", items: new Map(), rootIds: [], edges: [], parentOf: new Map() };
94
+ const selectedOptions = selectedOptionsByNode(graph, selections);
95
+ const selectors = applicableSelectors(graph, selectedOptions);
96
+ const selectorByNodeId = new Map(selectors.map(selector => [selector.nodeId, selector]));
97
+ const ids = new Set([...graph.nodes.map(node => node.id), ...graph.containers.map(container => container.id)]);
98
+ const parentOf = safeParents([
99
+ ...graph.containers.map(container => ({ id: container.id, parentId: container.parentId })),
100
+ ...graph.nodes.map(node => parentRef(node.id, node.parentId ?? node.parentNodeId)),
101
+ ], ids);
102
+ const items = new Map();
103
+ for (const container of graph.containers) {
104
+ const rawStatus = targetStatus(graph, container.id, container.status, selectedOptions);
105
+ const status = displayNodeStatus(rawStatus);
106
+ const parentId = parentOf.get(container.id);
107
+ items.set(container.id, {
108
+ id: container.id,
109
+ type: "container",
110
+ kind: container.kind,
111
+ label: container.label,
112
+ status,
113
+ rawStatus,
114
+ dimmed: false,
115
+ active: isActiveStatus(status),
116
+ children: [],
117
+ path: container.path,
118
+ nodeId: container.nodeId,
119
+ container: { ...container, status },
120
+ ...(parentId ? { parentId } : {}),
121
+ });
122
+ }
123
+ for (const node of graph.nodes) {
124
+ const rawStatus = targetStatus(graph, node.id, node.status, selectedOptions);
125
+ const status = displayNodeStatus(rawStatus);
126
+ const selector = selectorByNodeId.get(node.id);
127
+ const selectedOptionId = selector ? selectedOptions.get(selector.nodeId)?.id ?? selector.defaultOptionId : undefined;
128
+ const parentId = parentOf.get(node.id);
129
+ items.set(node.id, {
130
+ id: node.id,
131
+ type: "node",
132
+ kind: node.kind,
133
+ label: node.label,
134
+ status,
135
+ rawStatus,
136
+ dimmed: false,
137
+ active: isActiveStatus(status),
138
+ children: [],
139
+ path: node.path,
140
+ nodeId: node.nodeId,
141
+ node: { ...node, status },
142
+ ...(parentId ? { parentId } : {}),
143
+ ...(selector ? { selector } : {}),
144
+ ...(selectedOptionId ? { selectedOptionId } : {}),
145
+ });
146
+ }
147
+ const childrenByParent = new Map();
148
+ for (const id of items.keys()) {
149
+ const parentId = parentOf.get(id) ?? "";
150
+ childrenByParent.set(parentId, [...(childrenByParent.get(parentId) ?? []), id]);
151
+ }
152
+ const rank = rankItems(graph);
153
+ for (const [id, item] of items) {
154
+ item.children = orderChildren(childrenByParent.get(id) ?? [], graph.edges, rank);
155
+ }
156
+ applyDisplayRuntimeState(graph, items, parentOf);
157
+ return {
158
+ mode: graph.mode,
159
+ items,
160
+ rootIds: orderChildren(childrenByParent.get("") ?? [], graph.edges, rank),
161
+ edges: graph.edges.filter(edge => ids.has(edge.source) && ids.has(edge.target)),
162
+ parentOf,
163
+ };
164
+ }
165
+ export function normalizeSelections(graph, current) {
166
+ const next = {};
167
+ const selected = selectedOptionsByNode(graph, current);
168
+ for (const selector of applicableSelectors(graph, selected)) {
169
+ const currentId = current[selector.nodeId];
170
+ const option = selector.options.find(candidate => candidate.id === currentId)
171
+ ?? selector.options.find(candidate => candidate.id === selector.defaultOptionId)
172
+ ?? selector.options.at(-1)
173
+ ?? selector.options[0];
174
+ if (option)
175
+ next[selector.nodeId] = option.id;
176
+ }
177
+ return next;
178
+ }
179
+ export function selectionContext(graph, selections, targetId, parentOf) {
180
+ if (!graph || graph.mode === "static")
181
+ return [];
182
+ const selected = selectedOptionsByNode(graph, selections);
183
+ return applicableSelectors(graph, selected).flatMap(selector => {
184
+ if (targetId && parentOf && targetId !== selector.targetId && !isAncestor(selector.targetId, targetId, parentOf))
185
+ return [];
186
+ const option = selected.get(selector.nodeId)
187
+ ?? selector.options.find(candidate => candidate.id === selector.defaultOptionId)
188
+ ?? selector.options[0];
189
+ return option ? [selectionFromOption(selector, option)] : [];
190
+ });
191
+ }
192
+ export function safeParents(nodes, ids) {
193
+ const parentOf = new Map();
194
+ const reaches = (from, target) => {
195
+ const visited = new Set();
196
+ let current = from;
197
+ while (current && !visited.has(current)) {
198
+ if (current === target)
199
+ return true;
200
+ visited.add(current);
201
+ current = parentOf.get(current);
202
+ }
203
+ return false;
204
+ };
205
+ for (const node of nodes) {
206
+ const parent = node.parentId;
207
+ if (parent === undefined || parent === node.id || !ids.has(parent))
208
+ continue;
209
+ if (reaches(parent, node.id))
210
+ continue;
211
+ parentOf.set(node.id, parent);
212
+ }
213
+ return parentOf;
214
+ }
215
+ export function layoutWorkflow(model) {
216
+ const boxes = new Map();
217
+ const sizes = new Map();
218
+ const measureItem = (id) => {
219
+ const cached = sizes.get(id);
220
+ if (cached)
221
+ return cached;
222
+ const item = model.items.get(id);
223
+ if (!item)
224
+ return { width: 0, height: 0 };
225
+ let size;
226
+ if (item.children.length === 0) {
227
+ if (item.type === "container")
228
+ size = { width: containerMinWidth, height: emptyContainerHeight };
229
+ else if (isCompositeKind(item.kind))
230
+ size = { width: compositeMinWidthFor(item), height: compositeMinHeight };
231
+ else
232
+ size = { width: leafWidth, height: leafHeight };
233
+ }
234
+ else {
235
+ const children = measureChildren(item.children, model.items);
236
+ const minWidth = item.type === "container" ? containerMinWidth : compositeMinWidthFor(item);
237
+ const innerPadding = innerPaddingFor(item);
238
+ size = {
239
+ width: Math.max(minWidth, children.width + innerPadding * 2),
240
+ height: headerHeight + innerPadding + children.height + innerPadding,
241
+ };
242
+ }
243
+ sizes.set(id, size);
244
+ return size;
245
+ };
246
+ function compositeMinWidthFor(item) {
247
+ return item.kind === "loop" ? loopCompositeMinWidth : compositeMinWidth;
248
+ }
249
+ function innerPaddingFor(item) {
250
+ if (item.type !== "container")
251
+ return padding;
252
+ return item.container?.kind === "scope" ? scopeContainerPadding : branchContainerPadding;
253
+ }
254
+ const measureChildren = (ids, items) => {
255
+ if (ids.length === 0)
256
+ return { width: 0, height: 0 };
257
+ const layout = branchLayout(ids, items);
258
+ const childSizes = ids.map(measureItem);
259
+ if (layout === "horizontal") {
260
+ return {
261
+ width: childSizes.reduce((sum, size) => sum + size.width, 0) + branchGap * (childSizes.length - 1),
262
+ height: Math.max(...childSizes.map(size => size.height)),
263
+ };
264
+ }
265
+ return {
266
+ width: Math.max(...childSizes.map(size => size.width)),
267
+ height: childSizes.reduce((sum, size) => sum + size.height, 0) + verticalGap * (childSizes.length - 1),
268
+ };
269
+ };
270
+ const rootSize = measureChildren(model.rootIds, model.items);
271
+ const placeItem = (id, x, y) => {
272
+ const item = model.items.get(id);
273
+ if (!item)
274
+ return;
275
+ const size = measureItem(id);
276
+ boxes.set(id, { id, x, y, width: size.width, height: size.height });
277
+ if (item.children.length === 0)
278
+ return;
279
+ const childrenSize = measureChildren(item.children, model.items);
280
+ const innerPadding = innerPaddingFor(item);
281
+ placeChildren(item.children, x + (size.width - childrenSize.width) / 2, y + headerHeight + innerPadding, childrenSize.width, "center");
282
+ };
283
+ const placeChildren = (ids, x, y, groupWidth, align) => {
284
+ const layout = branchLayout(ids, model.items);
285
+ if (layout === "horizontal") {
286
+ let cursor = x;
287
+ for (const id of ids) {
288
+ const size = measureItem(id);
289
+ placeItem(id, cursor, y);
290
+ cursor += size.width + branchGap;
291
+ }
292
+ return;
293
+ }
294
+ let cursor = y;
295
+ for (const id of ids) {
296
+ const size = measureItem(id);
297
+ placeItem(id, align === "center" ? x + (groupWidth - size.width) / 2 : x, cursor);
298
+ cursor += size.height + verticalGap;
299
+ }
300
+ };
301
+ placeChildren(model.rootIds, canvasPadding, canvasPadding, rootSize.width, "center");
302
+ const width = Math.max(rootSize.width + canvasPadding * 2, 960);
303
+ const height = Math.max(rootSize.height + canvasPadding * 2, 540);
304
+ return {
305
+ boxes,
306
+ edgePaths: buildEdgePaths(model.edges, boxes, model.parentOf, activeEdgeIds(model)),
307
+ width,
308
+ height,
309
+ };
310
+ }
311
+ export function buildEdgePaths(edges, boxes, parentOf, activeEdgeIds = new Set()) {
312
+ const paths = [];
313
+ for (const edge of renderableEdges(edges, parentOf)) {
314
+ const source = boxes.get(edge.source);
315
+ const target = boxes.get(edge.target);
316
+ if (!source || !target)
317
+ continue;
318
+ const start = { x: source.x + source.width / 2, y: source.y + source.height };
319
+ const end = { x: target.x + target.width / 2, y: target.y };
320
+ const d = edge.kind === "loop" || end.y < start.y
321
+ ? loopPath(start.x, start.y, end.x, end.y, source, target)
322
+ : flowPath(start.x, start.y, end.x, end.y);
323
+ paths.push({ id: edge.id, kind: edge.kind, d, ...(activeEdgeIds.has(edge.id) ? { active: true } : {}) });
324
+ }
325
+ return paths;
326
+ }
327
+ export function buildProjectedEdgePaths(edges, boxes, parentOf, viewport, activeEdgeIds = new Set()) {
328
+ return renderableEdges(edges, parentOf).flatMap(edge => {
329
+ const path = projectEdgePath(edge, boxes, viewport);
330
+ return path ? [{ ...path, ...(activeEdgeIds.has(edge.id) ? { active: true } : {}) }] : [];
331
+ });
332
+ }
333
+ export function projectEdgePath(edge, boxes, viewport) {
334
+ const source = boxes.get(edge.source);
335
+ const target = boxes.get(edge.target);
336
+ if (!source || !target)
337
+ return undefined;
338
+ const sourceBox = projectBox(source, viewport);
339
+ const targetBox = projectBox(target, viewport);
340
+ const start = projectPoint({ x: source.x + source.width / 2, y: source.y + source.height }, viewport);
341
+ const end = projectPoint({ x: target.x + target.width / 2, y: target.y }, viewport);
342
+ const d = edge.kind === "loop" || end.y < start.y
343
+ ? loopPath(start.x, start.y, end.x, end.y, sourceBox, targetBox)
344
+ : flowPath(start.x, start.y, end.x, end.y);
345
+ return { id: edge.id, kind: edge.kind, d };
346
+ }
347
+ export function renderableEdges(edges, parentOf) {
348
+ return edges.filter(edge => parentOf.get(edge.source) === parentOf.get(edge.target)
349
+ && !isAncestor(edge.source, edge.target, parentOf)
350
+ && !isAncestor(edge.target, edge.source, parentOf));
351
+ }
352
+ export function activeEdgeIds(model) {
353
+ const ids = new Set();
354
+ for (const edge of renderableEdges(model.edges, model.parentOf)) {
355
+ const source = model.items.get(edge.source);
356
+ const target = model.items.get(edge.target);
357
+ if (!source || !target)
358
+ continue;
359
+ if (isActiveStatus(source.status) || isActiveStatus(target.status))
360
+ ids.add(edge.id);
361
+ }
362
+ return ids;
363
+ }
364
+ export function projectBoxes(boxes, viewport) {
365
+ return new Map([...boxes].map(([id, box]) => [id, projectBox(box, viewport)]));
366
+ }
367
+ export function projectBox(box, viewport) {
368
+ return {
369
+ id: box.id,
370
+ x: viewport.x + box.x * viewport.scale,
371
+ y: viewport.y + box.y * viewport.scale,
372
+ width: box.width * viewport.scale,
373
+ height: box.height * viewport.scale,
374
+ };
375
+ }
376
+ export function projectPoint(point, viewport) {
377
+ return {
378
+ x: viewport.x + point.x * viewport.scale,
379
+ y: viewport.y + point.y * viewport.scale,
380
+ };
381
+ }
382
+ export function isLosslessZoom(scale) {
383
+ return scale >= 1;
384
+ }
385
+ export function fitView(layout, rect) {
386
+ if (layout.width <= 0 || layout.height <= 0 || rect.width <= 0 || rect.height <= 0) {
387
+ return { x: 0, y: 0, scale: 1 };
388
+ }
389
+ const scale = fitScale(layout, rect);
390
+ return {
391
+ x: (rect.width - layout.width * scale) / 2,
392
+ y: (rect.height - layout.height * scale) / 2,
393
+ scale,
394
+ };
395
+ }
396
+ export function fitScale(layout, rect) {
397
+ if (layout.width <= 0 || layout.height <= 0 || rect.width <= 0 || rect.height <= 0)
398
+ return 1;
399
+ const padding = fitViewportPadding(rect);
400
+ const availableWidth = Math.max(1, rect.width - padding * 2);
401
+ const availableHeight = Math.max(1, rect.height - padding * 2);
402
+ return clamp(Math.min(availableWidth / layout.width, availableHeight / layout.height, 1), 0, maxScale);
403
+ }
404
+ export function fitViewportPadding(rect) {
405
+ if (rect.width <= 0 || rect.height <= 0)
406
+ return 0;
407
+ return clamp(Math.min(rect.width, rect.height) * fitPaddingFactor, minFitPadding, maxFitPadding);
408
+ }
409
+ export function visualBoundsForLayout(layout) {
410
+ return {
411
+ x: -visualOverflowPadding,
412
+ y: -visualOverflowPadding,
413
+ width: layout.width + visualOverflowPadding * 2,
414
+ height: layout.height + visualOverflowPadding * 2,
415
+ };
416
+ }
417
+ export function minZoomScale(fitScaleValue) {
418
+ return fitScaleValue * minZoomFactor;
419
+ }
420
+ export function wheelZoomScale(currentScale, deltaY, minScale, maxScaleValue) {
421
+ return clamp(currentScale * Math.exp(-deltaY * wheelZoomSensitivity), minScale, maxScaleValue);
422
+ }
423
+ export function zoomViewport(viewport, scale, px, py) {
424
+ return {
425
+ x: px - ((px - viewport.x) / viewport.scale) * scale,
426
+ y: py - ((py - viewport.y) / viewport.scale) * scale,
427
+ scale,
428
+ };
429
+ }
430
+ export function keepBoxInViewport(viewport, rect, box, margin = selectedVisibilityMargin) {
431
+ if (!box || rect.width <= 0 || rect.height <= 0)
432
+ return viewport;
433
+ const projected = projectBox(box, viewport);
434
+ const next = { ...viewport };
435
+ const availableWidth = rect.width - margin * 2;
436
+ const availableHeight = rect.height - margin * 2;
437
+ if (projected.width >= availableWidth) {
438
+ next.x += (rect.width - projected.width) / 2 - projected.x;
439
+ }
440
+ else if (projected.x < margin) {
441
+ next.x += margin - projected.x;
442
+ }
443
+ else if (projected.x + projected.width > rect.width - margin) {
444
+ next.x -= projected.x + projected.width - (rect.width - margin);
445
+ }
446
+ const adjusted = projectBox(box, next);
447
+ if (adjusted.height >= availableHeight) {
448
+ next.y += (rect.height - adjusted.height) / 2 - adjusted.y;
449
+ }
450
+ else if (adjusted.y < margin) {
451
+ next.y += margin - adjusted.y;
452
+ }
453
+ else if (adjusted.y + adjusted.height > rect.height - margin) {
454
+ next.y -= adjusted.y + adjusted.height - (rect.height - margin);
455
+ }
456
+ return next;
457
+ }
458
+ export function canStartPan(target) {
459
+ if (!target || typeof target.closest !== "function")
460
+ return false;
461
+ return !target.closest(".graph-toolbar, button, input, select, textarea, a");
462
+ }
463
+ export function isPanPastThreshold(dx, dy) {
464
+ return Math.hypot(dx, dy) >= 4;
465
+ }
466
+ export function depth(id, parentOf) {
467
+ let value = 0;
468
+ const visited = new Set();
469
+ let current = parentOf.get(id);
470
+ while (current && !visited.has(current)) {
471
+ visited.add(current);
472
+ value += 1;
473
+ current = parentOf.get(current);
474
+ }
475
+ return value;
476
+ }
477
+ export function isCompositeKind(kind) {
478
+ return kind === "if" || kind === "switch" || kind === "parallel" || kind === "fanout" || kind === "loop";
479
+ }
480
+ function parentRef(id, parentId) {
481
+ return parentId === undefined ? { id } : { id, parentId };
482
+ }
483
+ export function displayStatus(status) {
484
+ return normalizeRuntimeStatus(status);
485
+ }
486
+ function targetStatus(graph, targetId, fallback, selectedOptions) {
487
+ if (graph.mode === "static")
488
+ return fallback;
489
+ const states = graph.runtimeStates.filter(state => state.targetId === targetId);
490
+ if (states.length === 0)
491
+ return fallback;
492
+ return states.find(state => runtimeStateMatches(state, selectedOptions))?.status ?? "not_started";
493
+ }
494
+ function runtimeStateMatches(state, selectedOptions) {
495
+ return state.selectors.every(selection => {
496
+ const option = selectedOptions.get(selection.nodeId);
497
+ return !option || sameSelection(selection, option);
498
+ });
499
+ }
500
+ function sameSelection(selection, option) {
501
+ if (selection.kind === "fanout") {
502
+ if (selection.itemIndex !== undefined && option.itemIndex !== undefined)
503
+ return selection.itemIndex === option.itemIndex;
504
+ if (selection.itemKey !== undefined && option.itemKey !== undefined)
505
+ return selection.itemKey === option.itemKey;
506
+ return false;
507
+ }
508
+ return selection.iteration === option.iteration;
509
+ }
510
+ function selectionFromOption(selector, option) {
511
+ if (selector.kind === "fanout") {
512
+ return {
513
+ nodeId: selector.nodeId,
514
+ kind: "fanout",
515
+ ...(option.itemKey === undefined ? {} : { itemKey: option.itemKey }),
516
+ ...(option.itemIndex === undefined ? {} : { itemIndex: option.itemIndex }),
517
+ };
518
+ }
519
+ return {
520
+ nodeId: selector.nodeId,
521
+ kind: "loop",
522
+ ...(option.iteration === undefined ? {} : { iteration: option.iteration }),
523
+ };
524
+ }
525
+ function selectedOptionsByNode(graph, selections) {
526
+ const selected = new Map();
527
+ for (const selector of graph.selectors) {
528
+ const option = selector.options.find(candidate => candidate.id === selections[selector.nodeId])
529
+ ?? selector.options.find(candidate => candidate.id === selector.defaultOptionId)
530
+ ?? selector.options.at(-1)
531
+ ?? selector.options[0];
532
+ if (option)
533
+ selected.set(selector.nodeId, option);
534
+ }
535
+ return selected;
536
+ }
537
+ function applicableSelectors(graph, selectedOptions) {
538
+ return graph.selectors.map(selector => {
539
+ const options = selector.options.filter(option => option.parentSelections.every(parent => {
540
+ const selected = selectedOptions.get(parent.nodeId);
541
+ return selected ? sameSelection(parent, selected) : true;
542
+ }));
543
+ const defaultOptionId = options.some(option => option.id === selector.defaultOptionId)
544
+ ? selector.defaultOptionId
545
+ : options.at(-1)?.id;
546
+ return {
547
+ ...selector,
548
+ options,
549
+ ...(defaultOptionId ? { defaultOptionId } : {}),
550
+ };
551
+ }).filter(selector => selector.options.length > 0);
552
+ }
553
+ function rankItems(graph) {
554
+ const rank = new Map();
555
+ let index = 0;
556
+ for (const container of graph.containers)
557
+ rank.set(container.id, index++);
558
+ for (const node of graph.nodes)
559
+ rank.set(node.id, index++);
560
+ return rank;
561
+ }
562
+ function orderChildren(ids, edges, rank) {
563
+ const idSet = new Set(ids);
564
+ const outgoing = new Map();
565
+ const indegree = new Map(ids.map(id => [id, 0]));
566
+ for (const edge of edges) {
567
+ if (!idSet.has(edge.source) || !idSet.has(edge.target))
568
+ continue;
569
+ outgoing.set(edge.source, [...(outgoing.get(edge.source) ?? []), edge.target]);
570
+ indegree.set(edge.target, (indegree.get(edge.target) ?? 0) + 1);
571
+ }
572
+ const byRank = (a, b) => (rank.get(a) ?? Number.MAX_SAFE_INTEGER) - (rank.get(b) ?? Number.MAX_SAFE_INTEGER);
573
+ const ready = ids.filter(id => (indegree.get(id) ?? 0) === 0).sort(byRank);
574
+ const ordered = [];
575
+ while (ready.length > 0) {
576
+ const id = ready.shift();
577
+ ordered.push(id);
578
+ for (const target of outgoing.get(id) ?? []) {
579
+ const next = (indegree.get(target) ?? 0) - 1;
580
+ indegree.set(target, next);
581
+ if (next === 0) {
582
+ ready.push(target);
583
+ ready.sort(byRank);
584
+ }
585
+ }
586
+ }
587
+ return ordered.length === ids.length ? ordered : [...ids].sort(byRank);
588
+ }
589
+ function branchLayout(ids, items) {
590
+ if (ids.length <= 1)
591
+ return "vertical";
592
+ const allBranches = ids.every(id => {
593
+ const item = items.get(id);
594
+ return item?.type === "container" && item.container?.kind === "branch";
595
+ });
596
+ return allBranches ? "horizontal" : "vertical";
597
+ }
598
+ function applyDisplayRuntimeState(graph, items, parentOf) {
599
+ deriveSkippedDisplayStatus(graph, items, parentOf);
600
+ const hasActiveDescendant = (id) => {
601
+ const item = items.get(id);
602
+ if (!item)
603
+ return false;
604
+ if (isActiveStatus(item.status))
605
+ return true;
606
+ return item.children.some(hasActiveDescendant);
607
+ };
608
+ for (const item of items.values()) {
609
+ item.active = hasActiveDescendant(item.id);
610
+ item.dimmed = graph.mode === "runtime" && (item.status === "not_started" || item.status === "skipped");
611
+ }
612
+ }
613
+ function deriveSkippedDisplayStatus(graph, items, parentOf) {
614
+ if (graph.mode !== "runtime" || !isCompletedTerminalRun(graph.workflow.status))
615
+ return;
616
+ const markSkippedTree = (id) => {
617
+ const item = items.get(id);
618
+ if (!item || item.status !== "not_started")
619
+ return;
620
+ item.status = "skipped";
621
+ for (const child of item.children)
622
+ markSkippedTree(child);
623
+ };
624
+ const childrenByParent = new Map();
625
+ for (const id of items.keys()) {
626
+ const parent = parentOf.get(id);
627
+ if (!parent)
628
+ continue;
629
+ childrenByParent.set(parent, [...(childrenByParent.get(parent) ?? []), id]);
630
+ }
631
+ for (const siblings of childrenByParent.values()) {
632
+ if (siblings.length < 2)
633
+ continue;
634
+ const hasExecutedSibling = siblings.some(id => {
635
+ const status = items.get(id)?.status;
636
+ return status !== undefined && status !== "not_started" && status !== "skipped";
637
+ });
638
+ if (!hasExecutedSibling)
639
+ continue;
640
+ for (const id of siblings)
641
+ markSkippedTree(id);
642
+ }
643
+ }
644
+ function isCompletedTerminalRun(status) {
645
+ const display = displayRunStatus(status);
646
+ return display === "completed" || display === "canceled";
647
+ }
648
+ function isActiveStatus(status) {
649
+ return isActiveDisplayStatus(status);
650
+ }
651
+ function isAncestor(ancestor, id, parentOf) {
652
+ const visited = new Set();
653
+ let current = parentOf.get(id);
654
+ while (current && !visited.has(current)) {
655
+ if (current === ancestor)
656
+ return true;
657
+ visited.add(current);
658
+ current = parentOf.get(current);
659
+ }
660
+ return false;
661
+ }
662
+ function clamp(value, min, max) {
663
+ return Math.min(max, Math.max(min, value));
664
+ }
665
+ function flowPath(sx, sy, tx, ty) {
666
+ const midY = sy + Math.max(24, (ty - sy) / 2);
667
+ return `M ${sx} ${sy} V ${midY} H ${tx} V ${ty - 1}`;
668
+ }
669
+ function loopPath(sx, sy, tx, ty, source, target) {
670
+ const sideX = Math.max(source.x + source.width, target.x + target.width) + 22;
671
+ return `M ${sx} ${sy} V ${sy + 24} H ${sideX} V ${ty - 24} H ${tx} V ${ty - 1}`;
672
+ }
673
+ //# sourceMappingURL=graph-renderer.js.map